diff options
Diffstat (limited to 'kosten/app/utils.py')
-rw-r--r-- | kosten/app/utils.py | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/kosten/app/utils.py b/kosten/app/utils.py new file mode 100644 index 0000000..73f2b51 --- /dev/null +++ b/kosten/app/utils.py @@ -0,0 +1,117 @@ +from functools import wraps +from flask import flash, request, render_template, url_for +from flask import redirect as _redirect + +from .login import current_user + +import datetime +today = datetime.date.today + +def _gen_tpl(endpoint): + return endpoint.replace('.', '/') + '.jinja' + +def templated(template=None): + """Marks a view as being rendered by a template. The view then shall + return a dictionary holding the parameters for the template. Ig this + is not the case, the response is returned unchanged. This is needed + to support `redirect` and similar. + + The correct template is deducted as: + - when passed nothing: the name of the view + - when passed a string '.bla', the endpoint 'bla' in the current + blueprint + - when passed any other string: this string (VERBATIM!) + + Except for the last case, the hierarchy of blueprint and view is taken + as directories in the template directory. And '.jinja' is appended. + + If the first argument is a function, this is taken as 'None' to allow: + >>> @templated + ... def foo(): + ... ... + + (else it would have to be ``@templated()``). + """ + + fun = None + if template is not None and callable(template): + # a function was passed in + fun = template + template = None + + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if template is None: + template_name = _gen_tpl(request.endpoint) + elif template[0] == '.' and request.blueprint is not None: + template_name = _gen_tpl(request.blueprint + template) + else: + template_name = template + + ctx = f(*args, **kwargs) + if ctx is None: + ctx = {} + elif not isinstance(ctx, dict): + return ctx + return render_template(template_name, **ctx) + return decorated_function + + if fun is None: + return decorator + else: + return decorator(fun) + +def redirect (target, **kwargs): + """Convenience wrapper for `flask.redirect`. It applies `url_for` + on the target, which also gets passed all arguments. + + Special argument '_code' to set the HTTP-Code. + """ + code = kwargs.pop('_code', None) + url = url_for(target, **kwargs) + + if code is None: + return _redirect(url) + else: + return _redirect(url, code) + +def assert_authorisation(constructor, param): + """Asserts that the current user has the right to load some specific data. + + This is done by using the argument with keyword `param` and pass it + to `constructor`. If the resulting object has an attribute `user_id`, + this is checked to be equal to `current_user.id`. + + Usage example:: + + @route('/job/<int:id>') + @assert_authorisation(Job, 'id') + def show_job(id): + # this is only executed if Job(id).user_id == current_user.id + + """ + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + p = kwargs.get(param, None) + + if p is None: + raise TypeError("Keyword %s expected but not received." % param) + + obj = constructor(p) + if obj is None: + flash("Eintrag existiert nicht!", 'error') + return redirect('index') + + if not hasattr(obj, 'user_id'): + return f(*args, **kwargs) + + # explicitly use user_id to avoid having to load the user object + if obj.user_id != current_user.id: + flash("Nicht erlaubte Operation!", 'error') + return redirect('index') + else: + return f(*args, **kwargs) + return decorated_function + return decorator |