From 2bbf3bf2d37b319f06bc9e3a1c6d9097aa287bfa Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Fri, 1 Nov 2013 22:57:17 +0100 Subject: Fixes and cleanup and documentation --- app/views/categories.py | 6 ++++- app/views/consts.py | 57 +++++++++++++++++++++++++++------------------ app/views/expenses.py | 62 ++++++++++++++++++++++++++++++++++++------------- app/views/user.py | 37 ++++++++++++++++++----------- 4 files changed, 110 insertions(+), 52 deletions(-) (limited to 'app/views') diff --git a/app/views/categories.py b/app/views/categories.py index 0bb64ef..ca9b828 100644 --- a/app/views/categories.py +++ b/app/views/categories.py @@ -9,8 +9,12 @@ mod = Blueprint('categories', __name__) @mod.route('/', methods=('GET', 'POST')) @login_required @templated -def manage (): +def manage(): + """Workhorse: List and edit/create. For historic reasons, + everything is done in JavaScript. + NB: No deletion possible yet. + """ if request.method == 'GET': categories = Category.of(current_user).order_by(Category.name).all() diff --git a/app/views/consts.py b/app/views/consts.py index 97afad1..4b5ea68 100644 --- a/app/views/consts.py +++ b/app/views/consts.py @@ -7,14 +7,15 @@ from . import Blueprint, flash, db, \ from ..model import Category, ConstExpense from .. import forms as F -import datetime from sqlalchemy import sql from functools import partial -assert_authorisation = partial(assert_authorisation, ConstExpense.get) - mod = Blueprint('consts', __name__) +assert_authorisation = partial(assert_authorisation, ConstExpense.get) +# +# Form +# class ConstForm(F.Form): start = F.DateField(u'Beginn', F.req, format='%m.%Y', @@ -43,7 +44,7 @@ class ConstForm(F.Form): def __init__(self, cur=None, obj=None): obj = cur if obj is None else obj - super(F.Form, self).__init__(obj=obj) + super(ConstForm, self).__init__(obj=obj) self.category.query = Category.of(current_user).order_by(Category.name) # init prev_list @@ -57,10 +58,14 @@ class ConstForm(F.Form): self.prev.query = CE.of(current_user).filter(filter).order_by(CE.description) +# +# Views +# @mod.route('/') @login_required @templated def list (): + """List all constant expenses.""" d = today() expenses = ConstExpense.of(current_user).order_by(ConstExpense.description).all() @@ -80,18 +85,22 @@ def list (): return { 'current': current, 'old': old, 'future': future } + @mod.route('/') @login_required @assert_authorisation('id') @templated def show(id): + """Show a specific constant expense.""" return { 'exp': ConstExpense.get(id) } + @mod.route('/edit/', methods=('GET', 'POST')) @login_required @assert_authorisation('id') @templated def edit(id): + """Edit a specific constant expense. This includes deletion.""" exp = ConstExpense.get(id) form = ConstForm(exp) @@ -109,11 +118,33 @@ def edit(id): return { 'form': form } + +@mod.route('/add/', methods=('GET', 'POST')) +@login_required +@templated +def add(): + """Add a new constant expense.""" + exp = ConstExpense() + + form = ConstForm() + + if form.validate_on_submit(): + form.populate_obj(exp) + exp.user = current_user + db.session.add(exp) + db.session.commit() + flash(u"Eintrag hinzugefügt.") + return redirect('.show', id = exp.id) + + return { 'form': form } + + @mod.route('/add/from/') @login_required @assert_authorisation('other') @templated('.add') def add_from(other): + """Copy `other` and create a new expense based on it.""" exp = ConstExpense() # needed to initialize 'CE.next' other = ConstExpense.get(other) @@ -128,21 +159,3 @@ def add_from(other): if not other.next: form.prev.data = other return { 'form': form } - -@mod.route('/add/', methods=('GET', 'POST')) -@login_required -@templated -def add (): - exp = ConstExpense() - - form = ConstForm() - - if form.validate_on_submit(): - form.populate_obj(exp) - exp.user = current_user - db.session.add(exp) - db.session.commit() - flash(u"Eintrag hinzugefügt.") - return redirect('.show', id = exp.id) - - return { 'form': form } diff --git a/app/views/expenses.py b/app/views/expenses.py index 870b45f..23cd42e 100644 --- a/app/views/expenses.py +++ b/app/views/expenses.py @@ -9,12 +9,15 @@ from ..model import Category, SingleExpense, CatExpense, MonthExpense from .. import forms as F import datetime -from sqlalchemy import sql, func +from sqlalchemy import func from functools import partial assert_authorisation = partial(assert_authorisation, SingleExpense.get) mod = Blueprint('expenses', __name__) +# +# Form +# class ExpenseForm(F.Form): date = F.DateField(u'Datum', F.req, format="%d.%m.%Y", @@ -30,38 +33,52 @@ class ExpenseForm(F.Form): get_label='name') def __init__(self, obj = None): - super(F.Form, self).__init__(obj = obj) + super(ExpenseForm, self).__init__(obj = obj) self.category.query = Category.of(current_user).order_by(Category.name) +# +# Utilities +# def calc_month_exp(year, month): + """Returns the `MonthExpense` for the given month.""" ssum = func.sum(SingleExpense.expense) query = SingleExpense.of_month(current_user, month, year) result = query.group_by(SingleExpense.category_id).\ values(SingleExpense.category_id, ssum) - exps = [CatExpense(Category.query.get(c), s, query.filter(SingleExpense.category_id == c)) for c,s in result] + exps = [CatExpense(Category.get(c), s, query.filter(SingleExpense.category_id == c)) for c,s in result] return MonthExpense(current_user, datetime.date(year, month, 1), exps) -def pie_stuff(exp): + +def pie_data(exp): + """Generates the dictionary needed to show the pie diagram. + The resulting dict is category → sum of expenses. + """ expenses = {} for c in exp.catexps: - expenses[c.cat.name] = float(c.expense) + expenses[c.cat.name] = float(c.sum) for c in Category.of(current_user).order_by(Category.name).all(): yield (c.name, expenses.get(c.name, 0.0)) + def calc_month_and_pie(year, month): exp = calc_month_exp(year,month) - pie = pie_stuff(exp) + pie = pie_data(exp) return (exp, dict(pie)) + def entry_flash(msg, exp): + """When changing/adding an entry, a message is shown.""" url = url_for('.edit', id = exp.id) link = u"%s" % (url, exp.description) flash(Markup(msg % link)) +# +# Template additions +# @mod.app_template_filter() def prev_date(exp): if exp.date.month == 1: @@ -69,6 +86,7 @@ def prev_date(exp): else: return exp.date.replace(month = exp.date.month - 1) + @mod.app_template_filter() def next_date(exp): if exp.date.month == 12: @@ -76,23 +94,19 @@ def next_date(exp): else: return exp.date.replace(month = exp.date.month + 1) + @mod.app_template_test('last_date') def is_last(exp): return exp.date >= today().replace(day = 1) -@mod.route('//') -@login_required -@templated('.show') -def show_date(year, month): - c,p = calc_month_and_pie(year, month) - return { 'exps' : [c], 'pies' : [p] } - -mod.add_url_rule('/', endpoint = 'show_date_str', build_only = True) - +# +# Views +# @mod.route('/') @login_required @templated def show(): + """Show this and the last month.""" d = today() first, pfirst = calc_month_and_pie(d.year, d.month) @@ -103,11 +117,25 @@ def show(): return { 'exps' : [first, second], 'pies': [pfirst, psecond] } + +@mod.route('//') +@login_required +@templated('.show') +def show_date(year, month): + """Show the expenses of the specified month.""" + c,p = calc_month_and_pie(year, month) + return { 'exps' : [c], 'pies' : [p] } + +# shortcut to allow calling the above route, when year/month is a string +mod.add_url_rule('/', endpoint = 'show_date_str', build_only = True) + + @mod.route('/edit/', methods=('GET', 'POST')) @login_required @assert_authorisation('id') @templated def edit(id): + """Edit a single expense, given by `id`.""" exp = SingleExpense.get(id) form = ExpenseForm(exp) @@ -127,10 +155,12 @@ def edit(id): return { 'form': form } -@mod.route('/add/', methods=('GET', 'POST')) + +@mod.route('/add', methods=('GET', 'POST')) @login_required @templated def add(): + """Add a new expense.""" form = ExpenseForm() if form.validate_on_submit(): diff --git a/app/views/user.py b/app/views/user.py index 7f6f998..6102d3b 100644 --- a/app/views/user.py +++ b/app/views/user.py @@ -10,16 +10,19 @@ import flask mod = Blueprint('user', __name__) +# +# Forms +# class LoginForm(F.Form): username = F.StringField(u'Username', F.req) pwd = F.PasswordField(u'Passwort', F.req) def __init__(self, *args, **kwargs): - super(F.Form, self).__init__(*args, **kwargs) + super(LoginForm, self).__init__(*args, **kwargs) self.user = None def validate(self): - rv = super(F.Form, self).validate() + rv = super(LoginForm, self).validate() if not rv: return False @@ -30,6 +33,7 @@ class LoginForm(F.Form): self.user = user return True + class ChangePwdForm(F.Form): old = F.PasswordField(u'Passwort', F.req) new = F.PasswordField(u'Neues Passwort', F.req + [F.validators.EqualTo('confirm', u'Passwörter stimmen nicht überein')]) @@ -43,22 +47,33 @@ class ChangePwdForm(F.Form): def newpwd(self): return self.new.data +# +# Views +# @mod.route('/login', methods=('GET', 'POST')) @templated def login(): - form = LoginForm(flash=u"Login fehlgeschlagen!") + """Log the user in.""" + form = LoginForm(flash=u"Login fehlgeschlagen!") + if form.validate_on_submit(): + login_user(form.user) + # we explicitly need flask's variant as we redirect to a URI + return flask.redirect(request.args.get('next') or url_for('index')) + return { 'form': form } - if form.validate_on_submit(): - login_user(form.user) - # we explicitly need flask's variant as we redirect to a URI - return flask.redirect(request.args.get('next') or url_for('index')) - return { 'form': form } +@mod.route('/logout') +def logout(): + """Log the user out.""" + logout_user() + return redirect('.login') + @mod.route('/cpw', methods=('GET', 'POST')) @login_required @templated def cpw(): + """Change the password of the user.""" form = ChangePwdForm() if form.validate_on_submit(): @@ -69,9 +84,5 @@ def cpw(): return { 'form': form } -@mod.route('/logout') -def logout(): - logout_user() - return redirect('.login') - +# set this, so the user is redirected to the correct view, when not logged in login_manager.login_view = 'user.login' -- cgit v1.2.3-70-g09d2