init
This commit is contained in:
parent
4bd3e58859
commit
3173f1fd1c
81
src/BookKeeper.py
Normal file
81
src/BookKeeper.py
Normal file
@ -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()
|
26
src/Controller/Controller.py
Normal file
26
src/Controller/Controller.py
Normal file
@ -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
|
||||||
|
|
||||||
|
|
0
src/Controller/__init__.py
Normal file
0
src/Controller/__init__.py
Normal file
3
src/babel.cfg
Normal file
3
src/babel.cfg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[python: **.py]
|
||||||
|
[jinja2: **/templates/**.html]
|
||||||
|
extensions=jinja2.ext.autoescape,jinja2.ext.with_
|
42
src/config.cfg
Normal file
42
src/config.cfg
Normal file
@ -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
|
55
src/files/logging.json
Normal file
55
src/files/logging.json
Normal file
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
0
src/mod_auth/__init__.py
Normal file
0
src/mod_auth/__init__.py
Normal file
72
src/mod_auth/controllers.py
Normal file
72
src/mod_auth/controllers.py
Normal file
@ -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'))
|
20
src/mod_auth/forms.py
Normal file
20
src/mod_auth/forms.py
Normal file
@ -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.')])
|
0
src/mod_intern/__init__.py
Normal file
0
src/mod_intern/__init__.py
Normal file
104
src/mod_intern/controllers.py
Normal file
104
src/mod_intern/controllers.py
Normal file
@ -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/<string:element>', 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/<string:element>/<id>', 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)
|
0
src/mod_public/__init__.py
Normal file
0
src/mod_public/__init__.py
Normal file
7
src/mod_public/controllers.py
Normal file
7
src/mod_public/controllers.py
Normal file
@ -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__)
|
||||||
|
|
68
src/models/Account.py
Normal file
68
src/models/Account.py
Normal file
@ -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 '<Account %r>' % self.iban
|
54
src/models/Bank.py
Normal file
54
src/models/Bank.py
Normal file
@ -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 '<Bank %r>' % self.name
|
47
src/models/BaseClasses.py
Normal file
47
src/models/BaseClasses.py
Normal file
@ -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__)
|
44
src/models/Consultant.py
Normal file
44
src/models/Consultant.py
Normal file
@ -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 '<Consultant %r>' % self.name
|
22
src/models/IntEnum.py
Normal file
22
src/models/IntEnum.py
Normal file
@ -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)
|
6
src/models/Role.py
Normal file
6
src/models/Role.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from models.IntEnum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class Role(IntEnum):
|
||||||
|
ADMIN = 1
|
||||||
|
USER = 2
|
6
src/models/Status.py
Normal file
6
src/models/Status.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from models.IntEnum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class Status(IntEnum):
|
||||||
|
ACTIVE = 1
|
||||||
|
INACTIVE = 0
|
60
src/models/Transaction.py
Normal file
60
src/models/Transaction.py
Normal file
@ -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 '<Transaction %r>' % self.payment_reference
|
60
src/models/User.py
Normal file
60
src/models/User.py
Normal file
@ -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 '<User %r>' % self.name
|
8
src/models/__init__.py
Normal file
8
src/models/__init__.py
Normal file
@ -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
|
10
src/static/css/style.css
Normal file
10
src/static/css/style.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
h1 {
|
||||||
|
border: 2px #eee solid;
|
||||||
|
color: green;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-icon{
|
||||||
|
font-size: 0.73em;
|
||||||
|
}
|
30
src/templates/auth/forgot.html
Normal file
30
src/templates/auth/forgot.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="column is-4 is-offset-4">
|
||||||
|
<h3 class="title">Sign Up</h3>
|
||||||
|
<div class="box">
|
||||||
|
<form method="POST" action="/signup">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="password" name="password" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
54
src/templates/auth/login.html
Normal file
54
src/templates/auth/login.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{% macro render_field(field, placeholder=None) %}
|
||||||
|
{% if field.errors %}
|
||||||
|
<div>
|
||||||
|
{% elif field.flags.error %}
|
||||||
|
<div>
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
{% endif %}
|
||||||
|
{% set css_class = 'form-control ' + kwargs.pop('class', '') %}
|
||||||
|
{{ field(class=css_class, placeholder=placeholder, **kwargs) }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<legend>Sign in</legend>
|
||||||
|
{% with errors = get_flashed_messages(category_filter=["error"]) %}
|
||||||
|
{% if errors %}
|
||||||
|
<div>
|
||||||
|
{% for error in errors %}
|
||||||
|
{{ error }}<br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<div>
|
||||||
|
{% for field, error in form.errors.items() %}
|
||||||
|
{% for e in error %}
|
||||||
|
{{ e }}<br>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<form method="POST" action="./login" accept-charset="UTF-8" role="form">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
{{ render_field(form.email, placeholder="Your Email Address",
|
||||||
|
autofocus="") }}
|
||||||
|
{{ render_field(form.password, placeholder="Password") }}
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="remember" value="1"> Remember Me
|
||||||
|
</label>
|
||||||
|
<a role="button" href="">Forgot your password?</a><span class="clearfix"></span>
|
||||||
|
</div>
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth" type="submit" name="submit">Sign in</button>
|
||||||
|
</form>
|
||||||
|
<a href="{{url_for('auth.signup') }}"><button class="button is-block is-info is-large is-fullwidth">Sign up</button></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
48
src/templates/auth/signup.html
Normal file
48
src/templates/auth/signup.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{% macro render_field(field, placeholder=None) %}
|
||||||
|
{% if field.errors %}
|
||||||
|
<div>
|
||||||
|
{% elif field.flags.error %}
|
||||||
|
<div>
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
{% endif %}
|
||||||
|
{% set css_class = 'form-control ' + kwargs.pop('class', '') %}
|
||||||
|
{{ field(class=css_class, placeholder=placeholder, **kwargs) }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<legend>Sign up</legend>
|
||||||
|
{% with errors = get_flashed_messages(category_filter=["error"]) %}
|
||||||
|
{% if errors %}
|
||||||
|
<div>
|
||||||
|
{% for error in errors %}
|
||||||
|
{{ error }}<br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<div>
|
||||||
|
{% for field, error in form.errors.items() %}
|
||||||
|
{% for e in error %}
|
||||||
|
{{ e }}<br>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<form method="POST" action="./signup" accept-charset="UTF-8" role="form">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
{{ render_field(form.email, placeholder="Your Email Address", autofocus="") }}
|
||||||
|
{{ render_field(form.username, placeholder="Username") }}
|
||||||
|
{{ render_field(form.password, placeholder="Password") }}
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth" type="submit" name="submit">Sign up</button>
|
||||||
|
</form>
|
||||||
|
<a href="./login"><button class="button is-block is-info is-large is-fullwidth">Sign in</button></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
44
src/templates/base.html
Normal file
44
src/templates/base.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||||
|
<title>{% block title %} {% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('intern.index') }}">BookKeeper</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main_nav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li class="nav-item active"> <a class="nav-link" href="{{ url_for('intern.banks')}}">Banken</a> </li>
|
||||||
|
<li class="nav-item active"> <a class="nav-link" href="{{ url_for('intern.accounts')}}">Konten</a> </li>
|
||||||
|
<li class="nav-item active"> <a class="nav-link" href="{{ url_for('intern.settings')}}">Einstellungen</a> </li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item active"> <a class="nav-link" href="#">About</a> </li>
|
||||||
|
<li class="nav-item active"> <a class="nav-link" href="{{ url_for('auth.logout') }} ">Logout</a> </li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container">
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="alert alert-danger">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
30
src/templates/intern/accountFields.html
Normal file
30
src/templates/intern/accountFields.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<div class="form-group">
|
||||||
|
<label for="IBAN">IBAN:</label>
|
||||||
|
<input type="text" name="iban" id="iban"
|
||||||
|
placeholder="Konto IBAN" class="form-control"
|
||||||
|
{% if accounts is defined %} value="{{ accounts.iban }}"{%endif%}></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="BIC">BIC:</label>
|
||||||
|
<input type="text" name="bic" id="bic"
|
||||||
|
placeholder="Konto BIC" class="form-control"
|
||||||
|
{% if accounts is defined %} value="{{ accounts.bic }}"{%endif%}></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="account_type">Konto Typ:</label>
|
||||||
|
<select class="custom-select" id="account_type" name="account_type">
|
||||||
|
<option value="Giro" {% if accounts.type=="Giro" %} selected="selected"{% endif %}>Giro</option>
|
||||||
|
<option value="Loan" {% if accounts.type=="Loan" %} selected="selected"{% endif %}>Kredit</option>
|
||||||
|
<option value="Savings" {% if accounts.type=="Savings" %} selected="selected"{% endif %}>Sparbuch</option>
|
||||||
|
<option value="Building-society-saver" {% if accounts.type=="Building-society-saver" %} selected="selected"{% endif %}>Bausparer</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="account_remark">Bemerkung:</label>
|
||||||
|
<input type="text" name="account_remark" id="account_remark"
|
||||||
|
placeholder="Bemerkung" class="form-control"
|
||||||
|
{% if accounts is defined %} value="{{ accounts.remark }}"{%endif%}></input>
|
||||||
|
</div>
|
36
src/templates/intern/accounts.html
Normal file
36
src/templates/intern/accounts.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% block title %} Your Accounts: {% endblock %}</h1>
|
||||||
|
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">IBAN</th>
|
||||||
|
<th scope="col">Bank</th>
|
||||||
|
<th scope="col">Typ</th>
|
||||||
|
<th scope="col">Bemerkung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for account in accounts %}
|
||||||
|
<tr id="{{account.iban}}">
|
||||||
|
<td scope="row">{{ account.iban }}</td>
|
||||||
|
<td>{{ account.bank }}</td>
|
||||||
|
<td>_({{ account.typ}})</td>
|
||||||
|
<td>{{ account.remark }}</td>
|
||||||
|
<td><a href="/edit/account/{{account.iban}}"><i class="fas fa-edit"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row justify-content-end">
|
||||||
|
<a href="{{ url_for('intern.create', element='account')}}">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm">Neu</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
26
src/templates/intern/bankFields.html
Normal file
26
src/templates/intern/bankFields.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<div id="new_bank">
|
||||||
|
<div class="form-group" id="bank_name_label">
|
||||||
|
<label for="bank_name">Name:</label>
|
||||||
|
<input type="text" name="bank_name" id="bank_name"
|
||||||
|
placeholder="Name der Bank" class="form-control"
|
||||||
|
{% if bank is defined %}value="{{ bank.name }}"{%endif%} ></input>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" id="bank_address_label">
|
||||||
|
<label for="bank_address">Address:</label>
|
||||||
|
<input type="text" name="bank_address" id="bank_address"
|
||||||
|
placeholder="Adresse" class="form-control"
|
||||||
|
{% if bank is defined %}value="{{ bank.address }}"{%endif%}></input>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" id="bank_phone_label">
|
||||||
|
<label for="bank_phone">Telefonnummer:</label>
|
||||||
|
<input type="number" name="bank_phone" id="bank_phone"
|
||||||
|
placeholder="Telefonnummer" class="form-control"
|
||||||
|
{% if bank is defined %}value="{{ bank.phone_number }}"{%endif%}></input>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" id="bank_mail_label">
|
||||||
|
<label for="bank_mail">E-Mail:</label>
|
||||||
|
<input type="email" name="bank_mail" id="bank_mail"
|
||||||
|
placeholder="E-Mail Addresse" class="form-control"
|
||||||
|
{% if bank is defined %}value="{{ bank.email }}"{%endif%}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
37
src/templates/intern/banks.html
Normal file
37
src/templates/intern/banks.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% block title %} Banken {% endblock %}</h1>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Bank</th>
|
||||||
|
<th scope="col">Adresse</th>
|
||||||
|
<th scope="col">Kontakt</th>
|
||||||
|
<th scope="col">Bearbeiten</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for bank in banks %}
|
||||||
|
<tr id="{{ bank['name'] }}">
|
||||||
|
<td scope="row">{{ bank.name }}</td>
|
||||||
|
<td>{{ bank.address }}</td>
|
||||||
|
<td>{% if bank.phone_number is defined %}
|
||||||
|
{{ bank.phone_number }} {% else %}
|
||||||
|
{{ bank.email}}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td><a href="/edit/bank/{{bank.id}}"><i class="fas fa-edit"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row justify-content-end">
|
||||||
|
<a href="{{ url_for('intern.create', element='bank')}}">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm">Neu</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
53
src/templates/intern/createAccount.html
Normal file
53
src/templates/intern/createAccount.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% block title %}{% if accounts is defined %}
|
||||||
|
<h1>Konto bearbeiten</h1>{% else %}
|
||||||
|
<h1>Neues Konto anlegen</h1> {%endif%}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% include "intern/accountFields.html" %}
|
||||||
|
|
||||||
|
<div class="form-group btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
|
<label for="existing_radio" class="btn btn-secondary active">
|
||||||
|
<input type="radio" id="existing_radio" name="bank_choise" value="existing" checked=true > Vorhandene Bank
|
||||||
|
</label>
|
||||||
|
<label for="new_bank_radio" class="btn btn-secondary">
|
||||||
|
<input type="radio" id="new_bank_radio" name="bank_choise" value="new_bank" > Neue Bank
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" id="existing_bank">
|
||||||
|
<label for="banks">Bank:</label>
|
||||||
|
<select name="banks" id="banks" class="form-control">
|
||||||
|
{% for bank in banks %}
|
||||||
|
<option value="{{ bank['name'] }}">{{ bank['name'] }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "intern/bankFields.html" %}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#new_bank").hide();
|
||||||
|
$("#existing_bank").show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#existing_radio').change(function() {
|
||||||
|
$("#new_bank").hide();
|
||||||
|
$("#existing_bank").show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#new_bank_radio').change(function() {
|
||||||
|
$("#existing_bank").hide();
|
||||||
|
$("#new_bank").show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
14
src/templates/intern/createBank.html
Normal file
14
src/templates/intern/createBank.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% block title %}{% if bank is defined %}
|
||||||
|
<h1>Bank bearbeiten</h1>{% else %}
|
||||||
|
<h1>Neue Bank anlegen</h1> {%endif%}
|
||||||
|
{% endblock %}
|
||||||
|
<form method="post">
|
||||||
|
{% include "intern/bankFields.html" %}
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
5
src/templates/intern/index.html
Normal file
5
src/templates/intern/index.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% block title %} Welcome to BookKeeper {{ name }} ! {% endblock %}</h1>
|
||||||
|
{% endblock %}
|
141
src/templates/public/404.html
Normal file
141
src/templates/public/404.html
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<html lang="en"><head>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" type="image/png" href="https://cpwebassets.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="CodePen">
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico">
|
||||||
|
|
||||||
|
<link rel="mask-icon" type="" href="https://cpwebassets.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111">
|
||||||
|
|
||||||
|
|
||||||
|
<title>CodePen - Pure CSS 404</title>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Merriweather|Merriweather+Sans">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html, html::after, html::before, body, body::after, body::before, head, head::after, head::before, style, style::after, style::before {
|
||||||
|
content: "";
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::after, html::before, html::after, head::after, head, style {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-color-hue: 217;
|
||||||
|
--main-color: hsla(var(--main-color-hue), 97%, 61%, 1);
|
||||||
|
--black: #222;
|
||||||
|
--red: #ED6B5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
style {
|
||||||
|
overflow: hidden;
|
||||||
|
color: transparent;
|
||||||
|
line-height: 0;
|
||||||
|
width: 5.1rem;
|
||||||
|
height: 2rem;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
style::before {
|
||||||
|
width: 0.3rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
background: var(--black);
|
||||||
|
margin-top: 1.6rem;
|
||||||
|
margin-left: 1.6rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 0 0.3rem white, 1.4rem -0.2rem 0 var(--black), 1.5rem 0 0 0.3rem white;
|
||||||
|
}
|
||||||
|
|
||||||
|
head {
|
||||||
|
width: 5.1rem;
|
||||||
|
height: 5.1rem;
|
||||||
|
background: var(--main-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: auto;
|
||||||
|
z-index: -1;
|
||||||
|
box-shadow: 0 0 0 2rem hsla(var(--main-color-hue), 97%, 61%, 0.1), 0 0 0 4rem hsla(var(--main-color-hue), 97%, 61%, 0.05), 0 0 0 6rem hsla(var(--main-color-hue), 97%, 61%, 0.025);
|
||||||
|
}
|
||||||
|
head::after {
|
||||||
|
background: #222;
|
||||||
|
border-radius: 4rem 4rem 0.5rem 0.5rem;
|
||||||
|
width: 1.1rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
background: var(--red);
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
box-shadow: 0 -0.3rem 0 0.3rem var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: #f9f9f9;
|
||||||
|
font-family: system-ui, Helvetica, Roboto, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
html::after {
|
||||||
|
line-height: 0;
|
||||||
|
height: 0;
|
||||||
|
transform: translatex(1rem);
|
||||||
|
content: "4 4";
|
||||||
|
font-size: 7rem;
|
||||||
|
color: #3C86FC;
|
||||||
|
letter-spacing: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
html::before {
|
||||||
|
content: "You seem lost.";
|
||||||
|
font-family: "Merriweather", serif;
|
||||||
|
color: var(--main-color);
|
||||||
|
font-weight: bolder;
|
||||||
|
line-height: 0;
|
||||||
|
height: 0;
|
||||||
|
transform: translatey(11rem);
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
body::after {
|
||||||
|
content: "The page you are trying to reach doesn't exist.";
|
||||||
|
font-family: "Merriweather Sans", sans-serif;
|
||||||
|
color: var(--black);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 0;
|
||||||
|
height: 0;
|
||||||
|
transform: translatey(14rem);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.console = window.console || function(t) {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if (document.location.search.match(/type=embed/gi)) {
|
||||||
|
window.parent.postMessage("resize", "*");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body translate="no">
|
||||||
|
|
||||||
|
|
||||||
|
</body></html>
|
Loading…
Reference in New Issue
Block a user