From dbb134751c3a87cf203cd243b1952b146b8914c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Mon, 14 Oct 2013 23:50:27 +0200 Subject: Finish login stuff --- app/__init__.py | 1 + app/forms.py | 23 ++++++++++++++++++- app/login.py | 14 ++++++++++++ app/model.py | 55 ++++++++++++++++++++++++++++++++------------- app/views/__init__.py | 3 ++- app/views/categories.py | 6 +++-- app/views/consts.py | 13 ++++++++--- app/views/expenses.py | 15 ++++++++----- app/views/login.py | 26 +++++++++++++++++++++ templates/login/login.jinja | 13 +++++++++++ templates/menu.jinja | 3 ++- 11 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 app/login.py create mode 100644 app/views/login.py create mode 100644 templates/login/login.jinja diff --git a/app/__init__.py b/app/__init__.py index 0f84d3f..ca45a1d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,4 +10,5 @@ app.jinja_env.autoescape = True app.config.from_pyfile("settings.py") from .model import db +from .login import login_manager from . import views diff --git a/app/forms.py b/app/forms.py index 8fbd4fb..182f6de 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- from flask.ext.wtf import Form -from wtforms.fields import DateField, IntegerField, StringField, HiddenField +from wtforms.fields import DateField, IntegerField, StringField, HiddenField, PasswordField from wtforms import validators as v from wtforms import fields @@ -10,6 +10,7 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectField import datetime from . import app +from .model import User today = datetime.date.today @@ -64,3 +65,23 @@ class ConstForm(Form): prev = QuerySelectField(u"Vorgänger", get_label="description", allow_blank=True) + +class LoginForm(Form): + username = StringField(u"Username", req) + pwd = PasswordField(u"Passwort", req) + + def __init__(self, *args, **kwargs): + Form.__init__(self,*args, **kwargs) + self.user = None + + def validate(self): + rv = Form.validate(self) + if not rv: + return False + + user = User.get_by(name = self.username.data) + if user is None or not user.check_password(self.pwd.data): + return False + + self.user = user + return True diff --git a/app/login.py b/app/login.py new file mode 100644 index 0000000..6d3fdfa --- /dev/null +++ b/app/login.py @@ -0,0 +1,14 @@ +from flask.ext.login import LoginManager + +# just for exporting +from flask.ext.login import login_user, logout_user, login_required, current_user + +from passlib.apps import custom_app_context as pwd_context + +from . import app +from .model import User + +login_manager = LoginManager() +login_manager.init_app(app) + +login_manager.user_loader(User.get) diff --git a/app/model.py b/app/model.py index 414cfd0..ebf3d5b 100644 --- a/app/model.py +++ b/app/model.py @@ -58,17 +58,45 @@ class User (Model): def check_password(self, pwd): return pwd_context.verify(pwd, self.pwd) -class Category (Model): + # Stuff needed for LoginManager + def is_authenticated(self): + return True + + def is_active(self): + return True + + def is_anonymous(self): + return False + + def get_id(self): + return unicode(self.id) + +class UserModel (Model): + __abstract__ = True + + @declared_attr + def user_id(cls): + return ReqColumn(db.Integer, db.ForeignKey(User.id)) + + @declared_attr + def user(cls): + return db.relationship('User') + + @classmethod + def of (cls, user): + return cls.query.filter_by(user = user) + +class Category (UserModel): name = ReqColumn(db.Unicode(50), unique = True) - user_id = ReqColumn(db.Integer, db.ForeignKey(User.id)) parent_id = 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): + def __init__ (self, name, user, parent_id = None): Model.__init__(self) self.name = name + self.user = user self.parent_id = parent_id def __repr__ (self): @@ -77,7 +105,7 @@ class Category (Model): else: return '' % self.name -class Expense (Model): +class Expense (UserModel): __abstract__ = True description = Column(db.Unicode(50)) @@ -102,12 +130,7 @@ class SingleExpense (Expense): @classmethod def of_month (cls, user, month, year): - comp = sql.and_(cls.user == user, - sql.and_( - cls.month == month, - cls.year == year)) - - return cls.query.filter(comp) + return cls.of(user).filter_by(month = month, year = year) @property def date (self): @@ -133,9 +156,9 @@ class ConstExpense (Expense): return to_exp(self.expense / self.months) @classmethod - def of_month (cls, month, year): + def of_month (cls, user, month, year): d = datetime.date(year, month, 1) - return cls.query.filter(sql.between(d, cls.start, cls.end)) + return cls.of(user).filter(sql.between(d, cls.start, cls.end)) # # Work entities (not stored in DB) @@ -147,7 +170,7 @@ class CatExpense (namedtuple('CatExpense', 'cat expense exps')): def all (self): return self.exps.order_by(SingleExpense.day).all() -class MonthExpense (namedtuple('MonthExpense', 'date catexps')): +class MonthExpense (namedtuple('MonthExpense', 'user date catexps')): def __init__ (self, *args, **kwargs): self._consts = None @@ -156,7 +179,7 @@ class MonthExpense (namedtuple('MonthExpense', 'date catexps')): @property def consts (self): if self._consts is None: - self._consts = ConstExpense.of_month(self.date.month, self.date.year).all() + self._consts = ConstExpense.of_month(self.user, self.date.month, self.date.year).all() return self._consts @@ -171,10 +194,10 @@ class MonthExpense (namedtuple('MonthExpense', 'date catexps')): @property def all (self): - return SingleExpense.of_month(self.date.month, self.date.year).order_by(SingleExpense.day).all() + return SingleExpense.of_month(self.user, self.date.month, self.date.year).order_by(SingleExpense.day).all() def __str__ (self): - return '' % (self.date, self.sum) + return '' % (self.user.name, self.date, self.sum) # # Extra indizes have to be here diff --git a/app/views/__init__.py b/app/views/__init__.py index 659a568..ef7c033 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -33,8 +33,9 @@ def format_date(s, format="%Y/%m"): def page_not_found (error): return render_template("404.jinja", page = request.path), 404 -from . import categories, consts, expenses, api +from . import categories, consts, expenses, login, api +app.register_blueprint(login.mod) app.register_blueprint(expenses.mod) app.register_blueprint(consts.mod, url_prefix="/const") app.register_blueprint(categories.mod, url_prefix="/cat") diff --git a/app/views/categories.py b/app/views/categories.py index 3491998..1ec7b1d 100644 --- a/app/views/categories.py +++ b/app/views/categories.py @@ -1,23 +1,25 @@ from ..flask_extend import Blueprint from flask import request +from ..login import current_user, login_required from ..utils import templated, redirect from ..model import db, Category mod = Blueprint('categories', __name__) @mod.route("/", methods=("GET", "POST")) +@login_required @templated() def manage (): if request.method == "GET": - categories = Category.query.order_by(Category.name).all() + categories = Category.of(current_user).order_by(Category.name).all() return { 'cats' : categories } else: for id, name in request.form.iteritems(): if id.startswith("n-"): - db.session.add(Category(name = name)) + db.session.add(Category(name = name, user = current_user)) else: Category.get(id).name = name diff --git a/app/views/consts.py b/app/views/consts.py index dab02e0..8eef73b 100644 --- a/app/views/consts.py +++ b/app/views/consts.py @@ -5,6 +5,7 @@ import datetime from sqlalchemy import sql from ..model import db, Category, ConstExpense +from ..login import current_user, login_required from ..forms import ConstForm, today from ..utils import templated, redirect @@ -13,7 +14,7 @@ mod = Blueprint('consts', __name__) def const_form(cur=None, obj=None): obj = cur if obj is None else obj form = ConstForm(obj=obj) - form.category.query = Category.query.order_by(Category.name) + form.category.query = Category.of(current_user).order_by(Category.name) # init prev_list CE = ConstExpense @@ -24,16 +25,17 @@ def const_form(cur=None, obj=None): filter = sql.or_(CE.next == cur, filter) filter = sql.and_(filter, CE.id != cur.id) - form.prev.query = CE.query.filter(filter).order_by(CE.description) + form.prev.query = CE.of(current_user).filter(filter).order_by(CE.description) return form @mod.route("/") +@login_required @templated() def list (): d = today() - expenses = ConstExpense.query.order_by(ConstExpense.description).all() + expenses = ConstExpense.of(current_user).order_by(ConstExpense.description).all() current = [] old = [] @@ -51,11 +53,13 @@ def list (): return { 'current': current, 'old': old, 'future': future } @mod.route("/") +@login_required @templated() def show(id): return { 'exp': ConstExpense.get(id) } @mod.route("/edit/", methods=("GET", "POST")) +@login_required @templated() def edit(id): exp = ConstExpense.get(id) @@ -75,6 +79,7 @@ def edit(id): return { 'form': form } @mod.route("/add/from/") +@login_required @templated(".add") def add_from(other): exp = ConstExpense() # needed to initialize 'CE.next' @@ -93,6 +98,7 @@ def add_from(other): return { 'form': form } @mod.route("/add/", methods=("GET", "POST")) +@login_required @templated() def add (): exp = ConstExpense() @@ -101,6 +107,7 @@ def add (): if form.validate_on_submit(): form.populate_obj(exp) + exp.user = current_user db.session.add(exp) db.session.commit() return redirect(".show", id = exp.id) diff --git a/app/views/expenses.py b/app/views/expenses.py index efa6717..aa1fd51 100644 --- a/app/views/expenses.py +++ b/app/views/expenses.py @@ -5,7 +5,7 @@ import datetime, decimal from sqlalchemy import sql, func from ..model import db, Category, SingleExpense, CatExpense, MonthExpense - +from ..login import login_required, current_user from ..forms import ExpenseForm from ..utils import templated, redirect @@ -13,26 +13,26 @@ mod = Blueprint('expenses', __name__) def expense_form(obj=None): form = ExpenseForm(obj=obj) - form.category.query = Category.query.order_by(Category.name) + form.category.query = Category.of(current_user).order_by(Category.name) return form def calc_month_exp(year, month): ssum = func.sum(SingleExpense.expense) - query = SingleExpense.of_month(month, year) + 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] - return MonthExpense(datetime.date(year, month, 1), exps) + return MonthExpense(current_user, datetime.date(year, month, 1), exps) def pie_stuff(exp): expenses = {} for c in exp.catexps: expenses[c.cat.name] = float(c.expense) - for c in Category.query.order_by(Category.name).all(): + 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): @@ -59,6 +59,7 @@ def is_last(exp): return exp.date >= datetime.date.today().replace(day = 1) @mod.route("//") +@login_required @templated(".show") def show_date(year, month): c,p = calc_month_and_pie(year, month) @@ -67,6 +68,7 @@ def show_date(year, month): mod.add_url_rule("/", endpoint = "show_date_str", build_only = True) @mod.route("/") +@login_required @templated() def show(): d = datetime.date.today() @@ -80,6 +82,7 @@ def show(): return { 'exps' : [first, second], 'pies': [pfirst, psecond] } @mod.route("/edit/", methods=("GET", "POST")) +@login_required @templated() def edit(id): exp = SingleExpense.get(id) @@ -101,6 +104,7 @@ def edit(id): return { 'form': form } @mod.route("/add/", methods=("GET", "POST")) +@login_required @templated() def add(): form = expense_form() @@ -109,6 +113,7 @@ def add(): exp = SingleExpense() form.populate_obj(exp) + exp.user = current_user db.session.add(exp) db.session.commit() diff --git a/app/views/login.py b/app/views/login.py new file mode 100644 index 0000000..8693c30 --- /dev/null +++ b/app/views/login.py @@ -0,0 +1,26 @@ +from ..flask_extend import Blueprint +from flask import request, url_for, redirect + +from ..forms import LoginForm +from ..login import login_user, logout_user, login_manager +from ..utils import templated + +mod = Blueprint('login', __name__) + +@mod.route("/login", methods=("GET", "POST")) +@templated() +def login(): + form = LoginForm() + + if form.validate_on_submit(): + login_user(form.user) + return redirect(request.args.get("next") or url_for("index")) + + return { 'form': form } + +@mod.route("/logout") +def logout(): + logout_user() + return redirect(url_for(".login")) + +login_manager.login_view = 'login.login' diff --git a/templates/login/login.jinja b/templates/login/login.jinja new file mode 100644 index 0000000..3254fd6 --- /dev/null +++ b/templates/login/login.jinja @@ -0,0 +1,13 @@ +{% extends "layout.jinja" %} +{% from "macros.jinja" import render_form %} + +{% block heading %} + Login +{% endblock %} + +{% block content %} +
+ {{ render_form(form) }} + +
+{% endblock %} diff --git a/templates/menu.jinja b/templates/menu.jinja index 9950c96..79b20af 100644 --- a/templates/menu.jinja +++ b/templates/menu.jinja @@ -2,6 +2,7 @@ ("index", "Kosten"), ("expenses.add", "Neu"), ("consts.list", "Konstante Kosten"), - ("categories.manage", "Kategorien") + ("categories.manage", "Kategorien"), + ("login.logout", "Logout") ] %} -- cgit v1.2.3