Merge branch 'feature/4-import-kontor-flask-into-directory-flask' into 'develop/0.1.0'

Import kontor-flask into directory flask

Closes #4

See merge request tpeetz/kontor!2
This commit was merged in pull request #46.
This commit is contained in:
2025-01-08 21:44:47 +00:00
27 changed files with 618 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
.idea
+2
View File
@@ -0,0 +1,2 @@
# Kontor Flask
+5
View File
@@ -0,0 +1,5 @@
from kontor import create_app
if __name__ == '__main__':
app = create_app()
app.run(host="0.0.0.0", port=8000, debug=True)
+61
View File
@@ -0,0 +1,61 @@
from flask import Flask, render_template
from flask_jwt_extended import JWTManager
from kontor import config
from kontor.extensions import db, ma
from logging.config import dictConfig
dictConfig({
'version': 1,
'formatters': {'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
}},
'handlers': {'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
}},
'root': {
'level': 'INFO',
'handlers': ['wsgi']
}
})
app = Flask(__name__)
def create_app(config_class=config.Config):
app.config.from_object(config_class)
db.init_app(app)
ma.init_app(app)
# Initialize Flask extensions here
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
jwt = JWTManager(app)
with app.app_context():
# db.create_all()
db.reflect()
# Register blueprints here
from kontor.main import bp as main_bp
app.register_blueprint(main_bp)
from kontor.comics import comics_bp
app.register_blueprint(comics_bp, url_prefix='/comics')
from kontor.api import api_bp
app.register_blueprint(api_bp, url_prefix='/api/v1')
from kontor.comics import comics_api
app.register_blueprint(comics_api, url_prefix='/api/v1/comics')
from kontor.media import media_bp
app.register_blueprint(media_bp, url_prefix='/media')
from kontor.media import media_api
app.register_blueprint(media_api, url_prefix='/api/v1/media')
# from kontor.auth.auth import auth_bp
# from kontor.cart.cart import cart_bp
# from kontor.general.general import general_bp
# app.register_blueprint(auth_bp)
# app.register_blueprint(cart_bp, url_prefix='/cart')
# app.register_blueprint(general_bp)
return app
+5
View File
@@ -0,0 +1,5 @@
from flask import Blueprint
api_bp = Blueprint('api_bp', __name__)
from kontor.api import routes
+30
View File
@@ -0,0 +1,30 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token
from kontor.api import api_bp
@api_bp.route('/')
def index():
modules = ['comics']
return jsonify(modules)
# Create a route to authenticate your users and return JWTs. The
# create_access_token() function is used to actually generate the JWT.
@api_bp.route("/login", methods=["POST"])
def login():
username = request.json.get("username", None)
password = request.json.get("password", None)
if username != "test" or password != "test":
return jsonify({"msg": "Bad username or password"}), 401
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token)
@api_bp.route('/protected', methods=['GET'])
@jwt_required()
def protected():
current_user = get_jwt_identity()
return {'message': f'Hello, {current_user}!'}
+22
View File
@@ -0,0 +1,22 @@
import bcrypt
from flask import session
from flask_httpauth import HTTPBasicAuth
from kontor import app
from kontor.auth.models import User
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username, password):
if username is None:
return False
# Add your authentication logic here
app.logger.info("login user %s", username)
user = User.query.filter_by(user_name=username).first()
app.logger.info("User: %s", user)
app.logger.info("Stored Password: '%s' Hashed Password: '%s'", user.password, bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()))
if 'user_name' in session and session['user_name'] == username:
return True
return False
+45
View File
@@ -0,0 +1,45 @@
from kontor.extensions import db, ma
from sqlalchemy.sql import func
class User(db.Model):
# __table__ = db.metadata.tables["publisher"]
__tablename__ = "user"
__table_args__ = {'extend_existing': True}
id = db.Column(db.String, primary_key=True)
created_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
version = db.Column(db.Integer)
enabled = db.Column(db.SmallInteger)
email = db.Column(db.String)
first_name = db.Column(db.String)
last_name = db.Column(db.String)
user_name = db.Column(db.String)
password = db.Column(db.String)
token = db.Column(db.String)
token_expired = db.Column(db.SmallInteger)
def is_token_valid(self):
return self.review == 'b\x01'
def is_user_enabled(self):
return self.should_download == 'b\x01'
class Role(db.Model):
__tablename__ = "role"
__table_args__ = {'extend_existing': True}
id = db.Column(db.String, primary_key=True)
created_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
version = db.Column(db.Integer)
name = db.Column(db.String)
class AuthorizationMatrix(db.Model):
__tablename__ = "authorization_matrix"
__table_args__ = {'extend_existing': True}
id = db.Column(db.String, primary_key=True)
created_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
version = db.Column(db.Integer)
role_id = db.Column(db.String, db.ForeignKey("role.id"))
user_id = db.Column(db.String, db.ForeignKey("user.id"))
+9
View File
@@ -0,0 +1,9 @@
from flask import Blueprint
comics_bp = Blueprint('comics_bp', __name__,
template_folder='templates',
static_folder='static', static_url_path='assets')
comics_api = Blueprint('comics_api', __name__)
from kontor.comics import routes
from kontor.comics import api
+28
View File
@@ -0,0 +1,28 @@
from flask import Blueprint, render_template, jsonify
from kontor.comics import comics_api
from kontor.models import Comic, comics_schema, Publisher, comic_schema, publisher_schema, publishers_schema
@comics_api.route('/')
def index():
comics = Comic.query.all()
return comics_schema.dump(comics)
@comics_api.route('/comic/<string:comic_id>')
def view(comic_id):
comic = Comic.query.get(comic_id)
return comic_schema.dump(comic)
@comics_api.route('/publisher/')
def publisher_all():
publishers = Publisher.query.all()
return publishers_schema.dump(publishers)
@comics_api.route('/publisher/<string:id>')
def publisher_detail(publisher_id):
publisher = Publisher.query.get(publisher_id)
return publisher_schema.dump(publisher)
+16
View File
@@ -0,0 +1,16 @@
from flask import Blueprint, render_template
from kontor.comics import comics_bp
from kontor.models import Comic
@comics_bp.route('/')
def index():
comics = Comic.query.all()
return render_template('comics/list.html', comics=comics)
@comics_bp.route('/comic/<string:comic_id>')
def view(comic_id):
comic = Comic.query.get(comic_id)
return render_template('comics/view.html', comic=comic)
@@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} Comics {% endblock %}</h1>
</span>
<div class="comics">
<h2>Comics Blueprint</h2>
{% for comic in comics %}
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('comics_bp.view', comic_id=comic.id)}}">{{ comic.title }}</a>
</p>
</b>
<b><p class="identifier">{{ comic.publisher.name }}</p></b>
<div class="date">
<h4>Created</h4>
<p>{{ comic.created_date }}</p>
<h4>Modified</h4>
<p>{{ comic.last_modified_date }}</p>
</div>
<p>Version: {{ comic.version }}</p>
<p>Review: {{ comic.review }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} Comics {% endblock %}</h1>
</span>
<div class="comics">
<h2>Comics Blueprint</h2>
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('comics_bp.view', comic_id=comic.id)}}">{{ comic.title }}</a>
</p>
</b>
<b>
<p class="identifier">{{ comic.id }}</p>
</b>
<div class="date">
<h4>Created</h4>
<p>{{ comic.created_date }}</p>
<h4>Modified</h4>
<p>{{ comic.last_modified_date }}</p>
</div>
<p>Version: {{ comic.version }}</p>
</div>
</div>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
# SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'kontor.db')
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'mariadb+mariadbconnector://kontor:kontor@localhost/kontor'
SQLALCHEMY_TRACK_MODIFICATIONS = False
+5
View File
@@ -0,0 +1,5 @@
import flask_sqlalchemy
from flask_marshmallow import Marshmallow
db = flask_sqlalchemy.SQLAlchemy()
ma = Marshmallow()
+5
View File
@@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('main', __name__)
from kontor.main import routes
+18
View File
@@ -0,0 +1,18 @@
from flask import render_template, session
from kontor import app
from kontor.main import bp
from kontor.auth import auth
@bp.route('/')
@auth.login_required
def index():
return render_template('index.html')
@bp.route('/logout')
def logout():
app.logger.info("logout")
auth.current_user()
session['user_name'] = None
return render_template('index.html')
+9
View File
@@ -0,0 +1,9 @@
from flask import Blueprint
media_bp = Blueprint('media_bp', __name__,
template_folder='templates',
static_folder='static', static_url_path='assets')
media_api = Blueprint('media_api', __name__)
from kontor.media import routes
from kontor.media import api
+18
View File
@@ -0,0 +1,18 @@
from flask import Blueprint, render_template, jsonify
from kontor import app
from kontor.media import media_api
from kontor.media.models import MediaFile, mediafile_schema, mediafiles_schema
@media_api.route('/')
def mediafile_list():
app.logger.info("get all media files")
files = MediaFile.query.all()
return mediafiles_schema.dump(files)
@media_api.route('/mediafile/<string:mediafile_id>')
def mediafile_detail(file_id):
file = MediaFile.query.get(file_id)
return mediafile_schema.dump(file)
+43
View File
@@ -0,0 +1,43 @@
from kontor.extensions import db, ma
from sqlalchemy.sql import func
class MediaFile(db.Model):
# __table__ = db.metadata.tables["publisher"]
__tablename__ = "media_file"
__table_args__ = {'extend_existing': True}
id = db.Column(db.String, primary_key=True)
created_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
version = db.Column(db.Integer)
title = db.Column(db.String)
file_name = db.Column(db.String)
url = db.Column(db.String)
path = db.Column(db.String)
cloud_link = db.Column(db.String)
review = db.Column(db.SmallInteger)
should_download = db.Column(db.Boolean)
def is_review(self):
return self.review == 'b\x00'
def is_download(self):
return self.should_download == 'b\x00'
class MediaFileSchema(ma.SQLAlchemySchema):
class Meta:
model = MediaFile
fields = ("id", "created_date", "last_modified_date", "version", "title", "file_name", "url", "path", "cloud_link", "review", "should_download", "_links")
# Smart hyperlinking
_links = ma.Hyperlinks(
{
"self": ma.URLFor("media_api.mediafile_detail", values=dict(mediafile_id="<id>")),
"collection": ma.URLFor("media_api.mediafile_list"),
}
)
mediafile_schema = MediaFileSchema()
mediafiles_schema = MediaFileSchema(many=True)
+16
View File
@@ -0,0 +1,16 @@
from flask import Blueprint, render_template
from kontor.media import media_bp
from kontor.media.models import MediaFile
@media_bp.route('/')
def mediafile_list():
files = MediaFile.query.all()
return render_template('media/mediafile_list.html', mediafiles=files)
@media_bp.route('/mediafile/<string:mediafile_id>')
def mediafile_detail(mediafile_id):
mediafile = MediaFile.query.get(mediafile_id)
return render_template('media/mediafile_detail.html', mediafile=mediafile)
@@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} MediaFile {% endblock %}</h1>
</span>
<div class="comics">
<h2>MediaFile Blueprint</h2>
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('media_bp.mediafile_detail', mediafile_id=mediafile.id)}}">{{ mediafile.title }}</a>
</p>
</b>
<b>
<p class="identifier">{{ mediafile.id }}</p>
</b>
<div class="date">
<h4>Created</h4>
<p>{{ mediafile.created_date }}</p>
<h4>Modified</h4>
<p>{{ mediafile.last_modified_date }}</p>
</div>
<p>Version: {{ mediafile.version }}</p>
<p>Review: {{ mediafile.is_review() }}</p>
<p>Should Download: {{mediafile.is_download() }}</p>
</div>
</div>
{% endblock %}
@@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} MediaFile {% endblock %}</h1>
</span>
<div class="comic">
<h2>MediaFile Blueprint</h2>
{% for mediafile in mediafiles %}
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('media_bp.mediafile_detail', mediafile_id=mediafile.id)}}">{{ mediafile.title }}</a>
</p>
</b>
<b><p class="identifier">{{ mediafile.id }}</p></b>
<div class="date">
<h4>Created</h4>
<p>{{ mediafile.created_date }}</p>
<h4>Modified</h4>
<p>{{ mediafile.last_modified_date }}</p>
</div>
<p>Version: {{ mediafile.version }}</p>
<p>Review: {{ mediafile.is_review() }}</p>
<p>Should Download: {{ mediafile.is_download() }}</p>
<p>Links: {{ mediafile._links }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
from kontor.extensions import db, ma
from sqlalchemy.sql import func
class Publisher(db.Model):
# __table__ = db.metadata.tables["publisher"]
__tablename__ = "publisher"
__table_args__ = {'extend_existing': True}
id = db.Column(db.String, primary_key=True)
created_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
version = db.Column(db.Integer)
name = db.Column(db.String)
comics = db.relationship('Comic', back_populates='publisher', lazy='dynamic')
class PublisherSchema(ma.SQLAlchemySchema):
class Meta:
model = Publisher
comics = ma.List(ma.HyperlinkRelated("comics_api.index"))
publisher_schema = PublisherSchema()
publishers_schema = PublisherSchema(many=True)
class Comic(db.Model):
# __table__ = db.metadata.tables["comic"]
__tablename__ = "comic"
__table_args__ = {'extend_existing': True}
id = db.Column(db.String, primary_key=True)
created_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
version = db.Column(db.Integer)
title = db.Column(db.String)
publisher_id = db.Column(db.String, db.ForeignKey("publisher.id"))
publisher = db.relationship("Publisher", back_populates="comics")
class ComicSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Comic
include_fk = True
publisher = ma.HyperlinkRelated("comics_api.publisher_detail")
comic_schema = ComicSchema()
comics_schema = ComicSchema(many=True)
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %} {% endblock %} - FlaskApp</title>
<style>
h2 {
width: 100%;
}
.title {
margin: 5px;
width: 100%;
}
.content {
margin: 5px;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.comic {
flex: 20%;
padding: 10px;
margin: 5px;
background-color: #f3f3f3;
inline-size: 100%;
}
.identifier a {
color: #00a36f;
text-decoration: none;
}
.date {
padding: 10px;
margin: 5px;
background-color: #ffffff;
color: #004835;
}
nav a {
color: #d64161;
font-size: 3em;
margin-left: 50px;
text-decoration: none;
}
.title a {
color: #00a36f;
text-decoration: none;
}
</style>
</head>
<body>
<nav>
<a href="{{ url_for('main.index') }}">Kontor</a>
<a href="{{ url_for('comics_bp.index') }}">Comics</a>
<a href="{{ url_for('media_bp.mediafile_list') }}">Media</a>
</nav>
<hr>
<div class="content">
{% block content %} {% endblock %}
</div>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% block content %}
<span class="title"><h1>{% block title %} The Home Page of Kontor {% endblock %}</h1></span>
<div class="content">
<h2>This is the main Flask blueprint</h2>
</div>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
project kontor "Kontor" "0.1.0" 2024-12-05 +5m {
timezone "Europe/Berlin"
timeformat "%d.%m.%Y"
numberformat "-" "" "" "," 1
currencyformat "-" "" "" "," 0
currency "EUR"
scenario plan "Plan" {
scenario real "Realität"
}
}
resource gcpce "Google Cloud Compute Engine" {
efficiency 0.0
rate 0.25
}
task flask "Kontor-Flask" {
task import "Import repository kontor-flask into directory flask"
}
task springboot "Springboot Vaadin" {
task import "Import repository kontor-spring into directory springboot"
}
taskreport "Arbeitsliste" {
formats html
hidetask ~isleaf()
sorttasks plan.end.up
}