# -*- encoding: utf-8 -*- from __future__ import with_statement import web from web import form from web.form import Validator, Form from model import * from helper import appdir from renderer import render import datetime, decimal import itertools as iter import operator as op from sqlalchemy import sql, func class Show: def GET(self, year = '', month = ''): if year: c = self.calc(year, month) return self.render([c], self.is_last(c)) else: d = datetime.date.today() first = self.calc(d.year, d.month) if d.month == 1: second = self.calc(d.year - 1, 12) else: second = self.calc(d.year, d.month - 1) return self.render([first, second], self.is_last(first)) def calc(self, year, month): year = int(year) month = int(month) ssum = func.sum(SingleExpense.expense) query = SingleExpense.of_month(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] return MonthExpense(datetime.date(int(year), int(month), 1), exps) def is_last(self, exp): return exp.date >= datetime.date.today().replace(day = 1) def render(self, exps, is_last): return render("show", exps = exps, is_last = is_last) class Add: dformat = "%d.%m.%Y" template = "add" def GET(self): return self.render(self.form()) def POST(self): f = self.form() if f.validates(): category = Category.get_by(name = f.category.value) e = self.get_expense() e.category = category e.date = datetime.datetime.strptime(f.date.value, self.dformat) e.expense = decimal.Decimal(f.expense.value) e.description = f.description.value self.return_() else: return self.render(f) def get_expense(self): se = SingleExpense() web.ctx.orm.add(se) return se def render(self, f): return render(self.template, form = f) def return_(self): raise web.seeother("/add") def form(self): return Form( form.Textbox( "date", form.notnull, Validator("Datumsformat stimmt nicht", lambda v: datetime.datetime.strptime(v, self.dformat)), value = datetime.date.today().strftime(self.dformat), description = "Datum" ), form.Textbox( "expense", form.notnull, Validator("Keine Dezimalzahl", decimal.Decimal), description = "Betrag", post = " EUR" ), form.Textbox( "description", form.notnull, description = "Beschreibung" ), form.Dropdown( "category", map(op.itemgetter(0), sorted(Category.query.values(Category.name))), description = "Kategorie" ) ) class Edit (Add): template = "edit" def GET(self, id): exp = SingleExpense.get(id) self.get_expense = lambda *x: exp fvalues = { "date" : exp.date.strftime(self.dformat), "expense" : str(exp.expense), "description" : exp.description, "category" : exp.category.name } f = self.form() f.fill(fvalues) return self.render(f) def POST(self, id): exp = SingleExpense.get(id) self.get_expense = lambda *x: exp if "changeB" in web.input(): return Add.POST(self) elif "deleteB" in web.input(): web.ctx.orm.delete(exp) self.return_() else: return self.GET(id) def return_(self): raise web.seeother("/") class Const: dformat = "%m.%Y" def GET(self, id=None): if id is None: d = datetime.date.today() expenses = ConstExpense.query.order_by(ConstExpense.start).all() current = [] old = [] future = [] for e in expenses: if e.start <= d: if e.end >= d: current.append(e) else: old.append(e) else: future.append(e) return render("constlist", current = current, old = old, future = future) else: exp = ConstExpense.get(id) return render("const", exp = exp, dformat = lambda x: x.strftime(self.dformat)) class ConstAdd: template = "constadd" dformat = "%m.%Y" def GET(self, fromId = None): f = self.form() if fromId is not None: fromC = ConstExpense.get(fromId) fill = self.form_fill(fromC) fill["prev"] = fromC.id f.fill(fill) return render(self.template, form = f) def POST(self, fromId = None): f = self.form() if f.validates(): category = Category.get_by(name = f.category.value) e = self.get_expense() e.category = category e.start = datetime.datetime.strptime(f.start.value, self.dformat) e.end = datetime.datetime.strptime(f.end.value, self.dformat) e.months = f.months.value e.expense = decimal.Decimal(f.expense.value) e.description = f.description.value if int(f.prev.value) != -1: e.prev = ConstExpense.get(f.prev.value) else: e.prev = None web.ctx.orm.commit() raise web.seeother("/const/%s" % e.id) else: return render(self.template, form = f) def get_expense(self): ce = ConstExpense() web.ctx.orm.add(ce) return ce def form_fill(self, exp): return { "start" : exp.start.strftime(self.dformat), "end" : exp.end.strftime(self.dformat), "months" : str(exp.months), "expense" : str(exp.expense), "description" : exp.description, "category" : exp.category.name, "prev" : exp.prev.id if exp.prev else str(-1) } def form(self): CE = ConstExpense # get the list of 'previous' expenses cur = self.get_expense() # need to be before the next stmt, else 'next' might be undefined filter = (CE.next == None) if cur.id is None: # empty web.ctx.orm.expunge(cur) else: filter = sql.or_(CE.next == cur, filter) filter = sql.and_(filter, CE.id != cur.id) prev_list = CE.query.filter(filter).order_by(CE.description).values(CE.id, CE.description) return Form( form.Textbox( "start", form.notnull, Validator("Datumsformat stimmt nicht", lambda v: datetime.datetime.strptime(v, self.dformat)), value = datetime.date.today().strftime(self.dformat), description = "Start" ), form.Textbox( "end", form.notnull, Validator("Datumsformat stimmt nicht", lambda v: datetime.datetime.strptime(v, self.dformat)), value = datetime.date.today().strftime(self.dformat), description = "Ende", post = " (einschließlich)" ), form.Textbox( "months", form.notnull, Validator("Zahl muss größer als 0 sein", lambda v: int(v) > 0), value = "12", description = "Zahlungsrhythmus", post = " Monate" ), form.Textbox( "expense", form.notnull, Validator("Not a valid decimal", decimal.Decimal), description = "Betrag", post = " EUR" ), form.Textbox( "description", form.notnull, description = "Beschreibung" ), form.Dropdown( "category", map(op.itemgetter(0), Category.query.order_by(Category.name).values(Category.name)), description = "Kategorie" ), form.Dropdown( "prev", iter.chain([(-1, '')], prev_list), description = "Vorgänger" ) ) class ConstEdit (ConstAdd): template = "constedit" def GET(self, id): exp = ConstExpense.get(id) self.get_expense = lambda *x: exp f = self.form() f.fill(self.form_fill(exp)) return render(self.template, form = f) def POST(self, id): exp = ConstExpense.get(id) self.get_expense = lambda *x: exp if "changeB" in web.input(): return ConstAdd.POST(self) elif "deleteB" in web.input(): web.ctx.orm.delete(exp) raise web.seeother("/const") else: return self.GET(id) class Cat: def GET(self): categories = Category.query.order_by(Category.name).all() return render("cats", cats = categories) def POST(self): for id, name in web.input().iteritems(): if id.startswith("n-"): web.ctx.orm.add(Category(name = name)) else: Category.get(id).name = name raise web.seeother("/") class FourOhFour: """ 404 error page. """ def GET (self, p): raise self.catch(p) @staticmethod def catch (page = "?"): return web.notfound(render("404", level = "", page = page))