From 3173f1fd1cf49eea34892b15def794587371b2a9 Mon Sep 17 00:00:00 2001 From: "christian.hofer" Date: Sun, 19 Jun 2022 07:57:36 +0200 Subject: [PATCH] init --- src/BookKeeper.py | 81 ++++++++++++++ src/Controller/Controller.py | 26 +++++ src/Controller/__init__.py | 0 src/babel.cfg | 3 + src/config.cfg | 42 +++++++ src/files/logging.json | 55 +++++++++ src/mod_auth/__init__.py | 0 src/mod_auth/controllers.py | 72 ++++++++++++ src/mod_auth/forms.py | 20 ++++ src/mod_intern/__init__.py | 0 src/mod_intern/controllers.py | 104 +++++++++++++++++ src/mod_public/__init__.py | 0 src/mod_public/controllers.py | 7 ++ src/models/Account.py | 68 ++++++++++++ src/models/Bank.py | 54 +++++++++ src/models/BaseClasses.py | 47 ++++++++ src/models/Consultant.py | 44 ++++++++ src/models/IntEnum.py | 22 ++++ src/models/Role.py | 6 + src/models/Status.py | 6 + src/models/Transaction.py | 60 ++++++++++ src/models/User.py | 60 ++++++++++ src/models/__init__.py | 8 ++ src/static/css/style.css | 10 ++ src/templates/auth/forgot.html | 30 +++++ src/templates/auth/login.html | 54 +++++++++ src/templates/auth/signup.html | 48 ++++++++ src/templates/base.html | 44 ++++++++ src/templates/intern/accountFields.html | 30 +++++ src/templates/intern/accounts.html | 36 ++++++ src/templates/intern/bankFields.html | 26 +++++ src/templates/intern/banks.html | 37 +++++++ src/templates/intern/createAccount.html | 53 +++++++++ src/templates/intern/createBank.html | 14 +++ src/templates/intern/index.html | 5 + src/templates/public/404.html | 141 ++++++++++++++++++++++++ 36 files changed, 1313 insertions(+) create mode 100644 src/BookKeeper.py create mode 100644 src/Controller/Controller.py create mode 100644 src/Controller/__init__.py create mode 100644 src/babel.cfg create mode 100644 src/config.cfg create mode 100644 src/files/logging.json create mode 100644 src/mod_auth/__init__.py create mode 100644 src/mod_auth/controllers.py create mode 100644 src/mod_auth/forms.py create mode 100644 src/mod_intern/__init__.py create mode 100644 src/mod_intern/controllers.py create mode 100644 src/mod_public/__init__.py create mode 100644 src/mod_public/controllers.py create mode 100644 src/models/Account.py create mode 100644 src/models/Bank.py create mode 100644 src/models/BaseClasses.py create mode 100644 src/models/Consultant.py create mode 100644 src/models/IntEnum.py create mode 100644 src/models/Role.py create mode 100644 src/models/Status.py create mode 100644 src/models/Transaction.py create mode 100644 src/models/User.py create mode 100644 src/models/__init__.py create mode 100644 src/static/css/style.css create mode 100644 src/templates/auth/forgot.html create mode 100644 src/templates/auth/login.html create mode 100644 src/templates/auth/signup.html create mode 100644 src/templates/base.html create mode 100644 src/templates/intern/accountFields.html create mode 100644 src/templates/intern/accounts.html create mode 100644 src/templates/intern/bankFields.html create mode 100644 src/templates/intern/banks.html create mode 100644 src/templates/intern/createAccount.html create mode 100644 src/templates/intern/createBank.html create mode 100644 src/templates/intern/index.html create mode 100644 src/templates/public/404.html diff --git a/src/BookKeeper.py b/src/BookKeeper.py new file mode 100644 index 0000000..4eedcfa --- /dev/null +++ b/src/BookKeeper.py @@ -0,0 +1,81 @@ +# from Controller.Controller import Controller +# controller = Controller() +# controller.initLogger() +# controller.init_database() +# logger = logging.getLogger(__name__) +import logging + +from flask import Flask, render_template, request, g +from flask_babel import Babel + +from models.BaseClasses import db, login_manager, initLogger +from models import User + + +def create_app(): + # Define the WSGI application object + flask_app = Flask(__name__) + + # Configurations + flask_app.config.from_pyfile('config.cfg') + + db.init_app(flask_app) + + login_manager.login_view = 'auth.login' + login_manager.init_app(flask_app) + login_manager.refresh_view = 'auth.login' + login_manager.needs_refresh_message = (u"To protect your account, please reauthenticate to access this page.") + login_manager.needs_refresh_message_category = "info" + + # Import a module / component using its blueprint handler variable (mod_auth) + from src.mod_auth.controllers import mod_auth as auth_module + from src.mod_public.controllers import mod_public as public_module + from src.mod_intern.controllers import mod_intern as intern_module + + # Register blueprint(s) + flask_app.register_blueprint(auth_module) + flask_app.register_blueprint(intern_module) + flask_app.register_blueprint(public_module) + + logging.basicConfig() + logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) + + # Sample HTTP error handling + @flask_app.errorhandler(404) + def not_found(error): + return render_template('public/404.html'), 404 + + babel = Babel(flask_app) + + @babel.localeselector + def get_locale(): + translations = [str(translation) for translation in babel.list_translations()] + return request.accept_languages.best_match(translations) + + @babel.timezoneselector + def get_timezone(): + user = getattr(g, 'user', None) + if user is not None: + return user.timezone + + + return flask_app + + +def setup_database(flask_app): + with flask_app.app_context(): + db.create_all(app=flask_app) + user = User.query.filter_by(email="test@gmail.com").first() + if not user: + user = User("test", "test@gmail.com", "test") + db.session.add(user) + db.session.commit() + + +if __name__ == '__main__': + app = create_app() + setup_database(app) + logger = initLogger() + logging.getLogger(__name__).debug( + "translation directories: {}".format(app.config["BABEL_TRANSLATION_DIRECTORIES"])) + app.run() diff --git a/src/Controller/Controller.py b/src/Controller/Controller.py new file mode 100644 index 0000000..9f36f13 --- /dev/null +++ b/src/Controller/Controller.py @@ -0,0 +1,26 @@ +import json +import os +import logging +import logging.config +import logging.handlers + +from DatabaseHandler.SqliteDriver import SqliteDBDriver + + +class Controller: + def __init__(self): + self.dbconnection = SqliteDBDriver("bookkeeper", '', '', '', '') + + def init_database(self): + self.dbconnection.init_db() + + def get_all_accounts(self) -> list: + results = self.dbconnection.select( + 'Select a.iban, b.name as bank, a.remark from account a join bank b on a.bank = b.id;') + return results + + def get_all_banks(self) -> list: + results = self.dbconnection.select('Select * from bank;') + return results + + diff --git a/src/Controller/__init__.py b/src/Controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/babel.cfg b/src/babel.cfg new file mode 100644 index 0000000..0cc0bac --- /dev/null +++ b/src/babel.cfg @@ -0,0 +1,3 @@ +[python: **.py] +[jinja2: **/templates/**.html] +extensions=jinja2.ext.autoescape,jinja2.ext.with_ \ No newline at end of file diff --git a/src/config.cfg b/src/config.cfg new file mode 100644 index 0000000..d71bfdb --- /dev/null +++ b/src/config.cfg @@ -0,0 +1,42 @@ +# Statement for enabling the development environment +DEBUG = True + +# Define the application directory +import os + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) +DATABASE_DIR = os.path.join(BASE_DIR, '../app.db') + +# Define the database - we are working with +# SQLite for this example +SQLALCHEMY_DATABASE_URI = 'sqlite:///' + DATABASE_DIR +DATABASE_CONNECT_OPTIONS = {} + +#Babel config +BABEL_TRANSLATION_DIRECTORIES = os.path.join(BASE_DIR, "translation") +BABEL_DEFAULT_LANGUAGAE = 'en' +LANGUAGES = { + 'en': 'English', + 'de': 'Deutsch' +} + +# Application threads. A common general assumption is +# using 2 per available processor cores - to handle +# incoming requests using one and performing background +# operations using the other. +THREADS_PER_PAGE = 2 + +# Enable protection agains *Cross-site Request Forgery (CSRF) +CSRF_ENABLED = True + +# Use a secure, unique and absolutely secret key for +# signing the data. +CSRF_SESSION_KEY = "93OoDjMrQlgmF\\v0Mv^1" + +# Secret key for signing cookies +SECRET_KEY = 'O\"2v~PooLKdvT7#\VAr7' + +SQLALCHEMY_TRACK_MODIFICATIONS = False + +# The amount of time before the cookie expires, as a datetime.timedelta object or integer seconds. Default: 365 days (1 non-leap Gregorian year) +REMEMBER_COOKIE_DURATION = 86400 \ No newline at end of file diff --git a/src/files/logging.json b/src/files/logging.json new file mode 100644 index 0000000..6691ea5 --- /dev/null +++ b/src/files/logging.json @@ -0,0 +1,55 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "default": { + "format": "%(asctime)s | %(levelname)s | %(lineno)d | %(module)s | %(funcName)s | %(message)s" + }, + "console": { + "format": "%(asctime)s | %(levelname)s | %(lineno)d | %(module)s | %(funcName)s | %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "console", + "stream": "ext://sys.stdout" + }, + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "default", + "filename": "info.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + }, + "error_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "ERROR", + "formatter": "default", + "filename": "errors.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + "loggers": { + "my_module": { + "level": "ERROR", + "handlers": [ + "console" + ], + "propagate": false + } + }, + "root": { + "level": "INFO", + "handlers": [ + "console", + "info_file_handler", + "error_file_handler" + ] + } +} \ No newline at end of file diff --git a/src/mod_auth/__init__.py b/src/mod_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mod_auth/controllers.py b/src/mod_auth/controllers.py new file mode 100644 index 0000000..01a160f --- /dev/null +++ b/src/mod_auth/controllers.py @@ -0,0 +1,72 @@ +from flask import Blueprint, request, render_template, flash, redirect, url_for, session +from flask_login import login_user, logout_user +from werkzeug.security import check_password_hash, generate_password_hash + +from models.BaseClasses import login_manager, db +from .forms import LoginForm, SignUpForm +from models.User import User + +# Define the blueprint: 'auth', set its url prefix: app.url/auth +mod_auth = Blueprint('auth', __name__, url_prefix='/auth') + + +@login_manager.user_loader +def load_user(user_id): + return User.query.get(user_id) + + +@login_manager.unauthorized_handler +def unauthorized(): + return redirect(url_for("auth.login")) + + +# Set the route and accepted methods +@mod_auth.route('/login', methods=['GET', 'POST']) +def login(): + form = LoginForm(request.form) + if request.method == 'POST': + email = request.form.get('email') + password = request.form.get('password') + remember = True if request.form.get('remember') else False + user = User.query.filter_by(email=email).first() + + if not user: + flash('No user with given e-mail found!') + return redirect(url_for("auth.login")) + elif not check_password_hash(user.password, password): + flash('Wrong email or password', 'error-message') + return redirect(url_for("auth.login")) + else: + login_user(user, remember=remember) + session['user_id'] = user.id + return redirect(url_for("intern.index")) + else: + return render_template("auth/login.html", form=form) + + +@mod_auth.route('/signup', methods=['GET', 'POST']) +def signup(): + if request.method == 'POST': + email = request.form.get('email') + name = request.form.get('username') + password = request.form.get('password') + user = User.query.filter_by(email=email).first() + + if user: + return redirect(url_for('auth.login')) + + new_user = User(name, email, password) + db.session.add(new_user) + db.session.commit() + return redirect(url_for('auth.login')) + else: + form = SignUpForm(request.form) + return render_template("auth/signup.html", form=form) + + +@mod_auth.route('/logout') +def logout(): + logout_user() + if "user_id" in session: + del session["user_id"] + return redirect(url_for('auth.login')) diff --git a/src/mod_auth/forms.py b/src/mod_auth/forms.py new file mode 100644 index 0000000..267c07b --- /dev/null +++ b/src/mod_auth/forms.py @@ -0,0 +1,20 @@ +# Import Form and RecaptchaField (optional) +from flask_wtf import FlaskForm + +# Import Form elements such as TextField and BooleanField (optional) +from wtforms import TextField, PasswordField # BooleanField + +# Import Form validators +from wtforms.validators import Required, Email, EqualTo + + +# Define the login form (WTForms) +class LoginForm(FlaskForm): + email = TextField('Email Address', [Email(), Required(message='Forgot your email address?')]) + password = PasswordField('Password', [Required(message='Must provide a password. ;-)')]) + + +class SignUpForm(FlaskForm): + username = TextField('Username', [Required(message='Must provide a username.')]) + email = TextField('Email Address', [Email(), Required(message='Forgot your email address?')]) + password = PasswordField('Password', [Required(message='Must provide a password.')]) diff --git a/src/mod_intern/__init__.py b/src/mod_intern/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mod_intern/controllers.py b/src/mod_intern/controllers.py new file mode 100644 index 0000000..8c14aba --- /dev/null +++ b/src/mod_intern/controllers.py @@ -0,0 +1,104 @@ +import sqlite3 + +import flask +import logging +import sqlalchemy.exc +import werkzeug +from flask import Blueprint, request, render_template, flash, session, url_for +from flask_babel import gettext +from flask_login import login_required, current_user, fresh_login_required + +from models import Bank, Account +from models.User import User +from models.BaseClasses import db +logger = logging.getLogger(__name__) + +mod_intern = Blueprint('intern', __name__, template_folder='intern', static_folder='static') +logger = logging.getLogger(__name__) + + +@mod_intern.route('/') +@login_required +def index(): + return render_template('/intern/index.html', name=current_user.name) + + +@mod_intern.route('/accounts') +@login_required +def accounts(): + user = db.session.query(User).get(session['user_id']) + return render_template('/intern/accounts.html', accounts=user.accounts) + + +@mod_intern.route('/banks') +@login_required +def banks(): + bks = db.session.query(Bank) + return render_template('/intern/banks.html', banks=bks) + + +@mod_intern.route('/settings') +@login_required +@fresh_login_required +def settings(): + flask.abort(404) + + +@mod_intern.route('/create/', methods=('GET', 'POST')) +@login_required +@fresh_login_required +def create(element: str): + if request.method == 'POST': + try: + bank_name = request.form['bank_name'] if len(request.form['bank_name']) > 0 else request.form['banks'] + except werkzeug.exceptions.BadRequestKeyError as e: + logger.debug("locale for app: {}".format(request.accept_languages)) + logger.debug("test: {}".format(gettext("translate"))) + flash(gettext("No Bank provided or bank already exists!")) + return flask.redirect(request.url) + bank = Bank.query.filter_by(name=bank_name).first() + if not bank: + try: + bank = Bank(request.form['bank_name']) + bank.set_address(request.form['bank_address']) + bank.set_email(request.form['bank_mail']) + bank.set_phone_number(request.form['bank_phone']) + db.session.add(bank) + db.session.commit() + except (sqlite3.IntegrityError, sqlalchemy.exc.IntegrityError) as e: + pass + + if not bank: + flash("bank could not be stored") + return flask.redirect(request.url) + + if 'iban' in request.form: + ac = Account(request.form['iban'], request.form['bic'], request.form.get('account_type')) + ac.set_remark(request.form['account_remark']) + ac.set_bank(bank) + user = db.session.query(User).get(session['user_id']) + if ac not in user.get_accounts(): + user.add_account(ac) + db.session.commit() + flash("account successfully stored to account {}".format(user)) + return flask.redirect(url_for("intern.accounts")) + + if request.method == "GET" and element == "account": + user = db.session.query(User).get(session['user_id']) + banks = db.session.query(Bank) + return render_template('intern/createAccount.html', accounts=user.accounts, banks=banks) + elif request.method == "GET" and element == "bank": + return render_template('intern/createBank.html') + + +@mod_intern.route('/edit//', methods=('GET', 'POST')) +@login_required +@fresh_login_required +def edit(element: str, id): + if request.method == "GET" and element == "account": + ac = db.session.query(Account).get(id) + banks = db.session.query(Bank) + return render_template('intern/createAccount.html', accounts=ac, banks=banks) + elif request.method == "GET" and element == "bank": + bank = db.session.query(Bank).get(id) + return render_template('intern/createBank.html', bank=bank) diff --git a/src/mod_public/__init__.py b/src/mod_public/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mod_public/controllers.py b/src/mod_public/controllers.py new file mode 100644 index 0000000..cc9d1b9 --- /dev/null +++ b/src/mod_public/controllers.py @@ -0,0 +1,7 @@ +# Import flask dependencies +from flask import Blueprint, request, render_template, \ + flash, session, redirect, url_for + +# Define the blueprint: 'auth', set its url prefix: app.url/auth +mod_public = Blueprint('public', __name__) + diff --git a/src/models/Account.py b/src/models/Account.py new file mode 100644 index 0000000..ef9808d --- /dev/null +++ b/src/models/Account.py @@ -0,0 +1,68 @@ +from typing import Optional + +import logging + +from .BaseClasses import db +from .Bank import Bank +from .Transaction import Transaction + + +# Define a User model +class Account(db.Model): + __tablename__ = 'account' + + # table columns + iban = db.Column(db.String(250), primary_key=True, nullable=False, unique=True) + bic = db.Column(db.String(300)) + typ = db.Column(db.String(15), nullable=False) + remark = db.Column(db.String(2000)) + date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) + date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) + bank_id = db.Column(db.Integer, db.ForeignKey('bank.id'), nullable=False) + user_id = db.Column(db.String(20), db.ForeignKey('user.id'), nullable=False) + transactions = db.relationship(Transaction, backref="account", lazy=True) + + # New instance instantiation procedure + def __init__(self, iban, bic, typ): + if self.iban is None: + self.iban = iban + if self.bic is None: + self.bic = bic + if self.typ is None: + self.typ = typ + if self.remark is None: + self.remark = "" + + def get_iban(self) -> str: + return self.iban + + def set_iban(self, iban: str): + self.iban = iban + + def get_bic(self) -> str: + return self.bic + + def set_bic(self, bic: str): + self.bic = bic + + def get_typ(self) -> str: + return self.bic + + def set_typ(self, bic: str): + self.bic = bic + + def get_remark(self) -> Optional[str]: + if self.remark is None: + return "" + return self.remark + + def set_remark(self, remark: Optional[str]): + self.remark = remark + + def set_bank(self, bank: Bank): + logging.getLogger(__name__).debug(self.bank) + if bank.id is not None: + self.bank_id = bank.id + + def __repr__(self): + return '' % self.iban \ No newline at end of file diff --git a/src/models/Bank.py b/src/models/Bank.py new file mode 100644 index 0000000..f51e9ee --- /dev/null +++ b/src/models/Bank.py @@ -0,0 +1,54 @@ +from typing import Optional + +from .BaseClasses import db + + +# Define a User model +class Bank(db.Model): + __tablename__ = 'bank' + + # table columns + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(250), nullable=False, unique=True) + address = db.Column(db.String(300)) + phone_number = db.Column(db.String(15)) + email = db.Column(db.String(128)) + date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) + date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) + accounts = db.relationship('Account', backref='bank', lazy=True, cascade="save-update") + consultants = db.relationship('Consultant', backref='bank', lazy=True, cascade="save-update") + + # New instance instantiation procedure + def __init__(self, name): + if self.name is None: + self.name = name + + def get_name(self) -> str: + return self.name + + def set_name(self, name: str): + self.name = name + + def get_address(self) -> Optional[str]: + return self.address + + def set_address(self, address: str): + self.address = address + + def get_phone_number(self) -> Optional[str]: + return self.phone_number + + def set_phone_number(self, phone_number: str): + self.phone_number = phone_number + + def get_email(self) -> Optional[str]: + return self.email + + def set_email(self, email: str): + self.email = email + + def __str__(self): + return self.name + + def __repr__(self): + return '' % self.name diff --git a/src/models/BaseClasses.py b/src/models/BaseClasses.py new file mode 100644 index 0000000..ac20436 --- /dev/null +++ b/src/models/BaseClasses.py @@ -0,0 +1,47 @@ +import json +import logging +import logging.config +import logging.handlers +import os + +from flask_login import LoginManager +from flask_sqlalchemy import SQLAlchemy + +LOGLEVEL = {"INFO": logging.INFO, + "WARNING": logging.WARNING, + "ERROR": logging.ERROR, + "CRITICAL": logging.CRITICAL, + "DEBUG": logging.DEBUG, + "NOTSET": logging.NOTSET} + +db = SQLAlchemy() +login_manager = LoginManager() + + + +def initLogger(default_path=None, default_level=logging.INFO, env_key='LOG_CFG'): + # basic logging config + if default_path is None or default_path == "": + default_path = os.path.join("files", "logging.json") + + path = default_path + value = os.getenv(env_key, None) + if not os.path.exists("log"): + os.mkdir("log") + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + config = json.load(f) + # change handler log directory to users log directory + handlers = config["handlers"] + for handler in handlers: + if "filename" in handlers[handler]: + handlers[handler]["filename"] = os.path.join("log", handlers[handler]["filename"]) + logging.config.dictConfig(config) + else: + logging.basicConfig(level=default_level) + + # log level + logging.getLogger().setLevel(LOGLEVEL["DEBUG"]) + return logging.getLogger(__name__) diff --git a/src/models/Consultant.py b/src/models/Consultant.py new file mode 100644 index 0000000..2850a41 --- /dev/null +++ b/src/models/Consultant.py @@ -0,0 +1,44 @@ +from typing import Optional + +from .BaseClasses import db + + +# Define a User model +class Consultant(db.Model): + __tablename__ = 'consultant' + + # table columns + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(250), nullable=False) + email = db.Column(db.String(50)) + phone_number = db.Column(db.String(15)) + date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) + date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) + bank_id = db.Column(db.Integer, db.ForeignKey('bank.id'), nullable=False) + + # New instance instantiation procedure + def __init__(self, name): + self.name = name + self.email = None + self.phone_number = None + + def get_name(self) -> str: + return self.name + + def set_name(self, name: str): + self.name = name + + def get_email(self) -> Optional[str]: + return self.email + + def set_email(self, email: str): + self.email = email + + def get_phone_number(self) -> Optional[str]: + return self.phone_number + + def set_phone_number(self, phone_number:str): + self.phone_number = phone_number + + def __repr__(self): + return '' % self.name diff --git a/src/models/IntEnum.py b/src/models/IntEnum.py new file mode 100644 index 0000000..bb1c8fb --- /dev/null +++ b/src/models/IntEnum.py @@ -0,0 +1,22 @@ +from .BaseClasses import db + + +class IntEnum(db.TypeDecorator): + """ + Enables passing in a Python enum and storing the enum's *value* in the db. + The default would have stored the enum's *name* (ie the string). + """ + impl = db.Integer + + def __init__(self, enumtype, *args, **kwargs): + super(IntEnum, self).__init__(*args, **kwargs) + self._enumtype = enumtype + + def process_bind_param(self, value, dialect): + if isinstance(value, int): + return value + + return value.value + + def process_result_value(self, value, dialect): + return self._enumtype(value) diff --git a/src/models/Role.py b/src/models/Role.py new file mode 100644 index 0000000..17bdf7d --- /dev/null +++ b/src/models/Role.py @@ -0,0 +1,6 @@ +from models.IntEnum import IntEnum + + +class Role(IntEnum): + ADMIN = 1 + USER = 2 diff --git a/src/models/Status.py b/src/models/Status.py new file mode 100644 index 0000000..a7c939b --- /dev/null +++ b/src/models/Status.py @@ -0,0 +1,6 @@ +from models.IntEnum import IntEnum + + +class Status(IntEnum): + ACTIVE = 1 + INACTIVE = 0 diff --git a/src/models/Transaction.py b/src/models/Transaction.py new file mode 100644 index 0000000..eb39709 --- /dev/null +++ b/src/models/Transaction.py @@ -0,0 +1,60 @@ +import datetime +from .BaseClasses import db + + +# Define a User model +class Transaction(db.Model): + __tablename__ = 'transaction' + + # table columns + id = db.Column(db.String(250), primary_key=True, nullable=False, unique=True) + date = db.Column(db.Date, nullable=False) + payment_reference = db.Column(db.String(2000), nullable=False) + value = db.Column(db.Float, nullable=False) + currency = db.Column(db.String(5), nullable=False) + ledger = db.Column(db.Integer) + date_executed = db.Column(db.DateTime, nullable=False) + date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) + date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) + account_iban = db.Column(db.String(250), db.ForeignKey('account.iban'), nullable=False) + + # New instance instantiation procedure + def __init__(self, date: datetime.date, value: float, currency: str, payment_reference: str, date_executed: datetime.datetime): + self.date = date + self.payment_reference = payment_reference + self.value = value + self.currency = currency + self.date_executed = date_executed + + def get_date(self) -> datetime.date: + return self.date + + def set_date(self, date: datetime.date): + self.date = date + + def get_payment_reference(self) -> str: + return self.payment_reference + + def set_payment_reference(self, payment_reference: str): + self.payment_reference = payment_reference + + def get_value(self) -> float: + return self.value + + def set_value(self, value: float): + self.value = value + + def get_currency(self) -> str: + return self.currency + + def set_currency(self, currency: str): + self.currency = currency + + def get_date_executed(self) -> datetime.datetime: + return self.date_executed + + def set_date_executed(self, date_executed: datetime.datetime): + self.date_executed = date_executed + + def __repr__(self): + return '' % self.payment_reference diff --git a/src/models/User.py b/src/models/User.py new file mode 100644 index 0000000..0572503 --- /dev/null +++ b/src/models/User.py @@ -0,0 +1,60 @@ +import secrets + +import logging +from flask_login import UserMixin +from werkzeug.security import generate_password_hash + +from .BaseClasses import db +from .IntEnum import IntEnum +from .Role import Role +from .Status import Status +from .Bank import Bank +from .Account import Account + + +class User(UserMixin, db.Model): + __tablename__ = 'user' + + # table columns + id = db.Column(db.String(20), primary_key=True) + email = db.Column(db.String(128), nullable=False, unique=True) + name = db.Column(db.String(128), nullable=False) + password = db.Column(db.String(192), nullable=False) + role = db.Column(IntEnum(Role), default=Role.USER) + status = db.Column(IntEnum(Status), default=Status.ACTIVE) + session_id = db.Column(db.String(20)) + accounts = db.relationship('Account', backref='user', lazy=False, primaryjoin=id==Account.user_id) + date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) + date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) + + # New instance instantiation procedure + def __init__(self, name, email, password): + if self.name is None: + self.name = name + if self.email is None: + self.email = email + if self.password is None: + self.password = generate_password_hash(password, method='sha256') + if self.role is None: + self.role = Role.USER + if self.status is None: + self.status = Status.ACTIVE + if self.id is None: + self.id = secrets.token_urlsafe(16) + + def get_accounts(self): + return self.accounts + + def get_banks(self): + banks = list() + for ac in self.get_accounts(): + bank = db.session.query(Bank).get(ac.bank_id) + banks.append(bank) + return banks + + def add_account(self, account: Account): + logging.getLogger(__name__).debug(self.accounts) + self.accounts.append(account) + + def __repr__(self): + return '' % self.name diff --git a/src/models/__init__.py b/src/models/__init__.py new file mode 100644 index 0000000..4591b24 --- /dev/null +++ b/src/models/__init__.py @@ -0,0 +1,8 @@ +from .IntEnum import IntEnum +from .Role import Role +from .Status import Status +from .User import User +from .Bank import Bank +from .Consultant import Consultant +from .Account import Account +from .Transaction import Transaction diff --git a/src/static/css/style.css b/src/static/css/style.css new file mode 100644 index 0000000..e8bc8e2 --- /dev/null +++ b/src/static/css/style.css @@ -0,0 +1,10 @@ +h1 { + border: 2px #eee solid; + color: green; + text-align: center; + padding: 10px; +} + +.table-icon{ + font-size: 0.73em; +} \ No newline at end of file diff --git a/src/templates/auth/forgot.html b/src/templates/auth/forgot.html new file mode 100644 index 0000000..dd3d5fb --- /dev/null +++ b/src/templates/auth/forgot.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block content %} +
+

Sign Up

+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/src/templates/auth/login.html b/src/templates/auth/login.html new file mode 100644 index 0000000..d728c7b --- /dev/null +++ b/src/templates/auth/login.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} +{% block content %} + {% macro render_field(field, placeholder=None) %} + {% if field.errors %} +
+ {% elif field.flags.error %} +
+ {% else %} +
+ {% endif %} + {% set css_class = 'form-control ' + kwargs.pop('class', '') %} + {{ field(class=css_class, placeholder=placeholder, **kwargs) }} +
+ {% endmacro %} + +
+
+ Sign in + {% with errors = get_flashed_messages(category_filter=["error"]) %} + {% if errors %} +
+ {% for error in errors %} + {{ error }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + {% if form.errors %} +
+ {% for field, error in form.errors.items() %} + {% for e in error %} + {{ e }}
+ {% endfor %} + {% endfor %} +
+ {% endif %} +
+ {{ form.csrf_token }} + {{ render_field(form.email, placeholder="Your Email Address", + autofocus="") }} + {{ render_field(form.password, placeholder="Password") }} +
+ + Forgot your password? +
+ +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/src/templates/auth/signup.html b/src/templates/auth/signup.html new file mode 100644 index 0000000..99a2316 --- /dev/null +++ b/src/templates/auth/signup.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} +{% block content %} + {% macro render_field(field, placeholder=None) %} + {% if field.errors %} +
+ {% elif field.flags.error %} +
+ {% else %} +
+ {% endif %} + {% set css_class = 'form-control ' + kwargs.pop('class', '') %} + {{ field(class=css_class, placeholder=placeholder, **kwargs) }} +
+ {% endmacro %} + +
+
+ Sign up + {% with errors = get_flashed_messages(category_filter=["error"]) %} + {% if errors %} +
+ {% for error in errors %} + {{ error }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + {% if form.errors %} +
+ {% for field, error in form.errors.items() %} + {% for e in error %} + {{ e }}
+ {% endfor %} + {% endfor %} +
+ {% endif %} +
+ {{ form.csrf_token }} + {{ render_field(form.email, placeholder="Your Email Address", autofocus="") }} + {{ render_field(form.username, placeholder="Username") }} + {{ render_field(form.password, placeholder="Password") }} + +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/src/templates/base.html b/src/templates/base.html new file mode 100644 index 0000000..feb44f1 --- /dev/null +++ b/src/templates/base.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + {% block title %} {% endblock %} + + + +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %} {% endblock %} +
+ + + \ No newline at end of file diff --git a/src/templates/intern/accountFields.html b/src/templates/intern/accountFields.html new file mode 100644 index 0000000..035fe6b --- /dev/null +++ b/src/templates/intern/accountFields.html @@ -0,0 +1,30 @@ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
\ No newline at end of file diff --git a/src/templates/intern/accounts.html b/src/templates/intern/accounts.html new file mode 100644 index 0000000..1357892 --- /dev/null +++ b/src/templates/intern/accounts.html @@ -0,0 +1,36 @@ +{% extends 'base.html' %} + +{% block content %} +

{% block title %} Your Accounts: {% endblock %}

+ + + + + + + + + + + + {% for account in accounts %} + + + + + + + + {% endfor %} + +
IBANBankTypBemerkung
{{ account.iban }}{{ account.bank }}_({{ account.typ}}){{ account.remark }}
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/src/templates/intern/bankFields.html b/src/templates/intern/bankFields.html new file mode 100644 index 0000000..f3a26de --- /dev/null +++ b/src/templates/intern/bankFields.html @@ -0,0 +1,26 @@ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/src/templates/intern/banks.html b/src/templates/intern/banks.html new file mode 100644 index 0000000..e8c3f5d --- /dev/null +++ b/src/templates/intern/banks.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} + +{% block content %} +

{% block title %} Banken {% endblock %}

+ + + + + + + + + + + + {% for bank in banks %} + + + + + + + {% endfor %} + +
BankAdresseKontaktBearbeiten
{{ bank.name }}{{ bank.address }}{% if bank.phone_number is defined %} + {{ bank.phone_number }} {% else %} + {{ bank.email}} + {% endif %} +
+ + + +{% endblock %} \ No newline at end of file diff --git a/src/templates/intern/createAccount.html b/src/templates/intern/createAccount.html new file mode 100644 index 0000000..cc59495 --- /dev/null +++ b/src/templates/intern/createAccount.html @@ -0,0 +1,53 @@ +{% extends 'base.html' %} + +{% block content %} +{% block title %}{% if accounts is defined %} +

Konto bearbeiten

{% else %} +

Neues Konto anlegen

{%endif%} +{% endblock %} + +
+ {% include "intern/accountFields.html" %} + +
+ + +
+ +
+ + +
+ + {% include "intern/bankFields.html" %} + +
+ +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/src/templates/intern/createBank.html b/src/templates/intern/createBank.html new file mode 100644 index 0000000..24ed1d0 --- /dev/null +++ b/src/templates/intern/createBank.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} +{% block title %}{% if bank is defined %} +

Bank bearbeiten

{% else %} +

Neue Bank anlegen

{%endif%} +{% endblock %} +
+ {% include "intern/bankFields.html" %} +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/src/templates/intern/index.html b/src/templates/intern/index.html new file mode 100644 index 0000000..72935d5 --- /dev/null +++ b/src/templates/intern/index.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} + +{% block content %} +

{% block title %} Welcome to BookKeeper {{ name }} ! {% endblock %}

+{% endblock %} \ No newline at end of file diff --git a/src/templates/public/404.html b/src/templates/public/404.html new file mode 100644 index 0000000..08258ac --- /dev/null +++ b/src/templates/public/404.html @@ -0,0 +1,141 @@ + + + + + + + + + + + + + CodePen - Pure CSS 404 + + + + + + + + + + + + + + + + + + + \ No newline at end of file