initial commit
This commit is contained in:
0
project_name/ext/__init__.py
Normal file
0
project_name/ext/__init__.py
Normal file
33
project_name/ext/admin.py
Normal file
33
project_name/ext/admin.py
Normal 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
33
project_name/ext/auth.py
Normal 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)
|
||||
43
project_name/ext/commands.py
Normal file
43
project_name/ext/commands.py
Normal 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)
|
||||
7
project_name/ext/database.py
Normal file
7
project_name/ext/database.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
def init_app(app):
|
||||
db.init_app(app)
|
||||
13
project_name/ext/restapi/__init__.py
Normal file
13
project_name/ext/restapi/__init__.py
Normal 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)
|
||||
35
project_name/ext/restapi/resources.py
Normal file
35
project_name/ext/restapi/resources.py
Normal 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())
|
||||
16
project_name/ext/webui/__init__.py
Normal file
16
project_name/ext/webui/__init__.py
Normal 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)
|
||||
29
project_name/ext/webui/templates/index.html
Normal file
29
project_name/ext/webui/templates/index.html
Normal 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 %}
|
||||
13
project_name/ext/webui/templates/product.html
Normal file
13
project_name/ext/webui/templates/product.html
Normal 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 %}
|
||||
26
project_name/ext/webui/views.py
Normal file
26
project_name/ext/webui/views.py
Normal 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"
|
||||
Reference in New Issue
Block a user