diff --git a/flask/.gitignore b/flask/.gitignore new file mode 100644 index 0000000..259ed2a --- /dev/null +++ b/flask/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.idea diff --git a/flask/README.md b/flask/README.md new file mode 100644 index 0000000..6aad8a5 --- /dev/null +++ b/flask/README.md @@ -0,0 +1,2 @@ +# Kontor Flask + diff --git a/flask/app.py b/flask/app.py new file mode 100644 index 0000000..dc827b3 --- /dev/null +++ b/flask/app.py @@ -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) diff --git a/flask/config.py b/flask/config.py deleted file mode 100644 index 4ea5e78..0000000 --- a/flask/config.py +++ /dev/null @@ -1,47 +0,0 @@ -# config.py - - -class Config(object): - """ - Common configurations - """ - - # Put any configurations here that are common across all environments - host = '127.0.0.1' - port = 8500 - database = 'kontor' - - -class DevelopmentConfig(Config): - """ - Development configurations - """ - - DEBUG = True - host = '0.0.0.0' - database = 'kontor_dev' - - -class ProductionConfig(Config): - """ - Production configurations - """ - - DEBUG = False - -class TestingConfig(Config): - """ - Testing configurations - """ - - TESTING = True - DEBUG = True - port = 8600 - database = 'kontor_test' - -app_config = { - 'development': DevelopmentConfig, - 'production': ProductionConfig, - 'testing': TestingConfig -} - diff --git a/flask/docs/Makefile b/flask/docs/Makefile deleted file mode 100644 index d7218b4..0000000 --- a/flask/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = KontorFlask -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/flask/docs/conf.py b/flask/docs/conf.py deleted file mode 100644 index 7278f2d..0000000 --- a/flask/docs/conf.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Kontor Flask documentation build configuration file, created by -# sphinx-quickstart on Mon Nov 6 08:53:04 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'Kontor Flask' -copyright = '2017, Thomas Peetz' -author = 'Thomas Peetz' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.1' -# The full version, including alpha/beta/rc tags. -release = '0.0.7' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'de' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - ] -} - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'KontorFlaskdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'KontorFlask.tex', 'Kontor Flask Documentation', - 'Thomas Peetz', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'kontorflask', 'Kontor Flask Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'KontorFlask', 'Kontor Flask Documentation', - author, 'KontorFlask', 'One line description of project.', - 'Miscellaneous'), -] - - - -# -- Options for Epub output ---------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/flask/docs/index.rst b/flask/docs/index.rst deleted file mode 100644 index ca4df98..0000000 --- a/flask/docs/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. Kontor Flask documentation master file, created by - sphinx-quickstart on Mon Nov 6 08:53:04 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Kontor Flask's documentation! -======================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/flask/kontor/__init__.py b/flask/kontor/__init__.py index 961e132..f8cb55c 100644 --- a/flask/kontor/__init__.py +++ b/flask/kontor/__init__.py @@ -1,74 +1,61 @@ -""" -Module kontor implements Kontor application. -""" +from flask import Flask, render_template +from flask_jwt_extended import JWTManager -from flask import Flask -from flask_bootstrap import Bootstrap -from flask_login import LoginManager -from pymodm import connect +from kontor import config +from kontor.extensions import db, ma +from logging.config import dictConfig -# local imports -from config import app_config -from .version import __version__ +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__) -_LOGIN_MANAGER_ = LoginManager() +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) -def get_host(config_name): - """ - Returns host address from configuration. - :param config_name: - :return: host address - """ - host = app_config[config_name].host - return host + 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') -def get_port(config_name): - """ - Returns port number from configuration. - :param config_name: - :return: port number - """ - port = app_config[config_name].port - return port - - -def create_app(config_name): - """ - Returns Flask application. - :param config_name: - :return: Flask application - """ - app = Flask(__name__, instance_relative_config=True) - app.config.from_object(app_config[config_name]) - app.config.from_pyfile('config.py') - - database = "mongodb://localhost:27017/{}".format(app_config[config_name].database) - connect(database, alias="kontor") - - Bootstrap(app) - _LOGIN_MANAGER_.init_app(app) - _LOGIN_MANAGER_.login_message = "You must be logged in to access this page." - _LOGIN_MANAGER_.login_view = "auth.login" - - from .admin.views import ADMIN as ADMIN_BLUEPRINT - app.register_blueprint(ADMIN_BLUEPRINT, url_prefix='/admin') - - from .auth.views import AUTH as AUTH_BLUEPRINT - app.register_blueprint(AUTH_BLUEPRINT) - - from .home.views import HOME as HOME_BLUEPRINT - app.register_blueprint(HOME_BLUEPRINT) - - from .comics.views import COMIC as COMIC_BLUEPRINT - app.register_blueprint(COMIC_BLUEPRINT, url_prefix='/comics') - - from .library.views import LIBRARY as LIBRARY_BLUEPRINT - app.register_blueprint(LIBRARY_BLUEPRINT, url_prefix='/library') - - from .tysc.views import TYSC as TYSC_BLUEPRINT - app.register_blueprint(TYSC_BLUEPRINT, url_prefix='/tysc') + 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 diff --git a/flask/kontor/admin/__init__.py b/flask/kontor/admin/__init__.py deleted file mode 100644 index ca00a4b..0000000 --- a/flask/kontor/admin/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Module admin implements administration functions. -""" -from . import views diff --git a/flask/kontor/admin/forms.py b/flask/kontor/admin/forms.py deleted file mode 100644 index d05cfaf..0000000 --- a/flask/kontor/admin/forms.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Define form to edit users -""" -from flask_wtf import FlaskForm -from wtforms import StringField, SubmitField -from wtforms.validators import DataRequired - - -class UserEditForm(FlaskForm): - """ - Form for admin to edit users - """ - email = StringField('Email', validators=[DataRequired()]) - username = StringField('Username', validators=[DataRequired()]) - first_name = StringField('First Name', validators=[DataRequired()]) - last_name = StringField('Last Name', validators=[DataRequired()]) - submit = SubmitField('Submit') diff --git a/flask/kontor/admin/views.py b/flask/kontor/admin/views.py deleted file mode 100644 index e96d8f0..0000000 --- a/flask/kontor/admin/views.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Define routing rules for managing users -""" -from flask import abort, Blueprint, flash, redirect, render_template, url_for -from flask_login import current_user, login_required - -from .forms import UserEditForm -from ..models import User - -ADMIN = Blueprint('admin', __name__) - - -def check_admin(): - """ - Prevent non-admins from accessing the page - """ - if not current_user.is_admin: - abort(403) - - -# User Views -@ADMIN.route('/users') -@login_required -def list_users(): - """ - List all users - """ - check_admin() - - users = User.objects.all() - return render_template('admin/users/users.html', - users=users, title='Users') - - -@ADMIN.route('/users/edit/', methods=['GET', 'POST']) -@login_required -def edit_user(user_id): - """ - Edit an user. - """ - check_admin() - - user = User.objects.get({'username': user_id}) - - # prevent admin from being assigned a department or role - if user.is_admin: - abort(403) - - form = UserEditForm(obj=user) - if form.validate_on_submit(): - user.email = form.email.data - user.username = form.username.data - user.first_name = form.first_name.data - user.last_name = form.last_name.data - user.save() - flash('You have successfully edited an user.') - - # redirect to the roles page - return redirect(url_for('admin.list_users')) - - return render_template('admin/users/user.html', - user=user, form=form, - title='Edit User') diff --git a/flask/kontor/api/__init__.py b/flask/kontor/api/__init__.py new file mode 100644 index 0000000..b26fcb6 --- /dev/null +++ b/flask/kontor/api/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +api_bp = Blueprint('api_bp', __name__) + +from kontor.api import routes diff --git a/flask/kontor/api/routes.py b/flask/kontor/api/routes.py new file mode 100644 index 0000000..4e9b489 --- /dev/null +++ b/flask/kontor/api/routes.py @@ -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}!'} diff --git a/flask/kontor/auth/__init__.py b/flask/kontor/auth/__init__.py index 2d9b1ca..9da7988 100644 --- a/flask/kontor/auth/__init__.py +++ b/flask/kontor/auth/__init__.py @@ -1,4 +1,22 @@ -""" -Module auth implements authentication functions. -""" -from . import views +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 \ No newline at end of file diff --git a/flask/kontor/auth/forms.py b/flask/kontor/auth/forms.py deleted file mode 100644 index 6e4d6dd..0000000 --- a/flask/kontor/auth/forms.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Contains forms for authentication. -""" -from flask_wtf import FlaskForm -from wtforms import PasswordField, StringField, SubmitField, ValidationError -from wtforms.validators import DataRequired, Email, EqualTo - -from ..models import User - - -class RegistrationForm(FlaskForm): - """ - Form for users to create new account - """ - email = StringField('Email', validators=[DataRequired(), Email()]) - username = StringField('Username', validators=[DataRequired()]) - first_name = StringField('First Name', validators=[DataRequired()]) - last_name = StringField('Last Name', validators=[DataRequired()]) - password = PasswordField('Password', validators=[DataRequired(), - EqualTo('confirm_password') - ]) - confirm_password = PasswordField('Confirm Password') - submit = SubmitField('Register') - - def validate_email(self, field): - """ - Check if email is already in use. - :param field: - :return: - """ - if User.objects.raw({'email': field.data}).count() > 0: - raise ValidationError('Email is already in use.') - - def validate_username(self, field): - """ - check if username is already in use. - :param field: - :return: - """ - if User.objects.raw({'username': field.data}).count() > 0: - raise ValidationError('Username is already in use.') - - -class LoginForm(FlaskForm): - """ - Form for users to login - """ - email = StringField('Email', validators=[DataRequired(), Email()]) - password = PasswordField('Password', validators=[DataRequired()]) - submit = SubmitField('Login') diff --git a/flask/kontor/auth/models.py b/flask/kontor/auth/models.py new file mode 100644 index 0000000..fc3b8d4 --- /dev/null +++ b/flask/kontor/auth/models.py @@ -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")) diff --git a/flask/kontor/auth/views.py b/flask/kontor/auth/views.py deleted file mode 100644 index 217ae18..0000000 --- a/flask/kontor/auth/views.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Define routing rules for registering users and login and logout. -""" -from flask import Blueprint, flash, redirect, render_template, url_for -from flask_login import login_required, login_user, logout_user -from .forms import LoginForm, RegistrationForm -from ..models import User - -AUTH = Blueprint('auth', __name__) - - -@AUTH.route('/register', methods=['GET', 'POST']) -def register(): - """ - Handle requests to the /register route - Add an employee to the database through the registration form - """ - form = RegistrationForm() - if form.validate_on_submit(): - user = User() - user.email = form.email.data - user.username = form.username.data - user.first_name = form.first_name.data - user.last_name = form.last_name.data - user.password = form.password.data - # add employee to the database - user.save() - flash('You have successfully registered! You may now login.') - - # redirect to the login page - return redirect(url_for('auth.login')) - - # load registration template - return render_template('auth/register.html', form=form, title='Register') - - -@AUTH.route('/login', methods=['GET', 'POST']) -def login(): - """ - Handle requests to the /login route - Validate given email with matching password - :return: - """ - form = LoginForm() - if form.validate_on_submit(): - - # check whether employee exists in the database and whether - # the password entered matches the password in the database - user = User.objects.get({'email': form.email.data}) - if user is not None and user.verify_password( - form.password.data): - # log employee in - login_user(user) - - # redirect to the appropriate dashboard page - if user.is_admin: - return redirect(url_for('home.admin_dashboard')) - else: - return redirect(url_for('home.dashboard')) - - # when login details are incorrect - else: - flash('Invalid email or password.') - - # load login template - return render_template('auth/login.html', form=form, title='Login') - -@AUTH.route('/logout') -@login_required -def logout(): - """ - Handle requests to the /logout route - Log an employee out through the logout link - """ - logout_user() - flash('You have successfully been logged out.') - - # redirect to the login page - return redirect(url_for('auth.login')) diff --git a/flask/kontor/comics/__init__.py b/flask/kontor/comics/__init__.py index e947d82..12bb371 100644 --- a/flask/kontor/comics/__init__.py +++ b/flask/kontor/comics/__init__.py @@ -1,3 +1,9 @@ -""" -Define routing rules for comic related information -""" +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 diff --git a/flask/kontor/comics/api.py b/flask/kontor/comics/api.py new file mode 100644 index 0000000..22bca28 --- /dev/null +++ b/flask/kontor/comics/api.py @@ -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/') +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/') +def publisher_detail(publisher_id): + publisher = Publisher.query.get(publisher_id) + return publisher_schema.dump(publisher) \ No newline at end of file diff --git a/flask/kontor/comics/forms.py b/flask/kontor/comics/forms.py deleted file mode 100644 index 0bae45f..0000000 --- a/flask/kontor/comics/forms.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Define form to edit publisher, artists and comics""" -from flask_wtf import FlaskForm -from wtforms import StringField, SubmitField, BooleanField, SelectField -from wtforms.validators import DataRequired -from bson import ObjectId - - -class PublisherForm(FlaskForm): - """ - Form to add and edit a Comic publisher - """ - name = StringField('Name', validators=[DataRequired()]) - submit = SubmitField('Submit') - - -class ArtistForm(FlaskForm): - """ - Form to add and edit a Comic publisher - """ - name = StringField('Name', validators=[DataRequired()]) - submit = SubmitField('Submit') - - -class ComicForm(FlaskForm): - """ - Form to add and edit Comics - """ - title = StringField('Title', validators=[DataRequired()]) - publisher = SelectField('Publisher', coerce=ObjectId) - current_order = BooleanField('Current Order') - submit = SubmitField('Submit') diff --git a/flask/kontor/comics/models.py b/flask/kontor/comics/models.py deleted file mode 100644 index e39e744..0000000 --- a/flask/kontor/comics/models.py +++ /dev/null @@ -1,51 +0,0 @@ -"""This modules declares the model for Comic related information.""" - -from flask import current_app -from pymongo.write_concern import WriteConcern -from pymodm import MongoModel, fields - - -class Publisher(MongoModel): - """Class Publisher represents a publisher of a comic.""" - name = fields.CharField() - - def __str__(self): - return "Publisher({})".format(self.name) - - @property - def comics(self): - """ - Return list of comics which has reference to this publisher - :return: - """ - comics = Comic.objects.raw({'publisher': self.pk}) - current_app.logger.debug(comics) - return comics - - class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) - - -class Artist(MongoModel): - """Class Artist represents a comic artist.""" - name = fields.CharField() - - class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) - - -class Comic(MongoModel): - """Class Comic represents a comic.""" - title = fields.CharField() - publisher = fields.ReferenceField(Publisher) - current_order = fields.BooleanField(default=False) - completed = fields.BooleanField(default=False) - - class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) diff --git a/flask/kontor/comics/routes.py b/flask/kontor/comics/routes.py new file mode 100644 index 0000000..0b0f53e --- /dev/null +++ b/flask/kontor/comics/routes.py @@ -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/') +def view(comic_id): + comic = Comic.query.get(comic_id) + return render_template('comics/view.html', comic=comic) diff --git a/flask/kontor/comics/templates/comics/list.html b/flask/kontor/comics/templates/comics/list.html new file mode 100644 index 0000000..c0f4922 --- /dev/null +++ b/flask/kontor/comics/templates/comics/list.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} Comics {% endblock %}

+
+
+

Comics Blueprint

+ {% for comic in comics %} +
+ +

+ {{ comic.title }} +

+
+

{{ comic.publisher.name }}

+
+

Created

+

{{ comic.created_date }}

+

Modified

+

{{ comic.last_modified_date }}

+
+

Version: {{ comic.version }}

+

Review: {{ comic.review }}

+
+ {% endfor %} +
+{% endblock %} diff --git a/flask/kontor/comics/templates/comics/view.html b/flask/kontor/comics/templates/comics/view.html new file mode 100644 index 0000000..b70da14 --- /dev/null +++ b/flask/kontor/comics/templates/comics/view.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} Comics {% endblock %}

+
+
+

Comics Blueprint

+
+ +

+ {{ comic.title }} +

+
+ +

{{ comic.id }}

+
+
+

Created

+

{{ comic.created_date }}

+

Modified

+

{{ comic.last_modified_date }}

+
+

Version: {{ comic.version }}

+
+
+{% endblock %} diff --git a/flask/kontor/comics/views.py b/flask/kontor/comics/views.py deleted file mode 100644 index 090b032..0000000 --- a/flask/kontor/comics/views.py +++ /dev/null @@ -1,222 +0,0 @@ -""" -Define routing rules for comics, publisher and artists -""" -from flask import Blueprint, flash, redirect, render_template, url_for -from flask_login import login_required -from bson import ObjectId -from pymongo.errors import PyMongoError -from .forms import ComicForm, PublisherForm, ArtistForm -from .models import Comic, Publisher, Artist - - -COMIC = Blueprint('comic', __name__) - - -@COMIC.route('/artists') -@login_required -def list_artists(): - """ - List all artists - :return: - """ - artists = Artist.objects.all() - return render_template('comics/artists.html', - artists=artists, title="Artists") - - -@COMIC.route('/artists/edit/', methods=['GET', 'POST']) -@login_required -def edit_artist(artist_id): - """ - Edit a comic artist - """ - artist = Artist.objects.get({'_id': ObjectId(artist_id)}) - form = ArtistForm(obj=artist) - if form.validate_on_submit(): - artist.name = form.name.data - artist.save() - flash('You have successfully edited the artist.') - return redirect(url_for('comic.list_artists')) - form.name.data = artist.name - return render_template('simpleform.html', action="Edit", - form=form, title="Edit Artist") - - -@COMIC.route('/artists/add', methods=['GET', 'POST']) -@login_required -def add_artist(): - """ - Add a artist - :return: - """ - form = ArtistForm() - if form.validate_on_submit(): - artist = Artist() - artist.name = form.name.data - try: - # add publisher to the database - artist.save() - flash('You have successfully added a new artist.') - except PyMongoError: - # in case publisher name already exists - flash('Error: artist name already exists.') - return redirect(url_for('comic.list_artists')) - return render_template('simpleform.html', action="Add", - form=form, title="Add Artist") - - -@COMIC.route('/artists/delete/', methods=['GET', 'POST']) -@login_required -def delete_artist(artist_id): - """ - Delete a comic artist - :param artist_id: - :return: - """ - artist = Artist.objects.raw({'_id': ObjectId(artist_id)}) - if artist: - artist.delete() - flash('You have successfully deleted the comic artist.') - return redirect(url_for('comic.list_artists')) - - -@COMIC.route('/publishers') -@login_required -def list_publishers(): - """ - List all publishers - :return: - """ - publishers = Publisher.objects.all() - return render_template('comics/publishers.html', - publishers=publishers, title="Publishers") - - -@COMIC.route('/publishers/add', methods=['GET', 'POST']) -@login_required -def add_publisher(): - """ - Add a publisher to the database - :return: - """ - form = PublisherForm() - if form.validate_on_submit(): - publisher = Publisher() - publisher.name = form.name.data - try: - # add publisher to the database - publisher.save() - flash('You have successfully added a new publisher.') - except PyMongoError: - # in case publisher name already exists - flash('Error: publisher name already exists.') - return redirect(url_for('comic.list_publishers')) - return render_template('simpleform.html', action="Add", - form=form, title="Add Publisher") - - -@COMIC.route('/publishers/edit/', methods=['GET', 'POST']) -@login_required -def edit_publisher(publisher_id): - """ - Edit a publisher - """ - publisher = Publisher.objects.get({'_id': ObjectId(publisher_id)}) - form = PublisherForm(obj=publisher) - if form.validate_on_submit(): - publisher.name = form.name.data - publisher.save() - flash('You have successfully edited the publisher.') - return redirect(url_for('comic.list_publishers')) - form.name.data = publisher.name - return render_template('simpleform.html', action="Edit", - form=form, title="Edit Publisher") - - -@COMIC.route('/publishers/delete/', methods=['GET', 'POST']) -@login_required -def delete_publisher(publisher_id): - """ - Delete a publisher - :param publisher_id: ObjectId of publisher - :return: - """ - publisher = Publisher.objects.raw({'_id': ObjectId(publisher_id)}) - if publisher: - publisher.delete() - flash('You have successfully deleted the publisher.') - return redirect(url_for('comic.list_publishers')) - - -@COMIC.route('/comics') -@login_required -def list_comics(): - """ - List all comics - :return: - """ - comics = Comic.objects.all() - return render_template('comics/comics.html', - comics=comics, title="Comics") - - -@COMIC.route('/comics/edit/', methods=['GET', 'POST']) -@login_required -def edit_comic(comic_id): - """ - Edit a comic - """ - comic = Comic.objects.get({'_id': ObjectId(comic_id)}) - form = ComicForm(obj=comic) - form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()] - form.publisher.default = comic.publisher.pk - form.publisher.process_data(comic.publisher.pk) - if form.validate_on_submit(): - comic.title = form.title.data - comic.current_order = form.current_order.data - comic.publisher = form.publisher.data - comic.save() - flash('You have successfully edited the comic.') - return redirect(url_for('comic.list_comics')) - form.title.data = comic.title - form.current_order.data = comic.current_order - return render_template('simpleform.html', action="Edit", - form=form, title="Edit Comic") - - -@COMIC.route('/comics/add', methods=['GET', 'POST']) -@login_required -def add_comic(): - """ - Add a comic - :return: - """ - form = ComicForm() - form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()] - if form.validate_on_submit(): - comic = Comic() - comic.title = form.title.data - comic.publisher = form.publisher.data - try: - comic.save() - flash('You have successfully added a new comic.') - except PyMongoError: - flash('Error: comic title already exists.') - return redirect(url_for('comic.list_comics')) - return render_template('simpleform.html', action="Add", - form=form, title="Add Comic") - - -@COMIC.route('/comics/delete/', methods=['GET', 'POST']) -@login_required -def delete_comic(comic_id): - """ - Delete a comic - :param comic_id: - :return: - """ - comic = Comic.objects.raw({'_id': ObjectId(comic_id)}) - if comic: - comic.delete() - flash('You have successfully deleted the comic.') - return redirect(url_for('comic.list_comics')) diff --git a/flask/kontor/config.py b/flask/kontor/config.py new file mode 100644 index 0000000..8382f66 --- /dev/null +++ b/flask/kontor/config.py @@ -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 diff --git a/flask/kontor/extensions.py b/flask/kontor/extensions.py new file mode 100644 index 0000000..461bd5d --- /dev/null +++ b/flask/kontor/extensions.py @@ -0,0 +1,5 @@ +import flask_sqlalchemy +from flask_marshmallow import Marshmallow + +db = flask_sqlalchemy.SQLAlchemy() +ma = Marshmallow() diff --git a/flask/kontor/home/__init__.py b/flask/kontor/home/__init__.py deleted file mode 100644 index d4468a9..0000000 --- a/flask/kontor/home/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Module for the Kontor homepage. -""" diff --git a/flask/kontor/home/views.py b/flask/kontor/home/views.py deleted file mode 100644 index 4e4d539..0000000 --- a/flask/kontor/home/views.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Define routing rules for homepage and dashboards -""" - -from flask import Blueprint, render_template, abort -from flask_login import login_required, current_user - -HOME = Blueprint('home', __name__) - - -@HOME.route('/') -def homepage(): - """ - Render the homepage template on the / route - """ - return render_template('home/index.html', title="Welcome") - - -@HOME.route('/dashboard') -@login_required -def dashboard(): - """ - Render the dashboard template on the /dashboard route - """ - return render_template('home/dashboard.html', title="Dashboard") - - -@HOME.route('/admin/dashboard') -@login_required -def admin_dashboard(): - """ - Render the admin_dashboard template on the /admin/dashboard route - :return: - """ - # prevent non-admins from accessing the page - if not current_user.is_admin: - abort(403) - - return render_template('home/admin_dashboard.html', title="Dashboard") diff --git a/flask/kontor/library/__init__.py b/flask/kontor/library/__init__.py deleted file mode 100644 index 31eb0bc..0000000 --- a/flask/kontor/library/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Define routing rules for library related information -""" diff --git a/flask/kontor/library/forms.py b/flask/kontor/library/forms.py deleted file mode 100644 index a080781..0000000 --- a/flask/kontor/library/forms.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Define form to edit publisher, artists and books -""" -from flask_wtf import FlaskForm -from wtforms import StringField, SubmitField, SelectField, IntegerField -from wtforms.validators import DataRequired, Optional, NumberRange -from bson import ObjectId - - -class PublisherForm(FlaskForm): - """ - Form to add and edit a Comic publisher - """ - name = StringField('Name', validators=[DataRequired()]) - submit = SubmitField('Submit') - - -class AuthorForm(FlaskForm): - """ - Form to add and edit a Comic publisher - """ - name = StringField('Name', validators=[DataRequired()]) - submit = SubmitField('Submit') - - -class BookForm(FlaskForm): - """ - Form to add and edit Comics - """ - title = StringField('Title', validators=[DataRequired()]) - author = SelectField('Author', coerce=ObjectId) - publisher = SelectField('Publisher', coerce=ObjectId) - isbn = StringField('ISBN', validators=[Optional()]) - year = IntegerField('Year', validators=[NumberRange(min=1970, max=2050)]) - submit = SubmitField('Submit') diff --git a/flask/kontor/library/models.py b/flask/kontor/library/models.py deleted file mode 100644 index 7ca5c58..0000000 --- a/flask/kontor/library/models.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -This module declares the model for the library related information. -""" -from pymongo.write_concern import WriteConcern -from pymodm import MongoModel, fields - - -class Publisher(MongoModel): - """ - Class Publisher represents a publisher of a book. - """ - name = fields.CharField() - - def __str__(self): - return "Publisher({})".format(self.name) - - class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) - - -class Author(MongoModel): - """ - Class Author represents an author of a book. - """ - name = fields.CharField() - - def __str__(self): - return "Author({})".format(self.name) - - class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) - - -class Book(MongoModel): - """ - Class book represents a book. - """ - title = fields.CharField() - author = fields.ReferenceField(Author) - publisher = fields.ReferenceField(Publisher) - isbn = fields.CharField(blank=True) - year = fields.IntegerField(blank=True) - edition = fields.CharField() - - def __str__(self): - return "Book({})".format(self.title) - - class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) diff --git a/flask/kontor/library/views.py b/flask/kontor/library/views.py deleted file mode 100644 index 1b53eee..0000000 --- a/flask/kontor/library/views.py +++ /dev/null @@ -1,228 +0,0 @@ -"""Define routing rules for book publishers""" -from flask import Blueprint, flash, redirect, render_template, url_for -from flask_login import login_required -from bson import ObjectId -from pymongo.errors import PyMongoError -from .forms import PublisherForm, AuthorForm, BookForm -from .models import Publisher, Author, Book - - -LIBRARY = Blueprint('library', __name__) - - -@LIBRARY.route('/publishers') -@login_required -def list_publishers(): - """ - List all publishers - :return: - """ - publishers = Publisher.objects.all() - return render_template('library/publishers.html', - publishers=publishers, title="Publishers") - - -@LIBRARY.route('/publishers/add', methods=['GET', 'POST']) -@login_required -def add_publisher(): - """ - Add a publisher to the database - :return: - """ - form = PublisherForm() - if form.validate_on_submit(): - publisher = Publisher() - publisher.name = form.name.data - try: - publisher.save() - flash('You have successfully added a new publisher.') - except PyMongoError: - flash('Error: publisher name already exists.') - return redirect(url_for('library.list_publishers')) - return render_template('simpleform.html', action="Add", - form=form, title="Add Publisher") - - -@LIBRARY.route('/publishers/edit/', methods=['GET', 'POST']) -@login_required -def edit_publisher(publisher_id): - """ - Edit a publisher - """ - publisher = Publisher.objects.get({'_id': ObjectId(publisher_id)}) - form = PublisherForm(obj=publisher) - if form.validate_on_submit(): - publisher.name = form.name.data - publisher.save() - flash('You have successfully edited the publisher.') - return redirect(url_for('library.list_publishers')) - form.name.data = publisher.name - return render_template('simpleform.html', action="Edit", - form=form, publisher=publisher, title="Edit Publisher") - - -@LIBRARY.route('/publishers/delete/', methods=['GET', 'POST']) -@login_required -def delete_publisher(publisher_id): - """ - Delete a publisher - :param publisher_id: ObjectId of publisher - :return: - """ - publisher = Publisher.objects.raw({'_id': ObjectId(publisher_id)}) - if publisher: - publisher.delete() - flash('You have successfully deleted the publisher.') - return redirect(url_for('library.list_publishers')) - - -@LIBRARY.route('/authors') -@login_required -def list_authors(): - """ - List all authors - :return: - """ - authors = Author.objects.all() - return render_template('library/authors.html', - authors=authors, title="Authors") - - -@LIBRARY.route('/authors/edit/', methods=['GET', 'POST']) -@login_required -def edit_author(author_id): - """ - Edit a book artist - """ - author = Author.objects.get({'_id': ObjectId(author_id)}) - form = AuthorForm(obj=author) - if form.validate_on_submit(): - author.name = form.name.data - author.save() - flash('You have successfully edited the author.') - return redirect(url_for('library.list_authors')) - form.name.data = author.name - return render_template('simpleform.html', action="Edit", - form=form, author=author, title="Edit Author") - - -@LIBRARY.route('/authors/add', methods=['GET', 'POST']) -@login_required -def add_author(): - """ - Add a author - :return: - """ - form = AuthorForm() - if form.validate_on_submit(): - author = Author() - author.name = form.name.data - try: - author.save() - flash('You have successfully added a new author.') - except PyMongoError: - flash('Error: author name already exists.') - return redirect(url_for('library.list_authors')) - return render_template('simpleform.html', action="Add", - form=form, title="Add Author") - - -@LIBRARY.route('/authors/delete/', methods=['GET', 'POST']) -@login_required -def delete_author(author_id): - """ - Delete a author - :param author_id: - :return: - """ - author = Author.objects.raw({'_id': ObjectId(author_id)}) - if author: - author.delete() - flash('You have successfully deleted the author.') - return redirect(url_for('library.list_authors')) - - -@LIBRARY.route('/books') -@login_required -def list_books(): - """ - List all comics - :return: - """ - books = Book.objects.all() - return render_template('library/books.html', - books=books, title="Books") - - -@LIBRARY.route('/books/edit/', methods=['GET', 'POST']) -@login_required -def edit_book(book_id): - """ - Edit a book - """ - book = Book.objects.get({'_id': ObjectId(book_id)}) - form = BookForm(obj=book) - form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()] - if book.publisher: - form.publisher.default = book.publisher.pk - form.publisher.process_data(book.publisher.pk) - form.author.choices = [(a.pk, a.name) for a in Author.objects.all()] - if book.author: - form.author.default = book.author.pk - form.author.process_data(book.author.pk) - if form.validate_on_submit(): - book.title = form.title.data - book.publisher = form.publisher.data - book.author = form.author.data - if form.isbn.data: - book.isbn = form.isbn.data - else: - book.isbn = None - book.year = form.year.data - book.save() - flash('You have successfully edited the book.') - return redirect(url_for('library.list_books')) - form.title.data = book.title - return render_template('simpleform.html', action="Edit", - form=form, book=book, title="Edit Book") - - -@LIBRARY.route('/books/add', methods=['GET', 'POST']) -@login_required -def add_book(): - """ - Add a book - :return: - """ - form = BookForm() - form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()] - form.author.choices = [(a.pk, a.name) for a in Author.objects.all()] - if form.validate_on_submit(): - book = Book() - book.title = form.title.data - book.publisher = form.publisher.data - book.author = form.author.data - book.year = form.year.data - try: - book.save() - flash('You have successfully added a new book.') - except PyMongoError: - flash('Error: book title already exists.') - return redirect(url_for('library.list_books')) - return render_template('simpleform.html', action="Add", - form=form, title="Add Book") - - -@LIBRARY.route('/books/delete/', methods=['GET', 'POST']) -@login_required -def delete_book(book_id): - """ - Delete a book - :param book_id: - :return: - """ - book = Book.objects.raw({'_id': ObjectId(book_id)}) - if book: - book.delete() - flash('You have successfully deleted the book.') - return redirect(url_for('library.list_books')) diff --git a/flask/kontor/main/__init__.py b/flask/kontor/main/__init__.py new file mode 100644 index 0000000..ebdaf6e --- /dev/null +++ b/flask/kontor/main/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint('main', __name__) + +from kontor.main import routes diff --git a/flask/kontor/main/routes.py b/flask/kontor/main/routes.py new file mode 100644 index 0000000..0c2e5dd --- /dev/null +++ b/flask/kontor/main/routes.py @@ -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') diff --git a/flask/kontor/media/__init__.py b/flask/kontor/media/__init__.py new file mode 100644 index 0000000..581af5f --- /dev/null +++ b/flask/kontor/media/__init__.py @@ -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 diff --git a/flask/kontor/media/api.py b/flask/kontor/media/api.py new file mode 100644 index 0000000..be9b875 --- /dev/null +++ b/flask/kontor/media/api.py @@ -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/') +def mediafile_detail(file_id): + file = MediaFile.query.get(file_id) + return mediafile_schema.dump(file) diff --git a/flask/kontor/media/models.py b/flask/kontor/media/models.py new file mode 100644 index 0000000..4e54978 --- /dev/null +++ b/flask/kontor/media/models.py @@ -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="")), + "collection": ma.URLFor("media_api.mediafile_list"), + } + ) + + +mediafile_schema = MediaFileSchema() +mediafiles_schema = MediaFileSchema(many=True) diff --git a/flask/kontor/media/routes.py b/flask/kontor/media/routes.py new file mode 100644 index 0000000..a32f5e3 --- /dev/null +++ b/flask/kontor/media/routes.py @@ -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/') +def mediafile_detail(mediafile_id): + mediafile = MediaFile.query.get(mediafile_id) + return render_template('media/mediafile_detail.html', mediafile=mediafile) diff --git a/flask/kontor/media/templates/media/mediafile_detail.html b/flask/kontor/media/templates/media/mediafile_detail.html new file mode 100644 index 0000000..e3474ae --- /dev/null +++ b/flask/kontor/media/templates/media/mediafile_detail.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} MediaFile {% endblock %}

+
+
+

MediaFile Blueprint

+
+ +

+ {{ mediafile.title }} +

+
+ +

{{ mediafile.id }}

+
+
+

Created

+

{{ mediafile.created_date }}

+

Modified

+

{{ mediafile.last_modified_date }}

+
+

Version: {{ mediafile.version }}

+

Review: {{ mediafile.is_review() }}

+

Should Download: {{mediafile.is_download() }}

+
+
+{% endblock %} diff --git a/flask/kontor/media/templates/media/mediafile_list.html b/flask/kontor/media/templates/media/mediafile_list.html new file mode 100644 index 0000000..2d7d29f --- /dev/null +++ b/flask/kontor/media/templates/media/mediafile_list.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} MediaFile {% endblock %}

+
+
+

MediaFile Blueprint

+ {% for mediafile in mediafiles %} +
+ +

+ {{ mediafile.title }} +

+
+

{{ mediafile.id }}

+
+

Created

+

{{ mediafile.created_date }}

+

Modified

+

{{ mediafile.last_modified_date }}

+
+

Version: {{ mediafile.version }}

+

Review: {{ mediafile.is_review() }}

+

Should Download: {{ mediafile.is_download() }}

+

Links: {{ mediafile._links }}

+
+ {% endfor %} +
+{% endblock %} diff --git a/flask/kontor/models.py b/flask/kontor/models.py index 7150364..eba153c 100644 --- a/flask/kontor/models.py +++ b/flask/kontor/models.py @@ -1,66 +1,49 @@ -""" -This modules declares the model for Kontor users. -""" -from pymodm import MongoModel, fields -from pymongo.write_concern import WriteConcern -from flask_login import UserMixin -from werkzeug.security import generate_password_hash, check_password_hash -from kontor import _LOGIN_MANAGER_ +from kontor.extensions import db, ma +from sqlalchemy.sql import func -class User(UserMixin, MongoModel): - """ - This class represents an user for the Kontor application. - """ - email = fields.EmailField() - username = fields.CharField(max_length=60) - first_name = fields.CharField(max_length=60) - last_name = fields.CharField(max_length=60) - password_hash = fields.CharField(max_length=128) - is_admin = fields.BooleanField(default=False) +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') - @property - def password(self): - """ - Prevent pasword from being accessed - """ - raise AttributeError('password is not a readable attribute.') - - @password.setter - def password(self, password): - """ - Set password to a hashed password - """ - self.password_hash = generate_password_hash(password) - - def verify_password(self, password): - """ - Check if hashed password matches actual password - """ - return check_password_hash(self.password_hash, password) - - def get_id(self): - """ - Get username as id - :return: - """ - return self.username - - def __repr__(self): - return ''.format(self.username) +class PublisherSchema(ma.SQLAlchemySchema): class Meta: - """Sets the connection and connections details.""" - connection_alias = 'kontor' - write_concern = WriteConcern(j=True) + model = Publisher + + comics = ma.List(ma.HyperlinkRelated("comics_api.index")) -# Set up user_loader -@_LOGIN_MANAGER_.user_loader -def load_user(user_name): - """ - Get list of users from database - :param user_name: - :return: - """ - return User.objects.get({'username': user_name}) +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) diff --git a/flask/kontor/office/__init__.py b/flask/kontor/office/__init__.py deleted file mode 100644 index d830d41..0000000 --- a/flask/kontor/office/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Define routing rules for office related information -""" diff --git a/flask/kontor/static/css/style.css b/flask/kontor/static/css/style.css deleted file mode 100644 index 35fe053..0000000 --- a/flask/kontor/static/css/style.css +++ /dev/null @@ -1,126 +0,0 @@ -/* app/static/css/style.css */ - -body, html { - width: 100%; - height: 100%; -} - -body, h1, h2, h3 { - font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight: 700; -} - -a, .navbar-default .navbar-brand, .navbar-default .navbar-nav>li>a { - color: #aec251; -} - -a:hover, .navbar-default .navbar-brand:hover, .navbar-default .navbar-nav>li>a:hover { - color: #687430; -} - -footer { - padding: 50px 0; - background-color: #f8f8f8; -} - -p.copyright { - margin: 15px 0 0; -} - -.alert-info { - width: 50%; - margin: auto; - color: #687430; - background-color: #e6ecca; - border-color: #aec251; -} - -.btn-default { - border-color: #aec251; - color: #aec251; -} - -.btn-default:hover { - background-color: #aec251; -} - -.center { - margin: auto; - width: 70%; - padding: 10px; -} - -.content-section { - padding: 50px 0; - border-top: 1px solid #e7e7e7; -} - -.footer, .push { - clear: both; - height: 4em; -} - -.intro-divider { - width: 400px; - border-top: 1px solid #f8f8f8; - border-bottom: 1px solid rgba(0,0,0,0.2); -} - -.intro-header { - padding-top: 50px; - padding-bottom: 50px; - text-align: center; - color: #f8f8f8; - background: url(../img/intro-bg.jpg) no-repeat center center; - background-size: cover; - height: 100%; -} - -.intro-message { - position: relative; - padding-top: 20%; - padding-bottom: 20%; -} - -.intro-message > h1 { - margin: 0; - text-shadow: 2px 2px 3px rgba(0,0,0,0.6); - font-size: 5em; -} - -.intro-message > h3 { - text-shadow: 2px 2px 3px rgba(0,0,0,0.6); -} - -.lead { - font-size: 18px; - font-weight: 400; -} - -.topnav { - font-size: 14px; -} - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -4em; -} - -.outer { - display: table; - position: absolute; - height: 70%; - width: 100%; -} - -.middle { - display: table-cell; - vertical-align: middle; -} - -.inner { - margin-left: auto; - margin-right: auto; -} diff --git a/flask/kontor/templates/admin/users/user.html b/flask/kontor/templates/admin/users/user.html deleted file mode 100644 index 50df2c4..0000000 --- a/flask/kontor/templates/admin/users/user.html +++ /dev/null @@ -1,21 +0,0 @@ - - -{% import "bootstrap/wtf.html" as wtf %} -{% extends "base.html" %} -{% block title %}Edit User{% endblock %} -{% block body %} -
-
-
-
-
-

Edit User

-
-
- {{ wtf.quick_form(form) }} -
-
-
-
-
-{% endblock %} diff --git a/flask/kontor/templates/admin/users/users.html b/flask/kontor/templates/admin/users/users.html deleted file mode 100644 index b23f67c..0000000 --- a/flask/kontor/templates/admin/users/users.html +++ /dev/null @@ -1,58 +0,0 @@ - - -{% import "bootstrap/utils.html" as utils %} -{% extends "base.html" %} -{% block title %}Users{% endblock %} -{% block body %} -
-
-
-
-
- {{ utils.flashed_messages() }} -
-

Users

- {% if users %} -
-
- - - - - - - - - - - {% for user in users %} - {% if user.is_admin %} - - - - - - - {% else %} - - - - - - - {% endif %} - {% endfor %} - -
Name Email Username Edit
Admin N/A N/A N/A
{{ user.first_name }} {{ user.last_name }} {{ user.email }} {{ user.username }} - - Edit - -
-
- {% endif %} -
-
-
-
- -{% endblock %} diff --git a/flask/kontor/templates/auth/login.html b/flask/kontor/templates/auth/login.html deleted file mode 100644 index 8b70e01..0000000 --- a/flask/kontor/templates/auth/login.html +++ /dev/null @@ -1,18 +0,0 @@ - - -{% import "bootstrap/utils.html" as utils %} -{% import "bootstrap/wtf.html" as wtf %} -{% extends "base.html" %} -{% block title %}Login{% endblock %} -{% block body %} -
-
- {{ utils.flashed_messages() }} -
-
-

Login to your account

-
- {{ wtf.quick_form(form) }} -
-
-{% endblock %} diff --git a/flask/kontor/templates/auth/register.html b/flask/kontor/templates/auth/register.html deleted file mode 100644 index 55d4918..0000000 --- a/flask/kontor/templates/auth/register.html +++ /dev/null @@ -1,14 +0,0 @@ - - -{% import "bootstrap/wtf.html" as wtf %} -{% extends "base.html" %} -{% block title %}Register{% endblock %} -{% block body %} -
-
-

Register for an account

-
- {{ wtf.quick_form(form) }} -
-
-{% endblock %} diff --git a/flask/kontor/templates/base.html b/flask/kontor/templates/base.html index 80bdc27..0d7af2d 100644 --- a/flask/kontor/templates/base.html +++ b/flask/kontor/templates/base.html @@ -1,107 +1,68 @@ - - {{ title }} | Kontor - - - - - + + {% block title %} {% endblock %} - FlaskApp + -