summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorRené 'Necoro' Neumann <necoro@necoro.net>2013-04-11 01:14:49 +0200
committerRené 'Necoro' Neumann <necoro@necoro.net>2013-04-11 01:14:49 +0200
commit0567318344330295512176569a84afc9748d79c5 (patch)
treeb2ccd2e5ba3e254a945143060f31bdd4d42c921c /app
parent5bf8a03dae494f1625d3d5eeee5ffd6396b730fa (diff)
downloadkosten-0567318344330295512176569a84afc9748d79c5.tar.gz
kosten-0567318344330295512176569a84afc9748d79c5.tar.bz2
kosten-0567318344330295512176569a84afc9748d79c5.zip
First part of the transition to flask
Diffstat (limited to 'app')
-rw-r--r--app/__init__.py13
-rw-r--r--app/model.py167
-rw-r--r--app/views.py32
3 files changed, 212 insertions, 0 deletions
diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 0000000..6ba7025
--- /dev/null
+++ b/app/__init__.py
@@ -0,0 +1,13 @@
+from flask import Flask
+
+# create app
+app = Flask("kosten")
+
+# force autoescape in all files
+app.jinja_env.autoescape = True
+
+# load config
+app.config.from_pyfile("settings.py")
+
+from .model import db
+from . import views
diff --git a/app/model.py b/app/model.py
new file mode 100644
index 0000000..92b3710
--- /dev/null
+++ b/app/model.py
@@ -0,0 +1,167 @@
+from flask.ext.sqlalchemy import SQLAlchemy
+from sqlalchemy import sql
+from sqlalchemy.ext.declarative import declared_attr
+
+import datetime
+import decimal
+from functools import partial
+from collections import namedtuple
+
+from . import app
+
+db = SQLAlchemy(app)
+
+__all__ = ["db", "Category", "SingleExpense", "ConstExpense", "CatExpense", "MonthExpense"]
+
+ReqColumn = partial(db.Column, nullable = False)
+ExpNum = db.Numeric(scale = 2, precision = 10)
+
+def to_exp(d):
+ """Converts decimal into expense"""
+ return d.quantize(decimal.Decimal('.01'), rounding = decimal.ROUND_UP)
+
+#
+# Database Entities
+class Model (db.Model):
+ __abstract__ = True
+
+ id = db.Column(db.Integer, primary_key=True)
+
+ @declared_attr
+ def __tablename__ (cls):
+ return cls.__name__.lower()
+
+ @classmethod
+ def get_by (cls, *args, **kwargs):
+ return cls.query.filter_by(*args, **kwargs).first()
+
+ @classmethod
+ def get_by_or_404 (cls, *args, **kwargs):
+ return cls.query.filter_by(*args, **kwargs).first_or_404()
+
+ @classmethod
+ def get (cls, *args, **kwargs):
+ return cls.query.get(*args, **kwargs)
+
+ @classmethod
+ def get_or_404 (cls, *args, **kwargs):
+ return cls.query.get_or_404(*args, **kwargs)
+
+class Category (Model):
+ name = ReqColumn(db.Unicode(50), unique = True)
+ parent_id = db.Column(db.Integer, db.ForeignKey('category.id'))
+
+ children = db.relationship('Category',
+ backref=db.backref('parent', remote_side="Category.id"))
+
+ def __init__ (self, name, parent_id = None):
+ Model.__init__(self)
+ self.name = name
+ self.parent_id = parent_id
+
+ def __repr__ (self):
+ if self.parent:
+ return '<Category "%s" of "%s">' % (self.name, self.parent.name)
+ else:
+ return '<Category "%s">' % self.name
+
+class Expense (Model):
+ __abstract__ = True
+
+ description = db.Column(db.Unicode(50))
+ expense = ReqColumn(ExpNum)
+
+ @declared_attr
+ def category_id(cls):
+ return ReqColumn(db.Integer, db.ForeignKey(Category.id))
+
+ @declared_attr
+ def category(cls):
+ return db.relationship(Category, innerjoin = True)
+
+class SingleExpense (Expense):
+ year = ReqColumn(db.Integer)
+ month = ReqColumn(db.SmallInteger)
+ day = ReqColumn(db.SmallInteger)
+
+ @classmethod
+ def of_month (cls, month, year):
+ comp = sql.and_(
+ cls.month == month,
+ cls.year == year)
+
+ return cls.query.filter(comp)
+
+ @property
+ def date (self):
+ return datetime.date(self.year, self.month, self.day)
+
+ @date.setter
+ def date (self, d):
+ self.year = d.year
+ self.month = d.month
+ self.day = d.day
+
+class ConstExpense (Expense):
+ months = ReqColumn(db.SmallInteger)
+ start = ReqColumn(db.Date, index = True)
+ end = ReqColumn(db.Date, index = True)
+ prev_id = db.Column(db.Integer, db.ForeignKey('constexpense.id'))
+
+ prev = db.relationship('ConstExpense', remote_side = "ConstExpense.id", uselist = False,
+ backref=db.backref('next', uselist = False))
+
+ @property
+ def monthly(self):
+ return to_exp(self.expense / self.months)
+
+ @classmethod
+ def of_month (cls, month, year):
+ d = datetime.date(year, month, 1)
+ return cls.query.filter(sql.between(d, cls.start, cls.end))
+
+#
+# Work entities (not stored in DB)
+#
+class CatExpense (namedtuple('CatExpense', 'cat expense exps')):
+ __slots__ = ()
+
+ @property
+ def all (self):
+ return self.exps.order_by(SingleExpense.day).all()
+
+class MonthExpense (namedtuple('MonthExpense', 'date catexps')):
+
+ def __init__ (self, *args, **kwargs):
+ self._consts = None
+ super(MonthExpense, self).__init__(*args, **kwargs)
+
+ @property
+ def consts (self):
+ if self._consts is None:
+ self._consts = ConstExpense.of_month(self.date.month, self.date.year).all()
+
+ return self._consts
+
+ @property
+ def constsum (self):
+ s = sum(c.monthly for c in self.consts)
+ return s or 0
+
+ @property
+ def sum (self):
+ return self.constsum + sum(x.expense for x in self.catexps)
+
+ @property
+ def all (self):
+ return SingleExpense.of_month(self.date.month, self.date.year).order_by(SingleExpense.day).all()
+
+ def __str__ (self):
+ return '<MonthExpense of "%s": %s>' % (self.date, self.sum)
+
+#
+# Extra indizes have to be here
+#
+
+db.Index('idx_single_date', SingleExpense.year, SingleExpense.month)
+db.Index('idx_start_end', ConstExpense.start, ConstExpense.end)
diff --git a/app/views.py b/app/views.py
new file mode 100644
index 0000000..7450946
--- /dev/null
+++ b/app/views.py
@@ -0,0 +1,32 @@
+from flask import render_template, request, url_for
+import flask
+
+from . import app, db
+
+# check for mobile visitors
+mobile_checks = ["J2ME", "Opera Mini"]
+
+@app.before_request
+def handle_mobile():
+ ua = request.environ.get("HTTP_USER_AGENT", "")
+
+ flask.g.is_mobile = any((x in ua) for x in mobile_checks)
+
+
+@app.template_filter("static_url")
+def static_url(s):
+ return url_for("static", filename=s)
+
+
+#@app.errorhandler(404)
+#def page_not_found (error):
+# print request.path
+
+@app.route("/")
+@app.route("/index")
+def index():
+ return render_template("root.jinja")
+
+@app.route("/add")
+def addExp():
+ return render_template("root.jinja")