summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorAlexander Sulfrian <alex@spline.inf.fu-berlin.de>2016-01-10 05:08:36 +0100
committerAlexander Sulfrian <alex@spline.inf.fu-berlin.de>2016-01-10 05:08:36 +0100
commit1ec270de4390f215f874e8fad23736ce978c1bbd (patch)
treef56ebd30ec7648f785b558e499148d424bc55147 /utils
parent915c05c05a5b510d53042944582dc62c7d3f28d1 (diff)
downloadpadlite-teams-1ec270de4390f215f874e8fad23736ce978c1bbd.tar.gz
padlite-teams-1ec270de4390f215f874e8fad23736ce978c1bbd.tar.bz2
padlite-teams-1ec270de4390f215f874e8fad23736ce978c1bbd.zip
Use sqlalchemy, flask-migrate, flask-login and flask-script
No peewee anymore. All dependencies are available as debian packages now.
Diffstat (limited to 'utils')
-rw-r--r--utils/__init__.py0
-rw-r--r--utils/apimixin.py85
-rw-r--r--utils/filters.py39
-rw-r--r--utils/forms.py55
-rw-r--r--utils/login.py40
-rw-r--r--utils/pagination.py18
-rw-r--r--utils/request.py25
-rw-r--r--utils/viewdecorators.py21
-rw-r--r--utils/widgets.py21
9 files changed, 304 insertions, 0 deletions
diff --git a/utils/__init__.py b/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/utils/__init__.py
diff --git a/utils/apimixin.py b/utils/apimixin.py
new file mode 100644
index 0000000..b48fd60
--- /dev/null
+++ b/utils/apimixin.py
@@ -0,0 +1,85 @@
+from sqlalchemy import event, Column, String
+from sqlalchemy.orm.session import Session
+
+
+class APIMixin(object):
+ api_id = Column(String(255), nullable=False)
+
+ def get_api_id(self):
+ if self.api_id is None:
+ self.create_api_object()
+
+ return self.api_id
+
+ def before_created(self):
+ if self.api_id is None:
+ self.create_api_object()
+
+ def after_deleted(self):
+ if self.api_id is not None:
+ self.remove_api_object()
+ self.api_id = None
+
+ def create_api_object(self):
+ raise NotImplementedError
+
+ def remove_api_object(self):
+ raise NotImplementedError
+
+ def after_commit(self):
+ pass
+
+
+class SessionHelper(object):
+
+ def __init__(self):
+ self.new = set()
+ self.dirty = set()
+ self.deleted = set()
+
+ event.listen(Session, 'before_flush', self.before_flush)
+ event.listen(Session, 'after_flush', self.after_flush)
+ event.listen(Session, 'after_commit', self.after_commit)
+ event.listen(Session, 'after_rollback', self.after_rollback)
+
+ def before_flush(self, session, flush_context, instances):
+ for obj in session.new:
+ if isinstance(obj, APIMixin):
+ obj.before_created()
+
+ def after_flush(self, session, flush_context):
+ self.new.update(
+ obj for obj in session.new
+ if isinstance(obj, APIMixin))
+
+ self.dirty.update(
+ obj for obj in session.dirty
+ if isinstance(obj, APIMixin))
+
+ self.deleted.update(
+ obj for obj in session.deleted
+ if isinstance(obj, APIMixin))
+
+ def after_commit(self, session):
+ for obj in self.new:
+ obj.after_commit()
+ self.new.clear()
+
+ for obj in self.dirty:
+ obj.after_commit()
+ self.dirty.clear()
+
+ for obj in self.deleted:
+ obj.after_deleted()
+ self.deleted.clear()
+
+ def after_rollback(self, session):
+ self.dirty.clear()
+ self.deleted.clear()
+
+ for obj in self.new:
+ obj.after_deleted()
+ self.new.clear()
+
+
+helper = SessionHelper()
diff --git a/utils/filters.py b/utils/filters.py
new file mode 100644
index 0000000..eb0e1c8
--- /dev/null
+++ b/utils/filters.py
@@ -0,0 +1,39 @@
+from app import app
+from jinja2 import contextfilter
+from jinja2.filters import make_attrgetter
+
+
+@app.template_filter('selectattr')
+@contextfilter
+def do_selectattr(*args, **kwargs):
+ return _select_or_reject(args, kwargs, lambda x: x)
+
+
+@app.template_filter('rejectattr')
+@contextfilter
+def do_rejectattr(*args, **kwargs):
+ return _select_or_reject(args, kwargs, lambda x: not x)
+
+
+def _select_or_reject(args, kwargs, modfunc):
+ context = args[0]
+ seq = args[1]
+
+ try:
+ attr = args[2]
+ except LookupError:
+ raise FilterArgumentError('Missing parameter for attribute name')
+ transfunc = make_attrgetter(context.environment, attr)
+
+ try:
+ name = args[3]
+ args = args[4:]
+ func = lambda item: context.environment.call_test(
+ name, item, args, kwargs)
+ except LookupError:
+ func = bool
+
+ if seq:
+ for item in seq:
+ if modfunc(func(transfunc(item))):
+ yield item
diff --git a/utils/forms.py b/utils/forms.py
new file mode 100644
index 0000000..a6ff4de
--- /dev/null
+++ b/utils/forms.py
@@ -0,0 +1,55 @@
+from flask import request, url_for, redirect
+from urlparse import urlparse, urljoin
+from wtforms import Field, HiddenField, ValidationError
+
+from widgets import Static
+
+
+class Unique(object):
+ """ validator that checks field uniqueness """
+ def __init__(self, model, field, message=None):
+ self.model = model
+ self.field = field
+ if not message:
+ message = u'This element already exists.'
+ self.message = message
+
+ def __call__(self, form, field):
+ if self.model.query.filter(self.field == field.data).count() > 0:
+ raise ValidationError(self.message)
+
+
+class ReadonlyField(Field):
+ widget = Static()
+
+ def process_formdata(self, _):
+ pass
+
+
+class RedirectMixin(object):
+ next = HiddenField()
+
+ def __init__(self, *args, **kwargs):
+ super(RedirectMixin, self).__init__(*args, **kwargs)
+ if not self.next.data:
+ self.next.data = self._get_redirect_target() or ''
+
+ def _get_redirect_target(self):
+ for target in request.args.get('next'), request.referrer:
+ if not target:
+ continue
+ if self._is_safe_url(target):
+ return target
+
+ def _is_safe_url(self, target):
+ ref_url = urlparse(request.host_url)
+ test_url = urlparse(urljoin(request.host_url, target))
+ return test_url.scheme in ('http', 'https') and \
+ ref_url.netloc == test_url.netloc
+
+ def redirect(self, endpoint='index', **values):
+ if self._is_safe_url(self.next.data):
+ return redirect(self.next.data)
+
+ target = self._get_redirect_target()
+ return redirect(target or url_for(endpoint, **values))
diff --git a/utils/login.py b/utils/login.py
new file mode 100644
index 0000000..e6c8f21
--- /dev/null
+++ b/utils/login.py
@@ -0,0 +1,40 @@
+import ldap
+from functools import reduce
+
+
+def user_cls(login):
+ def decorator(cls):
+ login.user_loader(lambda uid: cls.query.get(uid))
+ return cls
+ return decorator
+
+
+def _format_dn(attr, base_dn=None):
+ attr = [attr]
+ if base_dn is not None:
+ attr.extend(base_dn)
+
+ return ','.join(['%s=%s' % (key, ldap.dn.escape_dn_chars(value))
+ for (key, value) in attr])
+
+
+def auth(config, model, username, password):
+ ldap.protocol_version = 3
+ l = ldap.initialize(config['host'])
+ l.set_option(ldap.OPT_X_TLS_DEMAND, True)
+ try:
+ user_dn = _format_dn(('uid', username), config['base_dn'])
+ l.simple_bind_s(user_dn, password)
+ except ldap.INVALID_CREDENTIALS:
+ return None
+
+ user = model.query.filter_by(name=username).first()
+ if user is None:
+ user_data = l.search_s(user_dn, ldap.SCOPE_BASE)
+ if len(user_data) != 1:
+ return None
+
+ (dn, user_data) = user_data[0]
+ user = model.create(name=username, email=user_data['mail'][0])
+
+ return user
diff --git a/utils/pagination.py b/utils/pagination.py
new file mode 100644
index 0000000..8d2cb60
--- /dev/null
+++ b/utils/pagination.py
@@ -0,0 +1,18 @@
+from flask import url_for, request
+from app import app
+
+
+def url_for_other_page(page):
+ args = request.view_args.copy()
+ args['page'] = page
+ return url_for(request.endpoint, **args)
+app.jinja_env.globals['url_for_other_page'] = url_for_other_page
+
+
+# @app.context_processor
+# def register_method():
+# def url_for_other_page(page):
+# args = request.view_args.copy()
+# args['page'] = page
+# return url_for(request.endpoint, **args)
+# return dict(url_for_other_page=url_for_other_page)
diff --git a/utils/request.py b/utils/request.py
new file mode 100644
index 0000000..8d36ed6
--- /dev/null
+++ b/utils/request.py
@@ -0,0 +1,25 @@
+from flask import g, request
+
+from app import app
+
+
+def after_this_request(f):
+ """
+ Decorator to execute methods after the request is handled, to
+ modify the response before sending back to the client. This could
+ be used to set cookies.
+ """
+
+ if not hasattr(g, 'after_request_callbacks'):
+ g.after_request_callbacks = []
+
+ g.after_request_callbacks.append(f)
+ return f
+
+
+@app.after_request
+def call_after_request_callbacks(response):
+ for callback in getattr(g, 'after_request_callbacks', ()):
+ callback(response)
+
+ return response
diff --git a/utils/viewdecorators.py b/utils/viewdecorators.py
new file mode 100644
index 0000000..8f96c07
--- /dev/null
+++ b/utils/viewdecorators.py
@@ -0,0 +1,21 @@
+from functools import wraps
+from flask import render_template, request
+
+
+# using http://flask.pocoo.org/docs/patterns/viewdecorators/
+def templated(template=None):
+ def decorator(f):
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ template_name = template
+ if template_name is None:
+ template_name = request.endpoint \
+ .replace('.', '/') + '.html'
+ 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
+ return decorator
diff --git a/utils/widgets.py b/utils/widgets.py
new file mode 100644
index 0000000..3e5d2b8
--- /dev/null
+++ b/utils/widgets.py
@@ -0,0 +1,21 @@
+import wtforms.widgets.core
+
+
+class Static(object):
+ def __call__(self, field, **kwargs):
+ kwargs.setdefault('id', field.id)
+ if kwargs['class_'] == 'form-control':
+ kwargs['class_'] = 'form-control-static'
+ html = ['<p %s>' % wtforms.widgets.core.html_params(**kwargs), field.data,'</p>']
+ return wtforms.widgets.core.HTMLString(''.join(html))
+
+
+class TextArea(wtforms.widgets.core.TextArea):
+ def __init__(self, **kwargs):
+ self.kwargs = kwargs
+
+ def __call__(self, field, **kwargs):
+ for arg in self.kwargs:
+ if arg not in kwargs:
+ kwargs[arg] = self.kwargs[arg]
+ return super(TextArea, self).__call__(field, **kwargs)