initial commit

This commit is contained in:
2024-11-10 07:27:36 -08:00
commit 5efd61d236
43 changed files with 1503 additions and 0 deletions

1
project_name/VERSION Normal file
View File

@@ -0,0 +1 @@
0.1.0

3
project_name/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from .base import create_app, create_app_wsgi
__all__ = ["create_app", "create_app_wsgi"]

13
project_name/__main__.py Normal file
View File

@@ -0,0 +1,13 @@
import click
from flask.cli import FlaskGroup
from . import create_app_wsgi
@click.group(cls=FlaskGroup, create_app=create_app_wsgi)
def main():
"""Management script for the project_name application."""
if __name__ == "__main__": # pragma: no cover
main()

21
project_name/base.py Normal file
View File

@@ -0,0 +1,21 @@
from dynaconf import FlaskDynaconf
from flask import Flask
def create_app(**config):
app = Flask(__name__)
FlaskDynaconf(app) # config managed by Dynaconf
app.config.load_extensions(
"EXTENSIONS"
) # Load extensions from settings.toml
app.config.update(config) # Override with passed config
return app
def create_app_wsgi():
# workaround for Flask issue
# that doesn't allow **config
# to be passed to create_app
# https://github.com/pallets/flask/issues/4170
app = create_app()
return app

View File

33
project_name/ext/admin.py Normal file
View File

@@ -0,0 +1,33 @@
from flask_admin import Admin
from flask_admin.base import AdminIndexView
from flask_admin.contrib import sqla
from flask_simplelogin import login_required
from werkzeug.security import generate_password_hash
from project_name.ext.database import db
from project_name.models import Product, User
# Proteck admin with login / Monkey Patch
AdminIndexView._handle_view = login_required(AdminIndexView._handle_view)
sqla.ModelView._handle_view = login_required(sqla.ModelView._handle_view)
admin = Admin()
class UserAdmin(sqla.ModelView):
column_list = ["username"]
can_edit = False
def on_model_change(self, form, model, is_created):
model.password = generate_password_hash(model.password)
def init_app(app):
admin.name = app.config.TITLE
admin.template_mode = app.config.FLASK_ADMIN_TEMPLATE_MODE
admin.init_app(app)
# Add admin page for Product
admin.add_view(sqla.ModelView(Product, db.session))
# Add admin page for User
admin.add_view(UserAdmin(User, db.session))

33
project_name/ext/auth.py Normal file
View File

@@ -0,0 +1,33 @@
from flask_simplelogin import SimpleLogin
from werkzeug.security import check_password_hash, generate_password_hash
from project_name.ext.database import db
from project_name.models import User
def verify_login(user):
"""Validates user login"""
username = user.get("username")
password = user.get("password")
if not username or not password:
return False
existing_user = User.query.filter_by(username=username).first()
if not existing_user:
return False
if check_password_hash(existing_user.password, password):
return True
return False
def create_user(username, password):
"""Creates a new user"""
if User.query.filter_by(username=username).first():
raise RuntimeError(f"{username} already exists")
user = User(username=username, password=generate_password_hash(password))
db.session.add(user)
db.session.commit()
return user
def init_app(app):
SimpleLogin(app, login_checker=verify_login)

View File

@@ -0,0 +1,43 @@
import click
from project_name.ext.auth import create_user
from project_name.ext.database import db
from project_name.models import Product
def create_db():
"""Creates database"""
db.create_all()
def drop_db():
"""Cleans database"""
db.drop_all()
def populate_db():
"""Populate db with sample data"""
data = [
Product(
id=1, name="Ciabatta", price="10", description="Italian Bread"
),
Product(id=2, name="Baguete", price="15", description="French Bread"),
Product(id=3, name="Pretzel", price="20", description="German Bread"),
]
db.session.bulk_save_objects(data)
db.session.commit()
return Product.query.all()
def init_app(app):
# add multiple commands in a bulk
for command in [create_db, drop_db, populate_db]:
app.cli.add_command(app.cli.command()(command))
# add a single command
@app.cli.command()
@click.option("--username", "-u")
@click.option("--password", "-p")
def add_user(username, password):
"""Adds a new user to the database"""
return create_user(username, password)

View File

@@ -0,0 +1,7 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def init_app(app):
db.init_app(app)

View File

@@ -0,0 +1,13 @@
from flask import Blueprint
from flask_restful import Api
from .resources import ProductItemResource, ProductResource
bp = Blueprint("restapi", __name__, url_prefix="/api/v1")
api = Api(bp)
def init_app(app):
api.add_resource(ProductResource, "/product/")
api.add_resource(ProductItemResource, "/product/<product_id>")
app.register_blueprint(bp)

View File

@@ -0,0 +1,35 @@
from flask import abort, jsonify
from flask_restful import Resource
from flask_simplelogin import login_required
from project_name.models import Product
class ProductResource(Resource):
def get(self):
products = Product.query.all() or abort(204)
return jsonify(
{"products": [product.to_dict() for product in products]}
)
@login_required(basic=True, username="admin")
def post(self):
"""
Creates a new product.
Only admin user authenticated using basic auth can post
Basic takes base64 encripted username:password.
# curl -XPOST localhost:5000/api/v1/product/ \
# -H "Authorization: Basic Y2h1Y2s6bm9ycmlz" \
# -H "Content-Type: application/json"
"""
return NotImplementedError(
"Someone please complete this example and send a PR :)"
)
class ProductItemResource(Resource):
def get(self, product_id):
product = Product.query.filter_by(id=product_id).first() or abort(404)
return jsonify(product.to_dict())

View File

@@ -0,0 +1,16 @@
from flask import Blueprint
from .views import index, only_admin, product, secret
bp = Blueprint("webui", __name__, template_folder="templates")
bp.add_url_rule("/", view_func=index)
bp.add_url_rule(
"/product/<product_id>", view_func=product, endpoint="productview"
)
bp.add_url_rule("/secret", view_func=secret, endpoint="secret")
bp.add_url_rule("/only_admin", view_func=only_admin, endpoint="onlyadmin")
def init_app(app):
app.register_blueprint(bp)

View File

@@ -0,0 +1,29 @@
{% extends "bootstrap/base.html" %}
{% block title %}{{config.get('TITLE')}}{% endblock %}
{% block navbar %}
<div class="navbar">
<div class="navbar-header">
<a class="navbar-brand" href="#">
<img alt="Brand" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAB+0lEQVR4AcyYg5LkUBhG+1X2PdZGaW3btm3btm3bHttWrPomd1r/2Jn/VJ02TpxcH4CQ/dsuazWgzbIdrm9dZVd4pBz4zx2igTaFHrhvjneVXNHCSqIlFEjiwMyyyOBilRgGSqLNF1jnwNQdIvAt48C3IlBmHCiLQHC2zoHDu6zG1iXn6+y62ScxY9AODO6w0pvAqf23oSE4joOfH6OxfMoRnoGUm+de8wykbFt6wZtA07QwtNOqKh3ZbS3Wzz2F+1c/QJY0UCJ/J3kXWJfv7VhxCRRV1jGw7XI+gcO7rEFFRvdYxydwcPsVsC0bQdKScngt4iUTD4Fy/8p7PoHzRu1DclwmgmiqgUXjD3oTKHbAt869qdJ7l98jNTEblPTkXMwetpvnftA0LLHb4X8kiY9Kx6Q+W7wJtG0HR7fdrtYz+x7iya0vkEtUULIzCjC21wY+W/GYXusRH5kGytWTLxgEEhePPwhKYb7EK3BQuxWwTBuUkd3X8goUn6fMHLyTT+DCsQdAEXNzSMeVPAJHdF2DmH8poCREp3uwm7HsGq9J9q69iuunX6EgrwQVObjpBt8z6rdPfvE8kiiyhsvHnomrQx6BxYUyYiNS8f75H1w4/ISepDZLoDhNJ9cdNUquhRsv+6EP9oNH7Iff2A9g8h8CLt1gH0Qf9NMQAFnO60BJFQe0AAAAAElFTkSuQmCC">
</a>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
<h1>{{config.get('TITLE')}}</h1>
<div class="jumbotron">
<ul class="list-group">
{% for product in products %}
<li class="list-group-item">
<a href="{{url_for('webui.productview', product_id=product.id)}}">{{product.name}}- {{ "%0.2f" | format(product.price)}}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends "index.html" %}
{% block content %}
<div class="container">
<h1>{{ product.name }}</h1>
<div class="jumbotron">
<h2>R$ {{ "%0.2f" | format(product.price)}}</h2>
<p>
{{product.description}}
</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
from flask import abort, render_template
from flask_simplelogin import login_required
from project_name.models import Product
def index():
products = Product.query.all()
return render_template("index.html", products=products)
def product(product_id):
product = Product.query.filter_by(id=product_id).first() or abort(
404, "produto nao encontrado"
)
return render_template("product.html", product=product)
@login_required
def secret():
return "This can be seen only if user is logged in"
@login_required(username="admin")
def only_admin():
return "only admin user can see this text"

16
project_name/models.py Normal file
View File

@@ -0,0 +1,16 @@
from sqlalchemy_serializer import SerializerMixin
from project_name.ext.database import db
class Product(db.Model, SerializerMixin):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140))
price = db.Column(db.Numeric())
description = db.Column(db.Text)
class User(db.Model, SerializerMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(140))
password = db.Column(db.String(512))