This commit is contained in:
christian.hofer 2022-06-19 07:57:36 +02:00
parent 4bd3e58859
commit 3173f1fd1c
36 changed files with 1313 additions and 0 deletions

81
src/BookKeeper.py Normal file
View 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()

View 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

View File

3
src/babel.cfg Normal file
View File

@ -0,0 +1,3 @@
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

42
src/config.cfg Normal file
View 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
View 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
View File

View 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
View 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.')])

View File

View 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)

View File

View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
from models.IntEnum import IntEnum
class Role(IntEnum):
ADMIN = 1
USER = 2

6
src/models/Status.py Normal file
View File

@ -0,0 +1,6 @@
from models.IntEnum import IntEnum
class Status(IntEnum):
ACTIVE = 1
INACTIVE = 0

60
src/models/Transaction.py Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,10 @@
h1 {
border: 2px #eee solid;
color: green;
text-align: center;
padding: 10px;
}
.table-icon{
font-size: 0.73em;
}

View 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 %}

View 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 %}

View 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
View 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>

View 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>

View 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 %}

View 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>

View 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 %}

View 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 %}

View 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 %}

View File

@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Welcome to BookKeeper {{ name }} ! {% endblock %}</h1>
{% endblock %}

View 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>