# -*- coding: utf-8 -*- from . import Blueprint, flash, db, \ current_user, login_required, \ assert_authorisation, templated, redirect, request, url_for, today from flask import Markup from ..model import Category, SingleExpense, CatExpense, MonthExpense from .. import forms as F import datetime 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('Datum', F.req, format="%d.%m.%Y", default=lambda: today()) expense = F.DecimalField('Betrag', F.req, description='EUR', places=2) description = F.StringField('Beschreibung') category = F.QuerySelectField('Kategorie', get_label='name') def __init__(self, obj = None, description_req = True): super().__init__(obj = obj) self.category.query = Category.of(current_user).order_by(Category.name) if description_req: self.description.validators.extend(F.req) # # 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.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_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.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_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 = "%s" % (url, exp.description) flash(Markup(msg % link)) # # Template additions # @mod.app_template_filter() def prev_date(exp): if exp.date.month == 1: return exp.date.replace(year = exp.date.year - 1, month = 12) else: return exp.date.replace(month = exp.date.month - 1) @mod.app_template_filter() def next_date(exp): if exp.date.month == 12: return exp.date.replace(year = exp.date.year + 1, month = 1) 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) # # 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) if d.month == 1: second, psecond = calc_month_and_pie(d.year - 1, 12) else: second, psecond = calc_month_and_pie(d.year, d.month - 1) 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) if form.is_submitted(): if 'deleteB' in request.form: db.session.delete(exp) elif form.flash_validate(): # change form.populate_obj(exp) else: return { 'form': form } db.session.commit() entry_flash("Eintrag %s geändert.", exp) return redirect('index') return { 'form': form } @mod.route('/add', methods=('GET', 'POST')) @login_required @templated def add(): """Add a new expense.""" form = ExpenseForm(description_req=False) if form.validate_on_submit(): if not form.description.data.strip(): form.description.data = form.category.data.name exp = SingleExpense() form.populate_obj(exp) exp.user = current_user db.session.add(exp) db.session.commit() entry_flash("Neuer Eintrag %s hinzugefügt.", exp) return redirect('.add') return { 'form': form } @mod.route('/search', methods=('POST', 'GET')) @login_required @templated def search(): try: query = request.form['search'].strip() except KeyError: flash("Ungültige Suchanfrage") return redirect('index') if not query: flash("Leere Suche") return redirect('index') exps = SingleExpense.of(current_user).filter(SingleExpense.description.ilike(query))\ .order_by(SingleExpense.year.desc(), SingleExpense.month, SingleExpense.day, SingleExpense.description)\ .all() if not exps: flash("Keine Ergebnisse") return redirect('index') return { 'exps': exps }