diff --git a/auth.py b/auth.py
index 0323a2fd3d1253fa22e80baeae21ff634cac0eb0..3241a712952fce4fd8bdc24dfa4a5e5d49d13740 100644
--- a/auth.py
+++ b/auth.py
@@ -1,12 +1,12 @@
-import hmac, hashlib
+import hmac
+import hashlib
 import ssl
-import ldap3
-from ldap3.utils.dn import parse_dn
 from datetime import datetime
-import grp, pwd, pam
+
 
 class User:
-    def __init__(self, username, groups, timestamp=None, obsolete=False, permanent=False):
+    def __init__(self, username, groups, timestamp=None, obsolete=False,
+                 permanent=False):
         self.username = username
         self.groups = groups
         if timestamp is not None:
@@ -17,7 +17,10 @@ class User:
         self.permanent = permanent
 
     def summarize(self):
-        return "{}:{}:{}:{}:{}".format(self.username, ",".join(self.groups), str(self.timestamp.timestamp()), self.obsolete, self.permanent)
+        return ":".join((
+            self.username, ",".join(self.groups),
+            str(self.timestamp.timestamp()), str(self.obsolete),
+            str(self.permanent)))
 
     @staticmethod
     def from_summary(summary):
@@ -36,6 +39,7 @@ class User:
         summary, hash = secure_string.split("=", 1)
         return User.from_summary(summary)
 
+
 class UserManager:
     def __init__(self, backends):
         self.backends = backends
@@ -44,7 +48,9 @@ class UserManager:
         for backend in self.backends:
             if backend.authenticate(username, password):
                 groups = sorted(list(set(backend.groups(username, password))))
-                return User(username, groups, obsolete=backend.obsolete, permanent=permanent)
+                return User(
+                    username, groups, obsolete=backend.obsolete,
+                    permanent=permanent)
         return None
 
     def all_groups(self):
@@ -52,89 +58,33 @@ class UserManager:
             yield from backend.all_groups()
 
 
-class LdapManager:
-    def __init__(self, host, user_dn, group_dn, port=636, use_ssl=True, obsolete=False):
-        self.server = ldap3.Server(host, port=port, use_ssl=use_ssl)
-        self.user_dn = user_dn
-        self.group_dn = group_dn
-        self.obsolete = obsolete
-
-    def authenticate(self, username, password):
-        try:
-            connection = ldap3.Connection(self.server, self.user_dn.format(username), password)
-            return connection.bind()
-        except ldap3.core.exceptions.LDAPSocketOpenError:
-            return False
-
-    def groups(self, username, password=None):
-        connection = ldap3.Connection(self.server)
-        obj_def = ldap3.ObjectDef("posixgroup", connection)
-        group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
-        username = username.lower()
-        for group in group_reader.search():
-            members = group.memberUid.value
-            if members is not None and username in members:
-                yield group.cn.value
+class SecurityManager:
+    def __init__(self, key, max_duration=300):
+        self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512)
+        self.max_duration = max_duration
 
-    def all_groups(self):
-        connection = ldap3.Connection(self.server)
-        obj_def = ldap3.ObjectDef("posixgroup", connection)
-        group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
-        for group in group_reader.search():
-            yield group.cn.value
-
-
-class ADManager:
-    def __init__(self, host, domain, user_dn, group_dn,
-        port=636, use_ssl=True, ca_cert=None, obsolete=False):
-        tls_config = ldap3.Tls(validate=ssl.CERT_REQUIRED)
-        if ca_cert is not None:
-            tls_config = ldap3.Tls(validate=ssl.CERT_REQUIRED,
-                ca_certs_file=ca_cert)
-        self.server = ldap3.Server(host, port=port, use_ssl=use_ssl,
-            tls=tls_config)
-        self.domain = domain
-        self.user_dn = user_dn
-        self.group_dn = group_dn
-        self.obsolete = obsolete
+    def hash_user(self, user):
+        maccer = self.maccer.copy()
+        summary = user.summarize()
+        maccer.update(summary.encode("utf-8"))
+        return "{}={}".format(summary, maccer.hexdigest())
 
-    def prepare_connection(self, username=None, password=None):
-        if username is not None and password is not None:
-            ad_user = "{}\\{}".format(self.domain, username)
-            return ldap3.Connection(self.server, ad_user, password)
-        return ldap3.Connection(self.server)
-        
-    def authenticate(self, username, password):
-        try:
-            return self.prepare_connection(username, password).bind()
-        except ldap3.core.exceptions.LDAPSocketOpenError:
+    def check_user(self, string):
+        parts = string.split("=", 1)
+        if len(parts) != 2:
+            # wrong format, expecting summary:hash
             return False
-
-    def groups(self, username, password):
-        connection = self.prepare_connection(username, password)
-        connection.bind()
-        obj_def = ldap3.ObjectDef("user", connection)
-        name_filter = "cn:={}".format(username)
-        user_reader = ldap3.Reader(connection, obj_def, self.user_dn, name_filter)
-        group_def = ldap3.ObjectDef("group", connection)
-        def _yield_recursive_groups(group_dn):
-            group_reader = ldap3.Reader(connection, group_def, group_dn, None)
-            for entry in group_reader.search():
-                yield entry.name.value
-                for child in entry.memberOf:
-                    yield from _yield_recursive_groups(child)
-        for result in user_reader.search():
-            for group_dn in result.memberOf:
-                yield from _yield_recursive_groups(group_dn)
-
-
-    def all_groups(self):
-        connection = self.prepare_connection()
-        connection.bind()
-        obj_def = ldap3.ObjectDef("group", connection)
-        group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
-        for result in reader.search():
-            yield result.name.value
+        summary, hash = map(lambda s: s.encode("utf-8"), parts)
+        maccer = self.maccer.copy()
+        maccer.update(summary)
+        user = User.from_hashstring(string)
+        if user is None:
+            return False
+        session_duration = datetime.now() - user.timestamp
+        macs_equal = hmac.compare_digest(
+            maccer.hexdigest().encode("utf-8"), hash)
+        time_short = int(session_duration.total_seconds()) < self.max_duration
+        return macs_equal and (time_short or user.permanent)
 
 
 class StaticUserManager:
@@ -151,59 +101,132 @@ class StaticUserManager:
 
     def authenticate(self, username, password):
         return (username in self.passwords
-            and self.passwords[username] == password)
+                and self.passwords[username] == password)
 
     def groups(self, username, password=None):
         if username in self.group_map:
             yield from self.group_map[username]
 
     def all_groups(self):
-        yield from list(set(group for group in groups.values()))
-
-
-class PAMManager:
-    def __init__(self, obsolete=False):
-        self.pam = pam.pam()
-        self.obsolete = obsolete
-
-    def authenticate(self, username, password):
-        return self.pam.authenticate(username, password)
+        yield from list(set(group for group in self.group_map.values()))
+
+
+try:
+    import ldap3
+
+    class LdapManager:
+        def __init__(self, host, user_dn, group_dn, port=636, use_ssl=True,
+                     obsolete=False):
+            self.server = ldap3.Server(host, port=port, use_ssl=use_ssl)
+            self.user_dn = user_dn
+            self.group_dn = group_dn
+            self.obsolete = obsolete
+
+        def authenticate(self, username, password):
+            try:
+                connection = ldap3.Connection(
+                    self.server, self.user_dn.format(username), password)
+                return connection.bind()
+            except ldap3.core.exceptions.LDAPSocketOpenError:
+                return False
+
+        def groups(self, username, password=None):
+            connection = ldap3.Connection(self.server)
+            obj_def = ldap3.ObjectDef("posixgroup", connection)
+            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
+            username = username.lower()
+            for group in group_reader.search():
+                members = group.memberUid.value
+                if members is not None and username in members:
+                    yield group.cn.value
+
+        def all_groups(self):
+            connection = ldap3.Connection(self.server)
+            obj_def = ldap3.ObjectDef("posixgroup", connection)
+            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
+            for group in group_reader.search():
+                yield group.cn.value
 
-    def groups(self, username, password=None):
-        print(username)
-        yield grp.getgrgid(pwd.getpwnam(username).pw_gid).gr_name
-        for group in grp.getgrall():
-            if username in group.gr_mem:
+    class ADManager:
+        def __init__(self, host, domain, user_dn, group_dn,
+                     port=636, use_ssl=True, ca_cert=None, obsolete=False):
+            tls_config = ldap3.Tls(validate=ssl.CERT_REQUIRED)
+            if ca_cert is not None:
+                tls_config = ldap3.Tls(
+                    validate=ssl.CERT_REQUIRED, ca_certs_file=ca_cert)
+            self.server = ldap3.Server(
+                host, port=port, use_ssl=use_ssl, tls=tls_config)
+            self.domain = domain
+            self.user_dn = user_dn
+            self.group_dn = group_dn
+            self.obsolete = obsolete
+
+        def prepare_connection(self, username=None, password=None):
+            if username is not None and password is not None:
+                ad_user = "{}\\{}".format(self.domain, username)
+                return ldap3.Connection(self.server, ad_user, password)
+            return ldap3.Connection(self.server)
+
+        def authenticate(self, username, password):
+            try:
+                return self.prepare_connection(username, password).bind()
+            except ldap3.core.exceptions.LDAPSocketOpenError:
+                return False
+
+        def groups(self, username, password):
+            connection = self.prepare_connection(username, password)
+            connection.bind()
+            obj_def = ldap3.ObjectDef("user", connection)
+            name_filter = "cn:={}".format(username)
+            user_reader = ldap3.Reader(
+                connection, obj_def, self.user_dn, name_filter)
+            group_def = ldap3.ObjectDef("group", connection)
+
+            def _yield_recursive_groups(group_dn):
+                group_reader = ldap3.Reader(
+                    connection, group_def, group_dn, None)
+                for entry in group_reader.search():
+                    yield entry.name.value
+                    for child in entry.memberOf:
+                        yield from _yield_recursive_groups(child)
+            for result in user_reader.search():
+                for group_dn in result.memberOf:
+                    yield from _yield_recursive_groups(group_dn)
+
+        def all_groups(self):
+            connection = self.prepare_connection()
+            connection.bind()
+            obj_def = ldap3.ObjectDef("group", connection)
+            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
+            for result in group_reader.search():
+                yield result.name.value
+
+except ModuleNotFoundError:
+    pass
+
+
+try:
+    import grp
+    import pwd
+    import pam
+
+    class PAMManager:
+        def __init__(self, obsolete=False):
+            self.pam = pam.pam()
+            self.obsolete = obsolete
+
+        def authenticate(self, username, password):
+            return self.pam.authenticate(username, password)
+
+        def groups(self, username, password=None):
+            print(username)
+            yield grp.getgrgid(pwd.getpwnam(username).pw_gid).gr_name
+            for group in grp.getgrall():
+                if username in group.gr_mem:
+                    yield group.gr_name
+
+        def all_groups(self):
+            for group in grp.getgrall():
                 yield group.gr_name
-
-    def all_groups(self):
-        for group in grp.getgrall():
-            yield group.gr_name
-
-class SecurityManager:
-    def __init__(self, key, max_duration=300):
-        self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512)
-        self.max_duration = max_duration
-
-    def hash_user(self, user):
-        maccer = self.maccer.copy()
-        summary = user.summarize()
-        maccer.update(summary.encode("utf-8"))
-        return "{}={}".format(summary, maccer.hexdigest())
-
-    def check_user(self, string):
-        parts = string.split("=", 1)
-        if len(parts) != 2:
-            # wrong format, expecting summary:hash
-            return False
-        summary, hash = map(lambda s: s.encode("utf-8"), parts)
-        maccer = self.maccer.copy()
-        maccer.update(summary)
-        user = User.from_hashstring(string)
-        if user is None:
-            return False
-        session_duration = datetime.now() - user.timestamp
-        macs_equal = hmac.compare_digest(maccer.hexdigest().encode("utf-8"), hash)
-        time_short = int(session_duration.total_seconds()) < self.max_duration 
-        return macs_equal and (time_short or user.permanent)
-
+except ModuleNotFoundError:
+    pass
diff --git a/back.py b/back.py
new file mode 100644
index 0000000000000000000000000000000000000000..57c91f332a845c619caf29d9d3fb41dcc305201c
--- /dev/null
+++ b/back.py
@@ -0,0 +1,27 @@
+# This snippet is in public domain.
+# However, please retain this link in your sources:
+# http://flask.pocoo.org/snippets/120/
+# Danya Alexeyevsky
+
+import functools
+from flask import session, request, redirect as flask_redirect, url_for
+import config
+
+cookie = getattr(config, "REDIRECT_BACK_COOKIE", "back")
+default_view = getattr(config, "REDIRECT_BACK_DEFAULT", "index")
+
+
+def anchor(func, cookie=cookie):
+    @functools.wraps(func)
+    def result(*args, **kwargs):
+        session[cookie] = request.url
+        return func(*args, **kwargs)
+    return result
+
+
+def url(default=default_view, cookie=cookie, **url_args):
+    return session.get(cookie, url_for(default, **url_args))
+
+
+def redirect(default=default_view, cookie=cookie, **url_args):
+    return flask_redirect(url(default, cookie, **url_args))
diff --git a/calendarpush.py b/calendarpush.py
index 804e1890d83d0ecb363abf370ea9f7af1df71ad2..5f0564a2d2d9ab72c6ff670a24d46fe635add969 100644
--- a/calendarpush.py
+++ b/calendarpush.py
@@ -2,15 +2,16 @@ from datetime import datetime, timedelta
 import random
 import quopri
 
-from caldav import DAVClient, Principal, Calendar, Event
-from caldav.lib.error import PropfindError
+from caldav import DAVClient
 from vobject.base import ContentLine
 
 import config
 
+
 class CalendarException(Exception):
     pass
 
+
 class Client:
     def __init__(self, calendar=None, url=None):
         if not config.CALENDAR_ACTIVE:
@@ -23,9 +24,12 @@ class Client:
                 self.principal = self.client.principal()
                 break
             except Exception as exc:
-                print("Got exception {} from caldav, retrying".format(str(exc)))
+                print("Got exception {} from caldav, retrying".format(
+                    str(exc)))
         if self.principal is None:
-            raise CalendarException("Got {} CalDAV-error from the CalDAV server.".format(config.CALENDAR_MAX_REQUESTS))
+            raise CalendarException(
+                "Got {} CalDAV-error from the CalDAV server.".format(
+                    config.CALENDAR_MAX_REQUESTS))
         if calendar is not None:
             self.calendar = self.get_calendar(calendar)
         else:
@@ -41,9 +45,11 @@ class Client:
                     for calendar in self.principal.calendars()
                 ]
             except Exception as exc:
-                print("Got exception {} from caldav, retrying".format(str(exc)))
-        raise CalendarException("Got {} CalDAV Errors from the CalDAV server.".format(config.CALENDAR_MAX_REQUESTS))
-
+                print("Got exception {} from caldav, retrying".format(
+                    str(exc)))
+        raise CalendarException(
+            "Got {} CalDAV Errors from the CalDAV server.".format(
+                config.CALENDAR_MAX_REQUESTS))
 
     def get_calendar(self, calendar_name):
         candidates = self.principal.calendars()
@@ -57,12 +63,14 @@ class Client:
             return
         candidates = [
             Event.from_raw_event(raw_event)
-            for raw_event in self.calendar.date_search(begin, begin + timedelta(hours=1))
+            for raw_event in self.calendar.date_search(
+                begin, begin + timedelta(hours=1))
         ]
         candidates = [event for event in candidates if event.name == name]
         event = None
         if len(candidates) == 0:
-            event = Event(None, name, description, begin,
+            event = Event(
+                None, name, description, begin,
                 begin + timedelta(hours=config.CALENDAR_DEFAULT_DURATION))
             vevent = self.calendar.add_event(event.to_vcal())
             event.vevent = vevent
@@ -76,11 +84,14 @@ NAME_KEY = "summary"
 DESCRIPTION_KEY = "description"
 BEGIN_KEY = "dtstart"
 END_KEY = "dtend"
+
+
 def _get_item(content, key):
     if key in content:
         return content[key][0].value
     return None
-        
+
+
 class Event:
     def __init__(self, vevent, name, description, begin, end):
         self.vevent = vevent
@@ -97,7 +108,8 @@ class Event:
         description = _get_item(content, DESCRIPTION_KEY)
         begin = _get_item(content, BEGIN_KEY)
         end = _get_item(content, END_KEY)
-        return Event(vevent=vevent, name=name, description=description,
+        return Event(
+            vevent=vevent, name=name, description=description,
             begin=begin, end=end)
 
     def set_description(self, description):
@@ -105,7 +117,8 @@ class Event:
         self.description = description
         encoded = encode_quopri(description)
         if DESCRIPTION_KEY not in raw_event.contents:
-            raw_event.contents[DESCRIPTION_KEY] = [ContentLine(DESCRIPTION_KEY, {"ENCODING": ["QUOTED-PRINTABLE"]}, encoded)]
+            raw_event.contents[DESCRIPTION_KEY] = [ContentLine(
+                DESCRIPTION_KEY, {"ENCODING": ["QUOTED-PRINTABLE"]}, encoded)]
         else:
             content_line = raw_event.contents[DESCRIPTION_KEY][0]
             content_line.value = encoded
@@ -129,21 +142,28 @@ SUMMARY:{summary}
 DESCRIPTION;ENCODING=QUOTED-PRINTABLE:{description}
 END:VEVENT
 END:VCALENDAR""".format(
-            uid=create_uid(), now=date_format(datetime.now()-offset),
-            begin=date_format(self.begin-offset), end=date_format(self.end-offset),
+            uid=create_uid(),
+            now=date_format(datetime.now() - offset),
+            begin=date_format(self.begin - offset),
+            end=date_format(self.end - offset),
             summary=self.name,
             description=encode_quopri(self.description))
 
+
 def create_uid():
     return str(random.randint(0, 1e10)).rjust(10, "0")
 
+
 def date_format(dt):
     return dt.strftime("%Y%m%dT%H%M%SZ")
 
+
 def get_timezone_offset():
     difference = datetime.now() - datetime.utcnow()
-    return timedelta(hours=round(difference.seconds / 3600 + difference.days * 24))
+    return timedelta(
+        hours=round(difference.seconds / 3600 + difference.days * 24))
 
-def encode_quopri(text):
-    return quopri.encodestring(text.encode("utf-8")).replace(b"\n", b"=0A").decode("utf-8")
 
+def encode_quopri(text):
+    return quopri.encodestring(text.encode("utf-8")).replace(
+        b"\n", b"=0A").decode("utf-8")
diff --git a/check_routes.py b/check_routes.py
index 5a577c037fb3c0078cfce2fb422e5abf7558ca0c..c7e8760d1aee5e9023fbec48e293137f36bf38be 100644
--- a/check_routes.py
+++ b/check_routes.py
@@ -3,14 +3,20 @@ import regex as re
 import os
 import sys
 
-ROUTE_PATTERN = r'@(?:[[:alpha:]])+\.route\(\"(?<url>[^"]+)"[^)]*\)\s*(?:@[[:alpha:]_()., ]+\s*)*def\s+(?<name>[[:alpha:]][[:alnum:]_]*)\((?<params>[[:alnum:], ]*)\):'
+ROUTE_PATTERN = (
+    r'@(?:[[:alpha:]])+\.route\(\"(?<url>[^"]+)"[^)]*\)\s*'
+    r'(?:@[[:alpha:]_()., ]+\s*)*def\s+(?<name>[[:alpha:]][[:alnum:]_]*)'
+    r'\((?<params>[[:alnum:], ]*)\):')
 quote_group = "[\"']"
-URL_FOR_PATTERN = r'url_for\({quotes}(?<name>[[:alpha:]][[:alnum:]_]*){quotes}'.format(quotes=quote_group)
+URL_FOR_PATTERN = (
+    r'url_for\({quotes}(?<name>[[:alpha:]][[:alnum:]_]*)'
+    '{quotes}'.format(quotes=quote_group))
 
 ROOT_DIR = "."
 ENDINGS = [".py", ".html", ".txt"]
 MAX_DEPTH = 2
 
+
 def list_dir(dir, level=0):
     if level >= MAX_DEPTH:
         return
@@ -23,7 +29,8 @@ def list_dir(dir, level=0):
                 if file.endswith(ending):
                     yield path
         elif os.path.isdir(path):
-            yield from list_dir(path, level+1)
+            yield from list_dir(path, level + 1)
+
 
 class Route:
     def __init__(self, file, name, parameters):
@@ -38,13 +45,15 @@ class Route:
     def get_parameter_set(self):
         return {parameter.name for parameter in self.parameters}
 
+
 class Parameter:
     def __init__(self, name, type=None):
         self.name = name
         self.type = type
 
     def __repr__(self):
-        return "Parameter({name}, {type})".format(name=self.name, type=self.type)
+        return "Parameter({name}, {type})".format(
+            name=self.name, type=self.type)
 
     @staticmethod
     def from_string(text):
@@ -53,6 +62,7 @@ class Parameter:
             return Parameter(name, type)
         return Parameter(text)
 
+
 def split_url_parameters(url):
     params = []
     current_param = None
@@ -68,9 +78,11 @@ def split_url_parameters(url):
                 current_param += char
     return params
 
+
 def split_function_parameters(parameters):
     return list(map(str.strip, parameters.split(",")))
 
+
 def read_url_for_parameters(content):
     params = []
     bracket_level = 1
@@ -92,6 +104,7 @@ def read_url_for_parameters(content):
             elif char == ")":
                 bracket_level -= 1
 
+
 class UrlFor:
     def __init__(self, file, name, parameters):
         self.file = file
@@ -99,8 +112,10 @@ class UrlFor:
         self.parameters = parameters
 
     def __repr__(self):
-        return "UrlFor(file={file}, name={name}, parameters={parameters})".format(
-            file=self.file, name=self.name, parameters=self.parameters)
+        return (
+            "UrlFor(file={file}, name={name}, parameters={parameters})".format(
+                file=self.file, name=self.name, parameters=self.parameters))
+
 
 routes = {}
 url_fors = []
@@ -109,24 +124,29 @@ for file in list_dir(ROOT_DIR):
         content = infile.read()
         for match in re.finditer(ROUTE_PATTERN, content):
             name = match.group("name")
-            function_parameters = split_function_parameters(match.group("params"))
+            function_parameters = split_function_parameters(
+                match.group("params"))
             url_parameters = split_url_parameters(match.group("url"))
             routes[name] = Route(file, name, url_parameters)
         for match in re.finditer(URL_FOR_PATTERN, content):
             name = match.group("name")
             begin, end = match.span()
             parameters = read_url_for_parameters(content[end:])
-            url_fors.append(UrlFor(file=file, name=name, parameters=parameters))
+            url_fors.append(UrlFor(
+                file=file, name=name, parameters=parameters))
+
 
 for url_for in url_fors:
     if url_for.name not in routes:
-        print("Missing route '{}' (for url_for in '{}')".format(url_for.name, url_for.file))
+        print("Missing route '{}' (for url_for in '{}')".format(
+            url_for.name, url_for.file))
         continue
     route = routes[url_for.name]
     route_parameters = route.get_parameter_set()
     url_parameters = set(url_for.parameters)
     if len(route_parameters ^ url_parameters) > 0:
-        print("Parameters not matching for '{}' in '{}:'".format(url_for.name, url_for.file))
+        print("Parameters not matching for '{}' in '{}:'".format(
+            url_for.name, url_for.file))
         only_route = route_parameters - url_parameters
         only_url = url_parameters - route_parameters
         if len(only_route) > 0:
diff --git a/config.py.example b/config.py.example
index d23beb8f7ffb7567deb7c8a8b3ae8f782aa46603..581ca2258cc1f74ba69b02a9fdcafc4f7e33565d 100644
--- a/config.py.example
+++ b/config.py.example
@@ -196,7 +196,7 @@ LATEX_BULLETPOINTS = [
 #        "logo": "asta-logo.tex", # optional: replaces the general template to include at the top of protocol.tex set by LATEX_LOGO_TEMPLATE
 #        "geometry": "bottom=1.6cm,top=1.6cm,inner=2.5cm,outer=1.0cm,footskip=1.0cm,headsep=0.6cm", # optional: replaces the general protocol page geometry set by LATEX_GEOMETRY
 #        "pagestyle": "fancy", # optional: replaces the general protocol pagestyle set by LATEX_PAGESTYLE
-#        "additionalpackages": ["[absolute]{textpos}", "{fancyheadings}"], # optional: replaces the general latex packages set by LATEX_ADDITIONAL_PACKAGES
+#        "additional_packages": ["[absolute]{textpos}", "{fancyheadings}"], # optional: replaces the general latex packages set by LATEX_ADDITIONAL_PACKAGES
 #        "headerfooter": True # optional: replaces the general LATEX_HEADER_FOOTER option
 #    }
 #}
diff --git a/decorators.py b/decorators.py
index 30fd142c8eedfdef29b4da748073397176a4ca32..17b73569653f0b514b910aa6ef152884252d2fb8 100644
--- a/decorators.py
+++ b/decorators.py
@@ -1,9 +1,11 @@
-from flask import redirect, flash, request, url_for
+from flask import request, flash, abort
 
 from functools import wraps
 
 from models.database import ALL_MODELS
-from shared import db, current_user
+from shared import current_user
+from utils import get_csrf_token
+import back
 
 ID_KEY = "id"
 KEY_NOT_PRESENT_MESSAGE = "Missing {}_id."
@@ -11,11 +13,14 @@ OBJECT_DOES_NOT_EXIST_MESSAGE = "There is no {} with id {}."
 
 MISSING_VIEW_RIGHT = "Dir fehlenden die nötigen Zugriffsrechte."
 
+
 def default_redirect():
-    return redirect(request.args.get("next") or url_for("index"))
+    return back.redirect()
+
 
 def login_redirect():
-    return redirect(request.args.get("next") or url_for("login"))
+    return back.redirect("login")
+
 
 def db_lookup(*models, check_exists=True):
     def _decorator(function):
@@ -31,7 +36,8 @@ def db_lookup(*models, check_exists=True):
                 obj = model.query.filter_by(id=obj_id).first()
                 if check_exists and obj is None:
                     model_name = model.__class__.__name__
-                    flash(OBJECT_DOES_NOT_EXIST_MESSAGE.format(model_name, obj_id),
+                    flash(OBJECT_DOES_NOT_EXIST_MESSAGE.format(
+                        model_name, obj_id),
                         "alert-error")
                     return default_redirect()
                 kwargs[key] = obj
@@ -40,8 +46,10 @@ def db_lookup(*models, check_exists=True):
         return _decorated_function
     return _decorator
 
+
 def require_right(right, require_exist):
     necessary_right_name = "has_{}_right".format(right)
+
     def _decorator(function):
         @wraps(function)
         def _decorated_function(*args, **kwargs):
@@ -64,17 +72,33 @@ def require_right(right, require_exist):
         return _decorated_function
     return _decorator
 
+
 def require_public_view_right(require_exist=True):
     return require_right("public_view", require_exist)
 
+
 def require_private_view_right(require_exist=True):
     return require_right("private_view", require_exist)
 
+
 def require_modify_right(require_exist=True):
     return require_right("modify", require_exist)
 
+
 def require_publish_right(require_exist=True):
     return require_right("publish", require_exist)
 
+
 def require_admin_right(require_exist=True):
     return require_right("admin", require_exist)
+
+
+def protect_csrf(function):
+    @wraps(function)
+    def _decorated_function(*args, **kwargs):
+        token = request.args.get("csrf_token")
+        if token != get_csrf_token():
+            print(token, get_csrf_token())
+            abort(400)
+        return function(*args, **kwargs)
+    return _decorated_function
diff --git a/legacy.py b/legacy.py
index 868458262da8a2b1dbf5fbb949a5e50db4dd6a82..507634ad3bef1afc5bfae78a6c34c8f8e913adff 100644
--- a/legacy.py
+++ b/legacy.py
@@ -1,16 +1,17 @@
 from datetime import datetime
-from fuzzywuzzy import fuzz, process
-import tempfile
+from fuzzywuzzy import process
 
-from models.database import Todo, OldTodo, Protocol, ProtocolType, TodoMail
+from models.database import OldTodo, Protocol, ProtocolType, TodoMail
 from shared import db
 
 import config
 
+
 def lookup_todo_id(old_candidates, new_who, new_description):
     # Check for perfect matches
     for candidate in old_candidates:
-        if candidate.who == new_who and candidate.description == new_description:
+        if (candidate.who == new_who
+                and candidate.description == new_description):
             return candidate.old_id
     # Accept if who has been changed
     for candidate in old_candidates:
@@ -32,11 +33,13 @@ def lookup_todo_id(old_candidates, new_who, new_description):
             new_description, best_match, best_match_score))
         return None
 
+
 INSERT_PROTOCOLTYPE = "INSERT INTO `protocolManager_protocoltype`"
 INSERT_PROTOCOL = "INSERT INTO `protocolManager_protocol`"
 INSERT_TODO = "INSERT INTO `protocolManager_todo`"
 INSERT_TODOMAIL = "INSERT INTO `protocolManager_todonamemailassignment`"
 
+
 def import_old_protocols(sql_text):
     protocoltype_lines = []
     protocol_lines = []
@@ -46,22 +49,27 @@ def import_old_protocols(sql_text):
         elif line.startswith(INSERT_PROTOCOL):
             protocol_lines.append(line)
     if (len(protocoltype_lines) == 0
-    or len(protocol_lines) == 0):
+            or len(protocol_lines) == 0):
         raise ValueError("Necessary lines not found.")
     type_id_to_handle = {}
     for type_line in protocoltype_lines:
-        for id, handle, name, mail, protocol_id in _split_insert_line(type_line):
+        for id, handle, name, mail, protocol_id in _split_insert_line(
+                type_line):
             type_id_to_handle[int(id)] = handle.lower()
     protocols = []
     for protocol_line in protocol_lines:
         for (protocol_id, old_type_id, date, source, textsummary, htmlsummary,
-            deleted, sent, document_id) in _split_insert_line(protocol_line):
+                deleted, sent, document_id) in _split_insert_line(
+                protocol_line):
             date = datetime.strptime(date, "%Y-%m-%d")
             handle = type_id_to_handle[int(old_type_id)]
-            protocoltype = ProtocolType.query.filter(ProtocolType.short_name.ilike(handle)).first()
+            protocoltype = ProtocolType.query.filter(
+                ProtocolType.short_name.ilike(handle)).first()
             if protocoltype is None:
-                raise KeyError("No protocoltype for handle '{}'.".format(handle))
-            protocol = Protocol(protocoltype_id=protocoltype.id, date=date, source=source)
+                raise KeyError(
+                    "No protocoltype for handle '{}'.".format(handle))
+            protocol = Protocol(
+                protocoltype_id=protocoltype.id, date=date, source=source)
             db.session.add(protocol)
             db.session.commit()
             import tasks
@@ -70,6 +78,7 @@ def import_old_protocols(sql_text):
         print(protocol.date)
         tasks.parse_protocol(protocol)
 
+
 def import_old_todomails(sql_text):
     todomail_lines = []
     for line in sql_text.splitlines():
@@ -98,28 +107,34 @@ def import_old_todos(sql_text):
         elif line.startswith(INSERT_TODO):
             todo_lines.append(line)
     if (len(protocoltype_lines) == 0
-    or len(protocol_lines) == 0
-    or len(todo_lines) == 0):
+            or len(protocol_lines) == 0
+            or len(todo_lines) == 0):
         raise ValueError("Necessary lines not found.")
     type_id_to_handle = {}
     for type_line in protocoltype_lines:
-        for id, handle, name, mail, protocol_id in _split_insert_line(type_line):
+        for id, handle, name, mail, protocol_id in _split_insert_line(
+                type_line):
             type_id_to_handle[int(id)] = handle.lower()
     protocol_id_to_key = {}
     for protocol_line in protocol_lines:
         for (protocol_id, type_id, date, source, textsummary, htmlsummary,
-            deleted, sent, document_id) in _split_insert_line(protocol_line):
+                deleted, sent, document_id) in _split_insert_line(
+                protocol_line):
             handle = type_id_to_handle[int(type_id)]
-            date_string = date [2:]
-            protocol_id_to_key[int(protocol_id)] = "{}-{}".format(handle, date_string)
+            date_string = date[2:]
+            protocol_id_to_key[int(protocol_id)] = "{}-{}".format(
+                handle, date_string)
     todos = []
     for todo_line in todo_lines:
-        for old_id, protocol_id, who, what, start_time, end_time, done in _split_insert_line(todo_line):
+        for (old_id, protocol_id, who, what, start_time, end_time,
+             done) in _split_insert_line(todo_line):
             protocol_id = int(protocol_id)
             if protocol_id not in protocol_id_to_key:
-                print("Missing protocol with ID {} for Todo {}".format(protocol_id, what))
+                print("Missing protocol with ID {} for Todo {}".format(
+                    protocol_id, what))
                 continue
-            todo = OldTodo(old_id=old_id, who=who, description=what,
+            todo = OldTodo(
+                old_id=old_id, who=who, description=what,
                 protocol_key=protocol_id_to_key[protocol_id])
             todos.append(todo)
     OldTodo.query.delete()
@@ -127,12 +142,16 @@ def import_old_todos(sql_text):
     for todo in todos:
         db.session.add(todo)
     db.session.commit()
-        
+
+
 def _split_insert_line(line):
     insert_part, values_part = line.split("VALUES", 1)
     return _split_base_level(values_part)
 
-def _split_base_level(text, begin="(", end=")", separator=",", string_terminator="'", line_end=";", ignore=" ", escape="\\"):
+
+def _split_base_level(
+        text, begin="(", end=")", separator=",", string_terminator="'",
+        line_end=";", ignore=" ", escape="\\"):
     raw_parts = []
     current_part = None
     index = 0
@@ -210,5 +229,3 @@ def _split_base_level(text, begin="(", end=")", separator=",", string_terminator
             fields.append(current_field)
         parts.append(fields)
     return parts
-        
-    
diff --git a/models/database.py b/models/database.py
index 5304fbf3c9b72d5e56b094cf42cde3a65f3b6d11..69f2bde49162f95a58e579655af750dee82158e3 100644
--- a/models/database.py
+++ b/models/database.py
@@ -1,25 +1,26 @@
-from flask import render_template, send_file, url_for, redirect, flash, request
+from flask import render_template
 
-from datetime import datetime, time, date, timedelta
-import math
-from io import StringIO, BytesIO
+from datetime import datetime
+from io import BytesIO
 from enum import Enum
 from uuid import uuid4
 
-from shared import db, date_filter, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY, current_user
-from utils import random_string, get_etherpad_url, split_terms, check_ip_in_networks
+from shared import (
+    db, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY,
+    current_user)
+from utils import get_etherpad_url, split_terms, check_ip_in_networks
 from models.errors import DateNotMatchingException
 from dateutil import tz
 
 import os
 
 from sqlalchemy import event
-from sqlalchemy.orm import relationship, backref, sessionmaker
-from sqlalchemy.ext.hybrid import hybrid_method
+from sqlalchemy.orm import relationship, backref
 
 import config
 from todostates import make_states
 
+
 class DatabaseModel(db.Model):
     __abstract__ = True
 
@@ -48,6 +49,11 @@ class DatabaseModel(db.Model):
             columns.append("{}={}".format(column_name, value))
         return "{}({})".format(self.__class__.__name__, ", ".join(columns))
 
+    @classmethod
+    def first_by_id(cls, instance_id):
+        return cls.query.filter_by(id=instance_id).first()
+
+
 class ProtocolType(DatabaseModel):
     __tablename__ = "protocoltypes"
     __model_name__ = "protocoltype"
@@ -73,46 +79,70 @@ class ProtocolType(DatabaseModel):
     allowed_networks = db.Column(db.String)
     latex_template = db.Column(db.String)
 
-    protocols = relationship("Protocol", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="Protocol.id")
-    default_tops = relationship("DefaultTOP", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="DefaultTOP.number")
-    reminders = relationship("MeetingReminder", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="MeetingReminder.days_before")
-    todos = relationship("Todo", backref=backref("protocoltype"), order_by="Todo.id")
-    metas = relationship("DefaultMeta", backref=backref("protocoltype"), cascade="all, delete-orphan")
-    decisioncategories = relationship("DecisionCategory", backref=backref("protocoltype"), cascade="all, delete-orphan")
+    protocols = relationship(
+        "Protocol", backref=backref("protocoltype"),
+        cascade="all, delete-orphan", order_by="Protocol.id")
+    default_tops = relationship(
+        "DefaultTOP", backref=backref("protocoltype"),
+        cascade="all, delete-orphan", order_by="DefaultTOP.number")
+    reminders = relationship(
+        "MeetingReminder", backref=backref("protocoltype"),
+        cascade="all, delete-orphan", order_by="MeetingReminder.days_before")
+    todos = relationship(
+        "Todo", backref=backref("protocoltype"), order_by="Todo.id")
+    metas = relationship(
+        "DefaultMeta", backref=backref("protocoltype"),
+        cascade="all, delete-orphan")
+    decisioncategories = relationship(
+        "DecisionCategory", backref=backref("protocoltype"),
+        cascade="all, delete-orphan")
 
     def get_latest_protocol(self):
-        candidates = sorted([protocol for protocol in self.protocols if protocol.is_done()], key=lambda p: p.date, reverse=True)
+        candidates = sorted([
+            protocol for protocol in self.protocols
+            if protocol.is_done()], key=lambda p: p.date, reverse=True)
         if len(candidates) == 0:
             return None
         return candidates[0]
 
     def has_public_view_right(self, user, check_networks=True):
-        return (self.has_public_anonymous_view_right(check_networks=check_networks)
-            or (user is not None and self.has_public_authenticated_view_right(user))
+        return (
+            self.has_public_anonymous_view_right(check_networks=check_networks)
+            or (user is not None
+                and self.has_public_authenticated_view_right(user))
             or self.has_admin_right(user))
 
     def has_public_anonymous_view_right(self, check_networks=True):
-        return (self.is_public
+        return (
+            self.is_public
             and ((not self.restrict_networks or not check_networks)
-                or check_ip_in_networks(self.allowed_networks)))
+                 or check_ip_in_networks(self.allowed_networks)))
 
     def has_public_authenticated_view_right(self, user):
-        return ((self.public_group != "" and self.public_group in user.groups)
-            or (self.private_group != "" and self.private_group in user.groups))
+        return (
+            (self.public_group != "" and self.public_group in user.groups)
+            or (self.private_group != ""
+                and self.private_group in user.groups))
 
     def has_private_view_right(self, user):
-        return ((user is not None
-            and (self.private_group != "" and self.private_group in user.groups))
+        return (
+            (user is not None
+             and (self.private_group != ""
+                  and self.private_group in user.groups))
             or self.has_admin_right(user))
 
     def has_modify_right(self, user):
-        return ((user is not None
-            and (self.modify_group != "" and self.modify_group in user.groups))
+        return (
+            (user is not None
+             and (self.modify_group != ""
+                  and self.modify_group in user.groups))
             or self.has_admin_right(user))
 
     def has_publish_right(self, user):
-        return ((user is not None
-            and (self.publish_group != "" and self.publish_group in user.groups))
+        return (
+            (user is not None
+             and (self.publish_group != ""
+                  and self.publish_group in user.groups))
             or self.has_admin_right(user))
 
     def has_admin_right(self, user):
@@ -129,7 +159,8 @@ class ProtocolType(DatabaseModel):
     def get_public_protocoltypes(user, check_networks=True):
         return [
             protocoltype for protocoltype in ProtocolType.query.all()
-            if protocoltype.has_public_view_right(user, check_networks=check_networks)
+            if protocoltype.has_public_view_right(
+                user, check_networks=check_networks)
         ]
 
     @staticmethod
@@ -163,12 +194,22 @@ class Protocol(DatabaseModel):
     public = db.Column(db.Boolean)
     pad_identifier = db.Column(db.String)
 
-    tops = relationship("TOP", backref=backref("protocol"), cascade="all, delete-orphan", order_by="TOP.number")
-    decisions = relationship("Decision", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Decision.id")
-    documents = relationship("Document", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Document.is_compiled")
-    errors = relationship("Error", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Error.id")
-    metas = relationship("Meta", backref=backref("protocol"), cascade="all, delete-orphan")
-    localtops = relationship("LocalTOP", backref=backref("protocol"), cascade="all, delete-orphan")
+    tops = relationship(
+        "TOP", backref=backref("protocol"),
+        cascade="all, delete-orphan", order_by="TOP.number")
+    decisions = relationship(
+        "Decision", backref=backref("protocol"),
+        cascade="all, delete-orphan", order_by="Decision.id")
+    documents = relationship(
+        "Document", backref=backref("protocol"),
+        cascade="all, delete-orphan", order_by="Document.is_compiled")
+    errors = relationship(
+        "Error", backref=backref("protocol"), cascade="all, delete-orphan",
+        order_by="Error.id")
+    metas = relationship(
+        "Meta", backref=backref("protocol"), cascade="all, delete-orphan")
+    localtops = relationship(
+        "LocalTOP", backref=backref("protocol"), cascade="all, delete-orphan")
 
     likes = relationship("Like", secondary="likeprotocolassociations")
 
@@ -177,14 +218,16 @@ class Protocol(DatabaseModel):
 
     def create_error(self, action, name, description):
         now = datetime.now()
-        return Error(protocol_id=self.id, action=action, name=name,
+        return Error(
+            protocol_id=self.id, action=action, name=name,
             datetime=now, description=description)
 
     def create_localtops(self):
         local_tops = []
         for default_top in self.protocoltype.default_tops:
-            local_tops.append(LocalTOP(defaulttop_id=default_top.id,
-                protocol_id=self.id, description=default_top.description or ""))
+            local_tops.append(LocalTOP(
+                defaulttop_id=default_top.id, protocol_id=self.id,
+                description=default_top.description or ""))
         return local_tops
 
     def fill_from_remarks(self, remarks):
@@ -211,7 +254,8 @@ class Protocol(DatabaseModel):
             new_date = _date_or_lazy(DATE_KEY, get_date=True)
             if self.date is not None:
                 if new_date != self.date:
-                    raise DateNotMatchingException(original_date=self.date, protocol_date=new_date)
+                    raise DateNotMatchingException(
+                        original_date=self.date, protocol_date=new_date)
             else:
                 self.date = new_date
         if START_TIME_KEY in remarks:
@@ -225,7 +269,9 @@ class Protocol(DatabaseModel):
         for default_meta in self.protocoltype.metas:
             if default_meta.key in remarks:
                 value = remarks[default_meta.key].value.strip()
-                meta = Meta(protocol_id=self.id, name=default_meta.name, value=value, internal=default_meta.internal)
+                meta = Meta(
+                    protocol_id=self.id, name=default_meta.name, value=value,
+                    internal=default_meta.internal)
                 db.session.add(meta)
         db.session.commit()
 
@@ -235,16 +281,21 @@ class Protocol(DatabaseModel):
             or self.protocoltype.has_private_view_right(user)
         )
 
+    def get_visible_content(self, user):
+        if self.has_private_view_right(user):
+            return self.content_private
+        return self.content_public
+
     def is_done(self):
         return self.done
 
     def get_state_glyph(self):
         if self.is_done():
-            state = "unchecked" #"Fertig"
+            state = "unchecked"  # Fertig
             if self.public:
-                state = "check" #"Veröffentlicht"
+                state = "check"  # Veröffentlicht
         else:
-            state = "pencil" #"Geplant"
+            state = "pencil"  # Geplant
         return state
 
     def get_state_name(self):
@@ -280,7 +331,9 @@ class Protocol(DatabaseModel):
         if self.pad_identifier is None:
             identifier = self.get_identifier()
             if self.protocoltype.non_reproducible_pad_links:
-                identifier = "{}-{}".format(identifier, str(uuid4()).replace("-", ""))[:50]
+                identifier = "{}-{}".format(
+                    identifier,
+                    str(uuid4()).replace("-", ""))[:50]
             self.pad_identifier = identifier
             db.session.commit()
         return get_etherpad_url(self.pad_identifier)
@@ -292,13 +345,18 @@ class Protocol(DatabaseModel):
 
     def get_datetime(self):
         time = self.get_time()
-        return datetime(self.date.year, self.date.month, self.date.day, time.hour, time.minute)
+        return datetime(
+            self.date.year, self.date.month, self.date.day, time.hour,
+            time.minute)
 
     def has_nonplanned_tops(self):
         return len([top for top in self.tops if not top.planned]) > 0
 
     def get_originating_todos(self):
-        return [todo for todo in self.todos if self == todo.get_first_protocol()]
+        return [
+            todo for todo in self.todos
+            if self == todo.get_first_protocol()
+        ]
 
     def get_open_todos(self):
         return [
@@ -310,7 +368,7 @@ class Protocol(DatabaseModel):
         candidates = [
             document for document in self.documents
             if document.is_compiled
-                and (private is None or document.is_private == private)
+            and (private is None or document.is_private == private)
         ]
         return len(candidates) > 0
 
@@ -318,10 +376,16 @@ class Protocol(DatabaseModel):
         candidates = [
             document for document in self.documents
             if document.is_compiled
-               and (private is None or document.is_private == private) 
+            and (private is None or document.is_private == private)
+        ]
+        private_candidates = [
+            document for document in candidates
+            if document.is_private
+        ]
+        public_candidates = [
+            document for document in candidates
+            if not document.is_private
         ]
-        private_candidates = [document for document in candidates if document.is_private]
-        public_candidates = [document for document in candidates if not document.is_private]
         if len(private_candidates) > 0:
             return private_candidates[0]
         elif len(public_candidates) > 0:
@@ -359,15 +423,16 @@ class Protocol(DatabaseModel):
     def create_new_protocol(protocoltype, date, start_time=None):
         if start_time is None:
             start_time = protocoltype.usual_time
-        protocol = Protocol(protocoltype_id=protocoltype.id,
-            date=date, start_time=start_time)
+        protocol = Protocol(
+            protocoltype_id=protocoltype.id, date=date, start_time=start_time)
         db.session.add(protocol)
         db.session.commit()
         for local_top in protocol.create_localtops():
             db.session.add(local_top)
         for default_meta in protocoltype.metas:
             if default_meta.prior:
-                meta = Meta(protocol_id=protocol.id, name=default_meta.name,
+                meta = Meta(
+                    protocol_id=protocol.id, name=default_meta.name,
                     internal=default_meta.internal, value=default_meta.value)
                 db.session.add(meta)
         db.session.commit()
@@ -375,7 +440,6 @@ class Protocol(DatabaseModel):
         tasks.push_tops_to_calendar(protocol)
         return protocol
 
-        
 
 @event.listens_for(Protocol, "before_delete")
 def on_protocol_delete(mapper, connection, protocol):
@@ -391,7 +455,9 @@ class DefaultTOP(DatabaseModel):
     number = db.Column(db.Integer)
     description = db.Column(db.String)
 
-    localtops = relationship("LocalTOP", backref=backref("defaulttop"), cascade="all, delete-orphan")
+    localtops = relationship(
+        "LocalTOP", backref=backref("defaulttop"),
+        cascade="all, delete-orphan")
 
     def get_parent(self):
         return self.protocoltype
@@ -400,15 +466,17 @@ class DefaultTOP(DatabaseModel):
         return self.number > 0
 
     def get_localtop(self, protocol):
-        return LocalTOP.query.filter_by(defaulttop_id=self.id,
-            protocol_id=protocol.id).first()
+        return LocalTOP.query.filter_by(
+            defaulttop_id=self.id, protocol_id=protocol.id).first()
 
     def get_top(self, protocol):
         localtop = self.get_localtop(protocol)
-        top = TOP(protocol_id=protocol.id, name=self.name,
+        top = TOP(
+            protocol_id=protocol.id, name=self.name,
             description=localtop.description)
         return top
 
+
 class TOP(DatabaseModel):
     __tablename__ = "tops"
     __model_name__ = "top"
@@ -424,6 +492,7 @@ class TOP(DatabaseModel):
     def get_parent(self):
         return self.protocol
 
+
 class LocalTOP(DatabaseModel):
     __tablename__ = "localtops"
     __model_name__ = "localtop"
@@ -437,7 +506,8 @@ class LocalTOP(DatabaseModel):
 
     def is_expandable(self):
         user = current_user()
-        return (self.has_private_view_right(user)
+        return (
+            self.has_private_view_right(user)
             and self.description is not None
             and len(self.description) > 0)
 
@@ -447,6 +517,7 @@ class LocalTOP(DatabaseModel):
             classes.append("expansion-button")
         return classes
 
+
 class Document(DatabaseModel):
     __tablename__ = "documents"
     __model_name__ = "document"
@@ -467,6 +538,7 @@ class Document(DatabaseModel):
         with open(self.get_filename(), "rb") as file:
             return BytesIO(file.read())
 
+
 @event.listens_for(Document, "before_delete")
 def on_document_delete(mapper, connection, document):
     if document.filename is not None:
@@ -474,6 +546,7 @@ def on_document_delete(mapper, connection, document):
         if os.path.isfile(document_path):
             os.remove(document_path)
 
+
 class DecisionDocument(DatabaseModel):
     __tablename__ = "decisiondocuments"
     __model_name__ = "decisiondocument"
@@ -492,6 +565,7 @@ class DecisionDocument(DatabaseModel):
         with open(self.get_filename(), "rb") as file:
             return BytesIO(file.read())
 
+
 @event.listens_for(DecisionDocument, "before_delete")
 def on_decisions_document_delete(mapper, connection, document):
     if document.filename is not None:
@@ -499,6 +573,7 @@ def on_decisions_document_delete(mapper, connection, document):
         if os.path.isfile(document_path):
             os.remove(document_path)
 
+
 class TodoState(Enum):
     open = 0
     waiting = 1
@@ -513,7 +588,7 @@ class TodoState(Enum):
     def get_name(self):
         STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
         return STATE_TO_NAME[self]
-    
+
     @staticmethod
     def get_name_to_state():
         STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
@@ -550,8 +625,10 @@ class TodoState(Enum):
     @staticmethod
     def from_name_with_date(name, protocol=None):
         name = name.strip().lower()
-        if not " " in name:
-            raise ValueError("{} does definitely not contain a state and a date".format(name))
+        if " " not in name:
+            raise ValueError(
+                "{} does not contain a state and a date".format(
+                    name))
         name_part, date_part = name.split(" ", 1)
         state = TodoState.from_name(name_part)
         date = None
@@ -566,7 +643,8 @@ class TodoState(Enum):
                     year = datetime.now().year
                     if protocol is not None:
                         year = protocol.date.year
-                    date = datetime(year=year, month=date.month, day=date.day).date()
+                    date = datetime(
+                        year=year, month=date.month, day=date.day).date()
                 break
             except ValueError as exc:
                 last_exc = exc
@@ -587,7 +665,8 @@ class Todo(DatabaseModel):
     state = db.Column(db.Enum(TodoState), nullable=False)
     date = db.Column(db.Date, nullable=True)
 
-    protocols = relationship("Protocol", secondary="todoprotocolassociations", backref="todos")
+    protocols = relationship(
+        "Protocol", secondary="todoprotocolassociations", backref="todos")
     likes = relationship("Like", secondary="liketodoassociations")
 
     def get_parent(self):
@@ -618,11 +697,13 @@ class Todo(DatabaseModel):
 
     def get_state(self):
         return "[{}]".format(self.get_state_plain())
+
     def get_state_plain(self):
         result = self.state.get_name()
         if self.state.needs_date():
             result = "{} {}".format(result, date_filter_short(self.date))
         return result
+
     def get_state_tex(self):
         return self.get_state_plain()
 
@@ -651,7 +732,8 @@ class Todo(DatabaseModel):
         bold = "'''"
         if use_dokuwiki:
             bold = "**"
-        return "{0}{1}:{0} {2}: {3} - {4}".format(bold,
+        return "{0}{1}:{0} {2}: {3} - {4}".format(
+            bold,
             "Neuer Todo" if self.is_new(current_protocol) else "Todo",
             self.who,
             self.description,
@@ -665,10 +747,14 @@ class Todo(DatabaseModel):
         parts.append("id {}".format(self.get_id()))
         return "[{}]".format(";".join(parts))
 
+
 class TodoProtocolAssociation(DatabaseModel):
     __tablename__ = "todoprotocolassociations"
-    todo_id = db.Column(db.Integer, db.ForeignKey("todos.id"), primary_key=True)
-    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"), primary_key=True)
+    todo_id = db.Column(
+        db.Integer, db.ForeignKey("todos.id"), primary_key=True)
+    protocol_id = db.Column(
+        db.Integer, db.ForeignKey("protocols.id"), primary_key=True)
+
 
 class Decision(DatabaseModel):
     __tablename__ = "decisions"
@@ -677,9 +763,12 @@ class Decision(DatabaseModel):
     protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
     content = db.Column(db.String)
 
-    document = relationship("DecisionDocument", backref=backref("decision"), cascade="all, delete-orphan", uselist=False)
+    document = relationship(
+        "DecisionDocument", backref=backref("decision"),
+        cascade="all, delete-orphan", uselist=False)
 
-    categories = relationship("DecisionCategory", secondary="decisioncategoryassociations")
+    categories = relationship(
+        "DecisionCategory", secondary="decisioncategoryassociations")
     likes = relationship("Like", secondary="likedecisionassociations")
 
     def get_parent(self):
@@ -688,6 +777,7 @@ class Decision(DatabaseModel):
     def get_categories_str(self):
         return ", ".join(map(lambda c: c.name, self.categories))
 
+
 class DecisionCategory(DatabaseModel):
     __tablename__ = "decisioncategories"
     __model_name__ = "decisioncategory"
@@ -698,10 +788,14 @@ class DecisionCategory(DatabaseModel):
     def get_parent(self):
         return self.protocoltype
 
+
 class DecisionCategoryAssociation(DatabaseModel):
     __tablename__ = "decisioncategoryassociations"
-    decision_id = db.Column(db.Integer, db.ForeignKey("decisions.id"), primary_key=True)
-    decisioncategory_id = db.Column(db.Integer, db.ForeignKey("decisioncategories.id"), primary_key=True)
+    decision_id = db.Column(
+        db.Integer, db.ForeignKey("decisions.id"), primary_key=True)
+    decisioncategory_id = db.Column(
+        db.Integer, db.ForeignKey("decisioncategories.id"), primary_key=True)
+
 
 class MeetingReminder(DatabaseModel):
     __tablename__ = "meetingreminders"
@@ -716,6 +810,7 @@ class MeetingReminder(DatabaseModel):
     def get_parent(self):
         return self.protocoltype
 
+
 class Error(DatabaseModel):
     __tablename__ = "errors"
     __model_name__ = "error"
@@ -737,6 +832,7 @@ class Error(DatabaseModel):
             return "\n".join(lines)
         return "\n".join(["\n".join(lines[:2]), "…", "\n".join(lines[-2:])])
 
+
 class TodoMail(DatabaseModel):
     __tablename__ = "todomails"
     __model_name__ = "todomail"
@@ -747,6 +843,7 @@ class TodoMail(DatabaseModel):
     def get_formatted_mail(self):
         return "{} <{}>".format(self.name, self.mail)
 
+
 class OldTodo(DatabaseModel):
     __tablename__ = "oldtodos"
     __model_name__ = "oldtodo"
@@ -756,6 +853,7 @@ class OldTodo(DatabaseModel):
     description = db.Column(db.String)
     protocol_key = db.Column(db.String)
 
+
 class DefaultMeta(DatabaseModel):
     __tablename__ = "defaultmetas"
     __model_name__ = "defaultmeta"
@@ -770,6 +868,7 @@ class DefaultMeta(DatabaseModel):
     def get_parent(self):
         return self.protocoltype
 
+
 class Meta(DatabaseModel):
     __tablename__ = "metas"
     __model_name__ = "meta"
@@ -782,30 +881,42 @@ class Meta(DatabaseModel):
     def get_parent(self):
         return self.protocol
 
+
 class Like(DatabaseModel):
     __tablename__ = "likes"
     __model_name__ = "like"
     id = db.Column(db.Integer, primary_key=True)
     who = db.Column(db.String)
 
+
 class LikeProtocolAssociation(DatabaseModel):
     __tablename__ = "likeprotocolassociations"
-    like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
-    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"), primary_key=True)
+    like_id = db.Column(
+        db.Integer, db.ForeignKey("likes.id"), primary_key=True)
+    protocol_id = db.Column(
+        db.Integer, db.ForeignKey("protocols.id"), primary_key=True)
+
 
 class LikeTodoAssociation(DatabaseModel):
     __tablename__ = "liketodoassociations"
-    like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
-    todo_id = db.Column(db.Integer, db.ForeignKey("todos.id"), primary_key=True)
+    like_id = db.Column(
+        db.Integer, db.ForeignKey("likes.id"), primary_key=True)
+    todo_id = db.Column(
+        db.Integer, db.ForeignKey("todos.id"), primary_key=True)
+
 
 class LikeDecisionAssociation(DatabaseModel):
     __tablename__ = "likedecisionassociations"
-    like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
-    decision_id = db.Column(db.Integer, db.ForeignKey("decisions.id"), primary_key=True)
+    like_id = db.Column(
+        db.Integer, db.ForeignKey("likes.id"), primary_key=True)
+    decision_id = db.Column(
+        db.Integer, db.ForeignKey("decisions.id"), primary_key=True)
+
 
 class LikeTOPAssociation(DatabaseModel):
     __tablename__ = "liketopassociations"
-    like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
+    like_id = db.Column(
+        db.Integer, db.ForeignKey("likes.id"), primary_key=True)
     top_id = db.Column(db.Integer, db.ForeignKey("tops.id"), primary_key=True)
 
 
diff --git a/protoparser.py b/protoparser.py
index bee63917caf77d0178d08d90adef684569bcaad7..5bcba3d38c475e6f0f9311fbed5dcec501b4fb56 100644
--- a/protoparser.py
+++ b/protoparser.py
@@ -10,10 +10,11 @@ import config
 
 INDENT_LETTER = "-"
 
+
 class ParserException(Exception):
     name = "Parser Exception"
     has_explanation = False
-    #explanation = "The source did generally not match the expected protocol syntax."
+
     def __init__(self, message, linenumber=None, tree=None):
         self.message = message
         self.linenumber = linenumber
@@ -22,13 +23,15 @@ class ParserException(Exception):
     def __str__(self):
         result = ""
         if self.linenumber is not None:
-            result = "Exception at line {}: {}".format(self.linenumber, self.message)
+            result = "Exception at line {}: {}".format(
+                self.linenumber, self.message)
         else:
             result = "Exception: {}".format(self.message)
         if self.has_explanation:
             result += "\n" + self.explanation
         return result
 
+
 class RenderType(Enum):
     latex = 0
     wikitext = 1
@@ -36,8 +39,12 @@ class RenderType(Enum):
     html = 3
     dokuwiki = 4
 
+
 def _not_implemented(self, render_type):
-    return NotImplementedError("The rendertype {} has not been implemented for {}.".format(render_type.name, self.__class__.__name__))
+    return NotImplementedError(
+        "The rendertype {} has not been implemented for {}.".format(
+            render_type.name, self.__class__.__name__))
+
 
 class Element:
     """
@@ -63,13 +70,15 @@ class Element:
         Parses a match of this elements pattern.
         Arguments:
         - match: the match of this elements pattern
-        - current: the current element of the document. Should be a fork. May be modified.
+        - current: the current element of the document. Should be a fork.
+            May be modified.
         - linenumber: the current line number, for error messages
         Returns:
         - the new current element
         - the line number after parsing this element
         """
-        raise ParserException("Trying to parse the generic base element!", linenumber)
+        raise ParserException(
+            "Trying to parse the generic base element!", linenumber)
 
     @staticmethod
     def parse_inner(match, current, linenumber=None):
@@ -104,7 +113,8 @@ class Element:
             element.fork = current
             return current
 
-    PATTERN = r"x(?<!x)" # yes, a master piece, but it should never be called
+    PATTERN = r"x(?<!x)"
+
 
 class Content(Element):
     def __init__(self, children, linenumber):
@@ -112,7 +122,9 @@ class Content(Element):
         self.linenumber = linenumber
 
     def render(self, render_type, show_private, level=None, protocol=None):
-        return "".join(map(lambda e: e.render(render_type, show_private, level=level, protocol=protocol), self.children))
+        return "".join(map(lambda e: e.render(
+            render_type, show_private, level=level, protocol=protocol),
+            self.children))
 
     def dump(self, level=None):
         if level is None:
@@ -123,14 +135,18 @@ class Content(Element):
         return "\n".join(result_lines)
 
     def get_tags(self, tags):
-        tags.extend([child for child in self.children if isinstance(child, Tag)])
+        tags.extend([
+            child for child in self.children
+            if isinstance(child, Tag)
+        ])
         return tags
 
     @staticmethod
     def parse(match, current, linenumber=None):
         linenumber = Element.parse_inner(match, current, linenumber)
         if match.group("content") is None:
-            raise ParserException("Content is missing its content!", linenumber)
+            raise ParserException(
+                "Content is missing its content!", linenumber)
         content = match.group("content")
         element = Content.from_content(content, current, linenumber)
         if len(content) == 0:
@@ -147,23 +163,22 @@ class Content(Element):
                 match = pattern.match(content)
                 if match is not None:
                     matched = True
-                    children.append(TEXT_PATTERNS[pattern](match, current, linenumber))
+                    children.append(TEXT_PATTERNS[pattern](
+                        match, current, linenumber))
                     content = content[len(match.group()):]
                     break
             if not matched:
-                raise ParserException("Dies ist kein valider Tag! (mögliche Tags sind: {})", linenumber, ", ".join(Tag.KNOWN_TAGS))
+                raise ParserException(
+                    "Dies ist kein valider Tag! "
+                    "(mögliche Tags sind: {})".format(
+                        ", ".join(Tag.KNOWN_TAGS)),
+                    linenumber)
         return Content(children, linenumber)
 
-    # v1: has problems with missing semicolons
-    #PATTERN = r"\s*(?<content>(?:[^\[\];]+)?(?:\[[^\]]+\][^;\[\]]*)*);"
-    # v2: does not require the semicolon, but the newline
-    #PATTERN = r"\s*(?<content>(?:[^\[\];\r\n]+)?(?:\[[^\]\r\n]+\][^;\[\]\r\n]*)*);?"
-    # v3: does not allow braces in the content
-    #PATTERN = r"\s*(?<content>(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)*);?"
-    # v4: do not allow empty match (require either the first or the second part to be non-empty)
-    PATTERN = r"\s*(?<content>(?:(?:[^\[\];\r\n{}]+)|(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)+));?"
-    # v5: do match emptystring if followed by a semi colon
-    #PATTERN = r"\s*(?<content>(?:[^\[\];\r\n{}]+);?|(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)+;?|;)"
+    PATTERN = (
+        r"\s*(?<content>(?:(?:[^\[\];\r\n{}]+)|(?:[^\[\];\r\n{}]+)?"
+        r"(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)+));?")
+
 
 class Text:
     def __init__(self, text, linenumber, fork):
@@ -199,9 +214,6 @@ class Text:
             raise ParserException("Text is empty!", linenumber)
         return Text(content, linenumber, current)
 
-    # v1: does not allow any [, as that is part of a tag
-    # PATTERN = r"(?<text>[^\[]+)(?:(?=\[)|$)"
-    # v2: does allow one [ at the beginning, which is used if it did not match a tag
     PATTERN = r"(?<text>\[?[^\[{}]+)(?:(?=\[)|$)"
 
 
@@ -222,12 +234,16 @@ class Tag:
                 return self.todo.render_latex(current_protocol=protocol)
             elif self.name == "beschluss":
                 if len(self.decision.categories):
-                    return r"\Beschluss[{}]{{{}}}".format(self.decision.get_categories_str(),self.decision.content)
+                    return r"\Beschluss[{}]{{{}}}".format(
+                        escape_tex(self.decision.get_categories_str()),
+                        escape_tex(self.decision.content))
                 else:
                     return r"\Beschluss{{{}}}".format(self.decision.content)
             elif self.name == "footnote":
                 return r"\footnote{{{}}}".format(self.values[0])
-            return r"\textbf{{{}:}} {}".format(escape_tex(self.name.capitalize()), escape_tex(";".join(self.values)))
+            return r"\textbf{{{}:}} {}".format(
+                escape_tex(self.name.capitalize()),
+                escape_tex(";".join(self.values)))
         elif render_type == RenderType.plaintext:
             if self.name == "url":
                 return self.values[0]
@@ -237,7 +253,8 @@ class Tag:
                 return self.values[0]
             elif self.name == "footnote":
                 return "[^]({})".format(self.values[0])
-            return "{}: {}".format(self.name.capitalize(), ";".join(self.values))
+            return "{}: {}".format(
+                self.name.capitalize(), ";".join(self.values))
         elif render_type == RenderType.wikitext:
             if self.name == "url":
                 return "[{0} {0}]".format(self.values[0])
@@ -247,7 +264,8 @@ class Tag:
                 return self.todo.render_wikitext(current_protocol=protocol)
             elif self.name == "footnote":
                 return "<ref>{}</ref>".format(self.values[0])
-            return "'''{}:''' {}".format(self.name.capitalize(), ";".join(self.values))
+            return "'''{}:''' {}".format(
+                self.name.capitalize(), ";".join(self.values))
         elif render_type == RenderType.html:
             if self.name == "url":
                 return "<a href=\"{0}\">{0}</a>".format(self.values[0])
@@ -268,8 +286,9 @@ class Tag:
                 else:
                     return "<b>Beschluss:</b> {}".format(self.values[0])
             elif self.name == "footnote":
-                return '<sup id="#fnref{0}"><a href="#fn{0}">Fn</a></sup>'.format(
-                    footnote_hash(self.values[0]))
+                return (
+                    '<sup id="#fnref{0}"><a href="#fn{0}">Fn</a></sup>'.format(
+                        footnote_hash(self.values[0])))
             return "[{}: {}]".format(self.name, ";".join(self.values))
         elif render_type == RenderType.dokuwiki:
             if self.name == "url":
@@ -277,21 +296,24 @@ class Tag:
             elif self.name == "todo":
                 if not show_private:
                     return ""
-                return self.todo.render_wikitext(current_protocol=protocol,
-                    use_dokuwiki=True)
+                return self.todo.render_wikitext(
+                    current_protocol=protocol, use_dokuwiki=True)
             elif self.name == "beschluss":
-                return "**{}:** {}".format(self.name.capitalize(), ";".join(self.values))
+                return "**{}:** {}".format(
+                    self.name.capitalize(), ";".join(self.values))
             elif self.name == "footnote":
                 return "(({}))".format(self.values[0])
             else:
-                return "**{}:** {}".format(self.name.capitalize(), ";".join(self.values))
+                return "**{}:** {}".format(
+                    self.name.capitalize(), ";".join(self.values))
         else:
             raise _not_implemented(self, render_type)
 
     def dump(self, level=None):
         if level is None:
             level = 0
-        return "{}tag: {}: {}".format(INDENT_LETTER * level, self.name, "; ".join(self.values))
+        return "{}tag: {}: {}".format(
+            INDENT_LETTER * level, self.name, "; ".join(self.values))
 
     @staticmethod
     def parse(match, current, linenumber):
@@ -302,12 +324,7 @@ class Tag:
             raise ParserException("Tag is empty!", linenumber)
         parts = content.split(";")
         return Tag(parts[0], parts[1:], linenumber, current)
-    
-    # v1: matches [text without semicolons]
-    #PATTERN = r"\[(?<content>(?:[^;\]]*;)*(?:[^;\]]*))\]"
-    # v2: needs at least two parts separated by a semicolon
-    #PATTERN = r"\[(?<content>(?:[^;\]]*;)+(?:[^;\]]*))\]"
-    # v3: also match [] without semicolons inbetween, as there is not other use for that
+
     PATTERN = r"\[(?<content>[^\]]*)\]"
 
     KNOWN_TAGS = ["todo", "url", "beschluss", "footnote", "sitzung"]
@@ -332,6 +349,7 @@ class Empty(Element):
 
     PATTERN = r"(?:\s+|;)"
 
+
 class Remark(Element):
     def __init__(self, name, value, linenumber):
         self.name = name
@@ -351,12 +369,12 @@ class Remark(Element):
             return r"{}: {}\\".format(self.name, self.value)
         else:
             raise _not_implemented(self, render_type)
-            
 
     def dump(self, level=None):
         if level is None:
             level = 0
-        return "{}remark: {}: {}".format(INDENT_LETTER * level, self.name, self.value)
+        return "{}remark: {}: {}".format(
+            INDENT_LETTER * level, self.name, self.value)
 
     def get_tags(self, tags):
         return tags
@@ -377,10 +395,11 @@ class Remark(Element):
 
     PATTERN = r"\s*\#(?<content>[^\n]+)"
 
+
 class Fork(Element):
     def __init__(self, is_top, name, parent, linenumber, children=None):
         self.is_top = is_top
-        self.name = name.strip() if (name is not None and len(name) > 0) else None
+        self.name = name.strip() if name else None
         self.parent = parent
         self.linenumber = linenumber
         self.children = [] if children is None else children
@@ -388,7 +407,12 @@ class Fork(Element):
     def dump(self, level=None):
         if level is None:
             level = 0
-        result_lines = ["{}fork: {}'{}'".format(INDENT_LETTER * level, "TOP " if self.is_top else "", self.name)]
+        result_lines = [
+            "{}fork: {}'{}'".format(
+                INDENT_LETTER * level,
+                "TOP " if self.is_top else "",
+                self.name)
+        ]
         for child in self.children:
             result_lines.append(child.dump(level + 1))
         return "\n".join(result_lines)
@@ -408,7 +432,9 @@ class Fork(Element):
             end_line = r"\end{itemize}"
             content_parts = []
             for child in self.children:
-                part = child.render(render_type, show_private, level=level+1, protocol=protocol)
+                part = child.render(
+                    render_type, show_private, level=level + 1,
+                    protocol=protocol)
                 if len(part.strip()) == 0:
                     continue
                 if not part.startswith(r"\item"):
@@ -421,27 +447,36 @@ class Fork(Element):
                 return "\n".join([begin_line, content_lines, end_line])
             elif self.test_private(self.name):
                 if show_private:
-                    return (r"\begin{tcolorbox}[breakable,title=Interner Abschnitt]" + "\n"
+                    return (r"\begin{tcolorbox}[breakable,title=Interner "
+                            r"Abschnitt]" + "\n"
                             + r"\begin{itemize}" + "\n"
                             + content_lines + "\n"
                             + r"\end{itemize}" + "\n"
                             + r"\end{tcolorbox}")
                 else:
-                    return r"\textit{[An dieser Stelle wurde intern protokolliert.]}"
+                    return (r"\textit{[An dieser Stelle wurde intern "
+                            r"protokolliert.]}")
             else:
-                return "\n".join([escape_tex(name_line), begin_line, content_lines, end_line])
-        elif render_type == RenderType.wikitext or render_type == RenderType.dokuwiki:
+                return "\n".join([
+                    escape_tex(name_line), begin_line,
+                    content_lines, end_line
+                ])
+        elif (render_type == RenderType.wikitext
+                or render_type == RenderType.dokuwiki):
             equal_signs = level + 2
             if render_type == RenderType.dokuwiki:
                 equal_signs = 6 - level
             title_line = "{0} {1} {0}".format("=" * equal_signs, name_line)
             content_parts = []
             for child in self.children:
-                part = child.render(render_type, show_private, level=level+1, protocol=protocol)
+                part = child.render(
+                    render_type, show_private, level=level + 1,
+                    protocol=protocol)
                 if len(part.strip()) == 0:
                     continue
                 content_parts.append(part)
-            content_lines = "{}\n\n{}\n".format(title_line, "\n\n".join(content_parts))
+            content_lines = "{}\n\n{}\n".format(
+                title_line, "\n\n".join(content_parts))
             if self.test_private(self.name) and not show_private:
                 return ""
             else:
@@ -450,11 +485,14 @@ class Fork(Element):
             title_line = "{} {}".format("#" * (level + 1), name_line)
             content_parts = []
             for child in self.children:
-                part = child.render(render_type, show_private, level=level+1, protocol=protocol)
+                part = child.render(
+                    render_type, show_private, level=level + 1,
+                    protocol=protocol)
                 if len(part.strip()) == 0:
                     continue
                 content_parts.append(part)
-            content_lines = "{}\n{}".format(title_line, "\n".join(content_parts))
+            content_lines = "{}\n{}".format(
+                title_line, "\n".join(content_parts))
             if self.test_private(self.name) and not show_private:
                 return ""
             else:
@@ -463,22 +501,29 @@ class Fork(Element):
             depth = level + 1 + getattr(config, "HTML_LEVEL_OFFSET", 0)
             content_lines = ""
             if depth < 5:
-                title_line = "<h{depth}>{content}</h{depth}>".format(depth=depth, content=name_line)
+                title_line = "<h{depth}>{content}</h{depth}>".format(
+                    depth=depth, content=name_line)
                 content_parts = []
                 for child in self.children:
-                    part = child.render(render_type, show_private, level=level+1, protocol=protocol)
+                    part = child.render(
+                        render_type, show_private, level=level + 1,
+                        protocol=protocol)
                     if len(part.strip()) == 0:
                         continue
                     content_parts.append("<p>{}</p>".format(part))
-                content_lines = "{}\n\n{}".format(title_line, "\n".join(content_parts))
+                content_lines = "{}\n\n{}".format(
+                    title_line, "\n".join(content_parts))
             else:
                 content_parts = []
                 for child in self.children:
-                    part = child.render(render_type, show_private, level=level+1, protocol=protocol)
+                    part = child.render(
+                        render_type, show_private, level=level + 1,
+                        protocol=protocol)
                     if len(part.strip()) == 0:
                         continue
                     content_parts.append("<li>{}</li>".format(part))
-                content_lines = "{}\n<ul>\n{}\n</ul>".format(name_line, "\n".join(content_parts))
+                content_lines = "{}\n<ul>\n{}\n</ul>".format(
+                    name_line, "\n".join(content_parts))
             if self.test_private(self.name) and not show_private:
                 return ""
             else:
@@ -486,7 +531,6 @@ class Fork(Element):
         else:
             raise _not_implemented(self, render_type)
 
-
     def get_tags(self, tags=None):
         if tags is None:
             tags = []
@@ -495,7 +539,7 @@ class Fork(Element):
         return tags
 
     def is_anonymous(self):
-        return self.name == None
+        return self.name is None
 
     def is_root(self):
         return self.parent is None
@@ -509,7 +553,8 @@ class Fork(Element):
         if self.is_root():
             return 1
         top = self.get_top()
-        tops = [child
+        tops = [
+            child
             for child in top.parent.children
             if isinstance(child, Fork)
         ]
@@ -559,23 +604,19 @@ class Fork(Element):
     def parse_end(match, current, linenumber=None):
         linenumber = Element.parse_inner(match, current, linenumber)
         if current.is_root():
-            raise ParserException("Found end tag for root element!", linenumber)
+            raise ParserException(
+                "Found end tag for root element!", linenumber)
         current = current.parent
         return current, linenumber
 
     def append(self, element):
         self.children.append(element)
 
-    # v1: has a problem with old protocols that do not use a lot of semicolons
-    #PATTERN = r"\s*(?<name1>[^{};]+)?{(?<environment>\S+)?\h*(?<name2>[^\n]+)?"
-    # v2: do not allow newlines in name1 or semicolons in name2
-    #PATTERN = r"\s*(?<name1>[^{};\n]+)?{(?<environment>[^\s{};]+)?\h*(?<name2>[^;{}\n]+)?"
-    # v3: no environment/name2 for normal lists, only for tops
-    #PATTERN = r"\s*(?<name>[^{};\n]+)?{(?:TOP\h*(?<topname>[^;{}\n]+))?"
-    # v4: do allow one newline between name and {
-    PATTERN = r"\s*(?<name>(?:[^{};\n])+)?\n?\s*{(?:TOP\h*(?<topname>[^;{}\n]+))?"
+    PATTERN = (
+        r"\s*(?<name>(?:[^{};\n])+)?\n?\s*{(?:TOP\h*(?<topname>[^;{}\n]+))?")
     END_PATTERN = r"\s*};?"
 
+
 PATTERNS = OrderedDict([
     (re.compile(Fork.PATTERN), Fork.parse),
     (re.compile(Fork.END_PATTERN), Fork.parse_end),
@@ -589,6 +630,7 @@ TEXT_PATTERNS = OrderedDict([
     (re.compile(Text.PATTERN), Text.parse)
 ])
 
+
 def parse(source):
     linenumber = 1
     tree = Fork.create_root()
@@ -600,18 +642,24 @@ def parse(source):
             if match is not None:
                 source = source[len(match.group()):]
                 try:
-                    current, linenumber = PATTERNS[pattern](match, current, linenumber)
+                    current, linenumber = PATTERNS[pattern](
+                        match, current, linenumber)
                 except ParserException as exc:
                     exc.tree = tree
                     raise exc
                 found = True
                 break
         if not found:
-            raise ParserException("No matching syntax element found!", linenumber, tree=tree)
+            raise ParserException(
+                "No matching syntax element found!", linenumber, tree=tree)
     if current is not tree:
-        raise ParserException("Du hast vergessen, Klammern zu schließen! (die öffnende ist in Zeile {})".format(current.linenumber), linenumber=current.linenumber, tree=tree)
+        raise ParserException(
+            "Du hast vergessen, Klammern zu schließen! (die öffnende ist in "
+            "Zeile {})".format(
+                current.linenumber), linenumber=current.linenumber, tree=tree)
     return tree
 
+
 def main(test_file_name=None):
     source = ""
     test_file_name = test_file_name or "source0"
@@ -624,7 +672,7 @@ def main(test_file_name=None):
         print(e)
     else:
         print("worked!")
-    
+
 
 if __name__ == "__main__":
     test_file_name = sys.argv[1] if len(sys.argv) > 1 else None
diff --git a/server.py b/server.py
index 1c594f1a3cf2625f05e45dec8f642ca443f75bb3..505dd067f93d592d59724c0c771f424c4c2f4fe5 100755
--- a/server.py
+++ b/server.py
@@ -2,35 +2,60 @@
 import locale
 locale.setlocale(locale.LC_TIME, "de_DE.utf8")
 
-from flask import Flask, g, current_app, request, session, flash, redirect, url_for, abort, render_template, Response, send_file as flask_send_file, Markup
+from flask import (
+    Flask, request, session, flash, redirect,
+    url_for, abort, render_template, Response, Markup)
 from werkzeug.utils import secure_filename
 from flask_script import Manager, prompt
 from flask_migrate import Migrate, MigrateCommand
-#from flask_socketio import SocketIO
 from celery import Celery
-from sqlalchemy import or_, and_
+from sqlalchemy import or_
 from apscheduler.schedulers.background import BackgroundScheduler
 from apscheduler.triggers.cron import CronTrigger
-from apscheduler.triggers.interval import IntervalTrigger
 import atexit
 import feedgen.feed
 import icalendar
-from io import StringIO, BytesIO
+from io import BytesIO
 import os
-from datetime import datetime, time, timedelta
+from datetime import datetime, timedelta
 import math
 import mimetypes
-import subprocess
-from dateutil import tz
 
 import config
-from shared import db, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, time_filter_short, user_manager, security_manager, current_user, check_login, login_required, group_required, class_filter, needs_date_test, todostate_name_filter, code_filter, indent_tab_filter
-from utils import is_past, mail_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg, fancy_join, footnote_hash
-from decorators import db_lookup, require_public_view_right, require_private_view_right, require_modify_right, require_publish_right, require_admin_right
-from models.database import ProtocolType, Protocol, DefaultTOP, TOP, LocalTOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta, DecisionCategory, Like
-from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, generate_protocol_form, TopForm, LocalTopForm, SearchForm, DecisionSearchForm, ProtocolSearchForm, TodoSearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm, DecisionCategoryForm, DocumentEditForm
-from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable, DefaultMetasTable, DecisionCategoriesTable
+from shared import (
+    db, date_filter, datetime_filter, date_filter_long,
+    date_filter_short, time_filter, time_filter_short, user_manager,
+    security_manager, current_user, check_login, login_required,
+    class_filter, needs_date_test, todostate_name_filter,
+    code_filter, indent_tab_filter)
+from utils import (
+    get_first_unused_int, get_etherpad_text, split_terms, optional_int_arg,
+    fancy_join, footnote_hash, get_git_revision, get_max_page_length_exp,
+    get_internal_filename, get_csrf_token)
+from decorators import (
+    db_lookup, protect_csrf,
+    require_private_view_right, require_modify_right, require_publish_right,
+    require_admin_right)
+from models.database import (
+    ProtocolType, Protocol, DefaultTOP, TOP, LocalTOP,
+    Document, Todo, Decision, MeetingReminder, Error, TodoMail,
+    DecisionDocument, TodoState, DefaultMeta, DecisionCategory, Like)
+from views.forms import (
+    LoginForm, ProtocolTypeForm, DefaultTopForm,
+    MeetingReminderForm, NewProtocolForm, DocumentUploadForm,
+    KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm,
+    generate_protocol_form, TopForm, LocalTopForm,
+    DecisionSearchForm, ProtocolSearchForm, TodoSearchForm,
+    NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm,
+    DefaultMetaForm, MergeTodosForm, DecisionCategoryForm,
+    DocumentEditForm)
+from views.tables import (
+    ProtocolsTable, ProtocolTypesTable,
+    ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable,
+    TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable,
+    TodoMailsTable, DefaultMetasTable, DecisionCategoriesTable)
 from legacy import import_old_todos, import_old_protocols, import_old_todomails
+import back
 
 app = Flask(__name__)
 app.config.from_object(config)
@@ -39,16 +64,14 @@ migrate = Migrate(app, db)
 manager = Manager(app)
 manager.add_command("db", MigrateCommand)
 
+
 def make_celery(app, config):
     celery = Celery(app.import_name, broker=config.CELERY_BROKER_URL)
     celery.conf.update(app.config)
     return celery
-celery = make_celery(app, config)
 
-#def make_socketio(app, config):
-#    socketio = SocketIO(app)
-#    return socketio
-#socketio = make_socketio(app, config)
+
+celery = make_celery(app, config)
 
 app.jinja_env.trim_blocks = True
 app.jinja_env.lstrip_blocks = True
@@ -58,7 +81,6 @@ app.jinja_env.filters["timify"] = time_filter
 app.jinja_env.filters["timify_short"] = time_filter_short
 app.jinja_env.filters["datify_short"] = date_filter_short
 app.jinja_env.filters["datify_long"] = date_filter_long
-#app.jinja_env.filters["url_complete"] = url_manager.complete
 app.jinja_env.filters["class"] = class_filter
 app.jinja_env.filters["todo_get_name"] = todostate_name_filter
 app.jinja_env.filters["code"] = code_filter
@@ -67,12 +89,13 @@ app.jinja_env.filters["fancy_join"] = fancy_join
 app.jinja_env.filters["footnote_hash"] = footnote_hash
 app.jinja_env.tests["auth_valid"] = security_manager.check_user
 app.jinja_env.tests["needs_date"] = needs_date_test
+app.jinja_env.globals["get_csrf_token"] = get_csrf_token
 
 additional_templates = getattr(config, "LATEX_LOCAL_TEMPLATES", None)
 if additional_templates is not None and os.path.isdir(additional_templates):
     if additional_templates not in app.jinja_loader.searchpath:
         app.jinja_loader.searchpath.append(additional_templates)
-    
+
 
 import tasks
 
@@ -83,32 +106,20 @@ app.jinja_env.globals.update(min=min)
 app.jinja_env.globals.update(max=max)
 app.jinja_env.globals.update(dir=dir)
 app.jinja_env.globals.update(now=datetime.now)
+app.jinja_env.globals["git_revision"] = get_git_revision()
 
-def get_git_revision():
-    gitlab_url = "https://git.fsmpi.rwth-aachen.de/protokollsystem/proto3"
-    commit_hash = subprocess.check_output(["git", "log", "-g", "-1", "--pretty=%H"]).decode("UTF-8").strip()
-    timestamp = int(subprocess.check_output(["git", "log", "-g", "-1", "--pretty=%at"]).strip())
-    commit_date = datetime.fromtimestamp(timestamp)
-    return {"url": gitlab_url, "hash": commit_hash, "date": commit_date}
-
-try:
-    app.jinja_env.globals["git_revision"] = get_git_revision()
-except:
-    pass
-
-# blueprints here
 
 @manager.command
 def import_legacy():
     """Import the old todos and protocols from an sql dump"""
     filename = prompt("SQL-file")
-    #filename = "legacy.sql"
     with open(filename, "rb") as sqlfile:
         content = sqlfile.read().decode("utf-8")
         import_old_todos(content)
         import_old_protocols(content)
         import_old_todomails(content)
 
+
 @manager.command
 def recompile_all():
     for protocol in sorted(Protocol.query.all(), key=lambda p: p.date):
@@ -116,8 +127,9 @@ def recompile_all():
             print(protocol.get_short_identifier())
             tasks.parse_protocol(protocol)
 
+
 @manager.command
-def merge_todos():
+def merge_duplicate_todos():
     todo_by_id = {}
     todos = Todo.query.all()
     for todo in todos:
@@ -138,33 +150,42 @@ def merge_todos():
         else:
             todo_by_id[todo_id] = todo
 
+
 @manager.command
 def runserver():
     app.run()
     make_scheduler()
 
-# cause uwsgi currently has a bug
+
 def send_file(file_like, cache_timeout, as_attachment, attachment_filename):
+    """
+    Replaces flask.send_file since that uses an uwsgi function that is buggy.
+    """
     mimetype, _ = mimetypes.guess_type(attachment_filename)
     response = Response(file_like.read(), mimetype)
     if as_attachment:
-        response.headers["Content-Disposition"] = 'attachment; filename="{}"'.format(attachment_filename)
+        response.headers["Content-Disposition"] = (
+            'attachment; filename="{}"'.format(attachment_filename))
     content_type = mimetype
     if mimetype.startswith("text/"):
         content_type = "{}; charset=utf-8".format(content_type)
     response.headers["Content-Type"] = content_type
-    response.headers["Cache-Control"] = "public, max-age={}".format(cache_timeout)
+    response.headers["Cache-Control"] = (
+        "public, max-age={}".format(cache_timeout))
     response.headers["Connection"] = "close"
     return response
 
+
 @app.route("/")
+@back.anchor
 def index():
     user = current_user()
     protocols = [
         protocol for protocol in Protocol.query.all()
-        if protocol.protocoltype.has_public_view_right(user,
-            check_networks=False)
+        if protocol.protocoltype.has_public_view_right(
+            user, check_networks=False)
     ]
+
     def _protocol_sort_key(protocol):
         if protocol.date is not None:
             return protocol.date
@@ -183,8 +204,10 @@ def index():
         [
             protocol for protocol in protocols
             if protocol.done and protocol.public
-            and (protocol.has_private_view_right(user)
-                or protocol.protocoltype.has_public_view_right(user, check_networks=False))
+            and (
+                protocol.has_private_view_right(user)
+                or protocol.protocoltype.has_public_view_right(
+                    user, check_networks=False))
         ],
         key=_protocol_sort_key,
         reverse=True
@@ -195,7 +218,8 @@ def index():
     if len(finished_protocols) > 0:
         protocol = finished_protocols[0]
         show_private = protocol.has_private_view_right(user)
-        has_public_view_right = protocol.protocoltype.has_public_view_right(user)
+        has_public_view_right = (
+            protocol.protocoltype.has_public_view_right(user))
     todos = None
     if check_login():
         todos = [
@@ -205,87 +229,94 @@ def index():
         ]
         user_todos = [
             todo for todo in todos
-            if user.username.lower() in list(map(str.strip, todo.who.lower().split(",")))
+            if user.username.lower()
+            in list(map(str.strip, todo.who.lower().split(",")))
         ]
         if len(user_todos) > 0:
             todos = user_todos
+
         def _todo_sort_key(todo):
             protocol = todo.get_first_protocol()
-            return protocol.date if protocol is not None and protocol.date is not None else datetime.now().date()
+            if protocol is not None and protocol.date is not None:
+                return protocol.date
+            return datetime.now().date()
         todos = sorted(todos, key=_todo_sort_key, reverse=True)
-    todos_table = TodosTable(todos) if todos is not None else None
-    return render_template("index.html", open_protocols=open_protocols, protocol=protocol, todos=todos, show_private=show_private, has_public_view_right=has_public_view_right)
+    return render_template(
+        "index.html", open_protocols=open_protocols,
+        protocol=protocol, todos=todos, show_private=show_private,
+        has_public_view_right=has_public_view_right)
 
 @app.route("/documentation")
+@back.anchor
 @login_required
 def documentation():
     return render_template(
         "documentation.html")
 
 @app.route("/documentation/sessionmanagement")
-# @back.anchor
+@back.anchor
 @login_required
 def sessionmanagement_documentation():
     return render_template(
         "documentation-sessionmanagement.html")
 
 @app.route("/documentation/sessionmanagement/plan")
-# @back.anchor
+back.anchor
 @login_required
 def plan_sessionmanagement_documentation():
     return render_template(
         "documentation-sessionmanagement-plan.html")
 
 @app.route("/documentation/sessionmanagement/write")
-# @back.anchor
+@back.anchor
 @login_required
 def write_sessionmanagement_documentation():
     return render_template(
         "documentation-sessionmanagement-write.html")
 
 @app.route("/documentation/sessionmanagement/tracking")
-# @back.anchor
+@back.anchor
 @login_required
 def tracking_sessionmanagement_documentation():
     return render_template(
         "documentation-sessionmanagement-tracking.html")
 
 @app.route("/documentation/syntax")
-# @back.anchor
+@back.anchor
 @login_required
 def syntax_documentation():
     return render_template(
         "documentation-syntax.html")
 
 @app.route("/documentation/syntax/meta")
-# @back.anchor
+@back.anchor
 @login_required
 def meta_syntax_documentation():
     return render_template(
         "documentation-syntax-meta.html")
 
 @app.route("/documentation/syntax/top")
-# @back.anchor
+@back.anchor
 @login_required
 def top_syntax_documentation():
     return render_template(
         "documentation-syntax-top.html")
 
 @app.route("/documentation/syntax/lists")
-# @back.anchor
+@back.anchor
 @login_required
 def lists_syntax_documentation():
     return render_template("documentation-syntax-lists.html")
 
 @app.route("/documentation/syntax/internal")
-# @back.anchor
+@back.anchor
 @login_required
 def internal_syntax_documentation():
     return render_template(
         "documentation-syntax-internal.html")
 
 @app.route("/documentation/syntax/tags")
-# @back.anchor
+@back.anchor
 @login_required
 def tags_syntax_documentation():
     todostates = list(TodoState)
@@ -295,39 +326,41 @@ def tags_syntax_documentation():
         name_to_state=name_to_state)
 
 @app.route("/documentation/configuration")
-# @back.anchor
+@back.anchor
 @login_required
 def configuration_documentation():
     return render_template(
         "documentation-configuration.html")
 
 @app.route("/documentation/configuration/types")
-# @back.anchor
+@back.anchor
 @login_required
 def types_configuration_documentation():
     return render_template(
         "documentation-configuration-types.html")
 
 @app.route("/documentation/configuration/todomails")
-# @back.anchor
+@back.anchor
 @login_required
 def todomails_configuration_documentation():
     return render_template(
         "documentation-configuration-todomails.html")
 
 @app.route("/types/list")
+@back.anchor
 @login_required
 def list_types():
-    is_logged_in = check_login()
     user = current_user()
     types = [
         protocoltype for protocoltype in ProtocolType.query.all()
         if (protocoltype.has_private_view_right(user)
-        or protocoltype.has_public_view_right(user)
-        or protocoltype.is_public)]
+            or protocoltype.has_public_view_right(user)
+            or protocoltype.is_public)]
     types = sorted(types, key=lambda t: t.short_name)
     types_table = ProtocolTypesTable(types)
-    return render_template("types-list.html", types=types, types_table=types_table)
+    return render_template(
+        "types-list.html", types=types, types_table=types_table)
+
 
 @app.route("/type/new", methods=["GET", "POST"])
 @login_required
@@ -336,16 +369,19 @@ def new_type():
     if form.validate_on_submit():
         user = current_user()
         if form.private_group.data not in user.groups:
-            flash("Du kannst keinen internen Protokolltypen anlegen, zu dem du selbst keinen Zugang hast.", "alert-error")
+            flash("Du kannst keinen internen Protokolltypen anlegen, "
+                  "zu dem du selbst keinen Zugang hast.", "alert-error")
         else:
             protocoltype = ProtocolType()
             form.populate_obj(protocoltype)
             db.session.add(protocoltype)
             db.session.commit()
-            flash("Der Protokolltyp {} wurde angelegt.".format(protocoltype.name), "alert-success")
-        return redirect(request.args.get("next") or url_for("list_types"))
+            flash("Der Protokolltyp {} wurde angelegt.".format(
+                protocoltype.name), "alert-success")
+        return back.redirect("list_types")
     return render_template("type-new.html", form=form)
 
+
 @app.route("/type/edit/<int:protocoltype_id>", methods=["GET", "POST"])
 @login_required
 @db_lookup(ProtocolType)
@@ -355,38 +391,54 @@ def edit_type(protocoltype):
     form = ProtocolTypeForm(obj=protocoltype)
     if form.validate_on_submit():
         if form.private_group.data not in user.groups:
-            flash("Du kannst keinen internen Protokolltypen anlegen, zu dem du selbst keinen Zugang hast.", "alert-error")
+            flash("Du kannst keinen internen Protokolltypen anlegen, "
+                  "zu dem du selbst keinen Zugang hast.", "alert-error")
         else:
             form.populate_obj(protocoltype)
             db.session.commit()
-            return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
-    return render_template("type-edit.html", form=form, protocoltype=protocoltype)
+            return back.redirect("show_type", protocoltype_id=protocoltype.id)
+    return render_template(
+        "type-edit.html", form=form, protocoltype=protocoltype)
+
 
 @app.route("/type/show/<int:protocoltype_id>")
+@back.anchor
 @login_required
 @db_lookup(ProtocolType)
 @require_private_view_right()
 def show_type(protocoltype):
     protocoltype_table = ProtocolTypeTable(protocoltype)
-    default_tops_table = DefaultTOPsTable(protocoltype.default_tops, protocoltype)
-    reminders_table = MeetingRemindersTable(protocoltype.reminders, protocoltype)
+    default_tops_table = DefaultTOPsTable(
+        protocoltype.default_tops, protocoltype)
+    reminders_table = MeetingRemindersTable(
+        protocoltype.reminders, protocoltype)
     metas_table = DefaultMetasTable(protocoltype.metas, protocoltype)
-    categories_table = DecisionCategoriesTable(protocoltype.decisioncategories, protocoltype)
-    return render_template("type-show.html", protocoltype=protocoltype, protocoltype_table=protocoltype_table, default_tops_table=default_tops_table, metas_table=metas_table, reminders_table=reminders_table, mail_active=config.MAIL_ACTIVE, categories_table=categories_table)
+    categories_table = DecisionCategoriesTable(
+        protocoltype.decisioncategories, protocoltype)
+    return render_template(
+        "type-show.html", protocoltype=protocoltype,
+        protocoltype_table=protocoltype_table,
+        default_tops_table=default_tops_table, metas_table=metas_table,
+        reminders_table=reminders_table, mail_active=config.MAIL_ACTIVE,
+        categories_table=categories_table)
+
 
 @app.route("/type/delete/<int:protocoltype_id>")
 @login_required
-@group_required(config.ADMIN_GROUP)
+@protect_csrf
 @db_lookup(ProtocolType)
+@require_admin_right()
 @require_modify_right()
 def delete_type(protocoltype):
     name = protocoltype.name
-    db.session.delete(protocoltype) 
+    db.session.delete(protocoltype)
     db.session.commit()
     flash("Der Protokolltype {} wurde gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("list_types"))
+    return back.redirect("list_types")
 
-@app.route("/type/reminders/new/<int:protocoltype_id>", methods=["GET", "POST"])
+
+@app.route("/type/reminders/new/<int:protocoltype_id>",
+           methods=["GET", "POST"])
 @login_required
 @db_lookup(ProtocolType)
 @require_modify_right()
@@ -397,10 +449,13 @@ def new_reminder(protocoltype):
         form.populate_obj(meetingreminder)
         db.session.add(meetingreminder)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
-    return render_template("reminder-new.html", form=form, protocoltype=protocoltype)
+        return back.redirect("show_type", protocoltype_id=protocoltype.id)
+    return render_template(
+        "reminder-new.html", form=form, protocoltype=protocoltype)
 
-@app.route("/type/reminder/edit/<int:meetingreminder_id>", methods=["GET", "POST"])
+
+@app.route("/type/reminder/edit/<int:meetingreminder_id>",
+           methods=["GET", "POST"])
 @login_required
 @db_lookup(MeetingReminder)
 @require_modify_right()
@@ -409,18 +464,23 @@ def edit_reminder(meetingreminder):
     if form.validate_on_submit():
         form.populate_obj(meetingreminder)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
-    return render_template("reminder-edit.html", form=form, meetingreminder=meetingreminder)
+        return back.redirect(
+            "show_type", protocoltype_id=meetingreminder.protocoltype.id)
+    return render_template(
+        "reminder-edit.html", form=form, meetingreminder=meetingreminder)
+
 
 @app.route("/type/reminder/delete/<int:meetingreminder_id>")
 @login_required
+@protect_csrf
 @db_lookup(MeetingReminder)
 @require_modify_right()
 def delete_reminder(meetingreminder):
     protocoltype = meetingreminder.protocoltype
     db.session.delete(meetingreminder)
     db.session.commit()
-    return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
+    return back.redirect("show_type", protocoltype_id=protocoltype.id)
+
 
 @app.route("/type/tops/new/<int:protocoltype_id>", methods=["GET", "POST"])
 @login_required
@@ -435,15 +495,20 @@ def new_default_top(protocoltype):
         db.session.commit()
         for protocol in protocoltype.protocols:
             if not protocol.done:
-                localtop = LocalTOP(protocol_id=protocol.id,
+                localtop = LocalTOP(
+                    protocol_id=protocol.id,
                     defaulttop_id=defaulttop.id, description="")
                 db.session.add(localtop)
         db.session.commit()
-        flash("Der Standard-TOP {} wurde für dem Protokolltyp {} hinzugefügt.".format(defaulttop.name, protocoltype.name), "alert-success")
-        return redirect(request.args.get("next") or url_for("index"))
-    return render_template("default-top-new.html", form=form, protocoltype=protocoltype)
+        flash("Der Standard-TOP {} wurde für dem Protokolltyp {} hinzugefügt."
+              .format(defaulttop.name, protocoltype.name), "alert-success")
+        return back.redirect()
+    return render_template(
+        "default-top-new.html", form=form, protocoltype=protocoltype)
 
-@app.route("/type/tops/edit/<int:protocoltype_id>/<int:defaulttop_id>", methods=["GET", "POST"])
+
+@app.route("/type/tops/edit/<int:protocoltype_id>/<int:defaulttop_id>",
+           methods=["GET", "POST"])
 @login_required
 @db_lookup(ProtocolType, DefaultTOP)
 @require_modify_right()
@@ -452,20 +517,27 @@ def edit_default_top(protocoltype, defaulttop):
     if form.validate_on_submit():
         form.populate_obj(defaulttop)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
-    return render_template("default-top-edit.html", form=form, protocoltype=protocoltype, defaulttop=defaulttop)
+        return back.redirect("show_type", protocoltype_id=protocoltype.id)
+    return render_template(
+        "default-top-edit.html", form=form,
+        protocoltype=protocoltype, defaulttop=defaulttop)
+
 
 @app.route("/type/tops/delete/<int:defaulttop_id>")
 @login_required
+@protect_csrf
 @db_lookup(DefaultTOP)
 @require_modify_right()
 def delete_default_top(defaulttop):
     db.session.delete(defaulttop)
     db.session.commit()
-    return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=defaulttop.protocoltype.id))
+    return back.redirect(
+        "show_type", protocoltype_id=defaulttop.protocoltype.id)
+
 
 @app.route("/type/tops/move/<int:defaulttop_id>/<diff>/")
 @login_required
+@protect_csrf
 @db_lookup(DefaultTOP)
 @require_modify_right()
 def move_default_top(defaulttop, diff):
@@ -474,13 +546,14 @@ def move_default_top(defaulttop, diff):
         db.session.commit()
     except ValueError:
         flash("Die angegebene Differenz ist keine Zahl.", "alert-error")
-    return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=defaulttop.protocoltype.id))
+    return back.redirect(
+        "show_type", protocoltype_id=defaulttop.protocoltype.id)
+
 
 @app.route("/protocols/list")
+@back.anchor
 def list_protocols():
-    is_logged_in = check_login()
     user = current_user()
-    protocoltype = None
     protocoltype_id = None
     try:
         protocoltype_id = int(request.args.get("protocoltype_id"))
@@ -492,11 +565,11 @@ def list_protocols():
     except (ValueError, TypeError):
         pass
     search_term = request.args.get("search")
-    protocoltypes = ProtocolType.get_public_protocoltypes(user, check_networks=False)
+    protocoltypes = ProtocolType.get_public_protocoltypes(
+        user, check_networks=False)
     search_form = ProtocolSearchForm(protocoltypes)
     if protocoltype_id is not None:
         search_form.protocoltype_id.data = protocoltype_id
-        protocoltype = ProtocolType.query.filter_by(id=protocoltype_id).first()
     if state_open is not None:
         search_form.state_open.data = state_open
     if search_term is not None:
@@ -513,14 +586,17 @@ def list_protocols():
             ))
     protocols = [
         protocol for protocol in protocol_query.all()
-        if protocol.protocoltype.has_public_view_right(user, check_networks=False)
+        if protocol.protocoltype.has_public_view_right(
+            user, check_networks=False)
     ]
+
     def _matches_search(content):
         content = content.lower()
         for search_term in search_terms:
             if search_term.lower() not in content:
                 return False
         return True
+
     def _matches_search_lazy(content):
         content = content.lower()
         for search_term in search_terms:
@@ -548,7 +624,7 @@ def list_protocols():
                 and _matches_search(protocol.content_public))
         ]
         for protocol in protocols:
-            content = protocol.content_private if protocol.protocoltype.has_private_view_right(user) else protocol.content_public
+            content = protocol.get_visible_content(user)
             lines = content.splitlines()
             matches = [line for line in lines if _matches_search_lazy(line)]
             formatted_lines = []
@@ -557,13 +633,18 @@ def list_protocols():
                 lower_line = line.lower()
                 last_index = 0
                 while last_index < len(line):
-                    index_candidates = list(filter(lambda t: t[0] != -1, 
-                        [(lower_line.find(term, last_index), term) for term in search_terms]))
+                    index_candidates = list(filter(
+                        lambda t: t[0] != -1,
+                        [
+                            (lower_line.find(term, last_index), term)
+                            for term in search_terms
+                        ]))
                     if len(index_candidates) == 0:
                         parts.append((line[last_index:], False))
                         break
                     else:
-                        new_index, term = min(index_candidates, key=lambda t: t[0])
+                        new_index, term = min(
+                            index_candidates, key=lambda t: t[0])
                         new_end_index = new_index + len(term)
                         parts.append((line[last_index:new_index], False))
                         parts.append((line[new_index:new_end_index], True))
@@ -573,7 +654,8 @@ def list_protocols():
                     for text, matched in parts
                 ]))
             search_results[protocol] = " …<br />\n".join(formatted_lines)
-    protocols = sorted(protocols, key=lambda protocol: protocol.date, reverse=True)
+    protocols = sorted(
+        protocols, key=lambda protocol: protocol.date, reverse=True)
     page = _get_page()
     page_length = _get_page_length()
     page_count = int(math.ceil(len(protocols) / page_length))
@@ -581,10 +663,17 @@ def list_protocols():
         page = 0
     begin_index = page * page_length
     end_index = (page + 1) * page_length
-    max_page_length_exp = math.ceil(math.log10(len(protocols))) if len(protocols) > 0 else 1
+    max_page_length_exp = get_max_page_length_exp(protocols)
     protocols = protocols[begin_index:end_index]
     protocols_table = ProtocolsTable(protocols, search_results=search_results)
-    return render_template("protocols-list.html", protocols=protocols, protocols_table=protocols_table, search_form=search_form, page=page, page_count=page_count, page_diff=config.PAGE_DIFF, protocoltype_id=protocoltype_id, search_term=search_term, state_open=state_open, page_length=page_length, max_page_length_exp=max_page_length_exp)
+    return render_template(
+        "protocols-list.html", protocols=protocols,
+        protocols_table=protocols_table, search_form=search_form, page=page,
+        page_count=page_count, page_diff=config.PAGE_DIFF,
+        protocoltype_id=protocoltype_id, search_term=search_term,
+        state_open=state_open, page_length=page_length,
+        max_page_length_exp=max_page_length_exp)
+
 
 @app.route("/protocol/new", methods=["GET", "POST"])
 @login_required
@@ -595,50 +684,67 @@ def new_protocol():
     upload_form = NewProtocolSourceUploadForm(protocoltypes)
     file_upload_form = NewProtocolFileUploadForm(protocoltypes)
     if form.validate_on_submit():
-        protocoltype = ProtocolType.query.filter_by(id=form.protocoltype_id.data).first()
+        protocoltype = ProtocolType.query.filter_by(
+            id=form.protocoltype_id.data).first()
         if protocoltype is None or not protocoltype.has_modify_right(user):
             flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
-            return redirect(request.args.get("next") or url_for("index"))
-        protocol = Protocol.create_new_protocol(protocoltype,
-            form.date.data, form.start_time.data)
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+            return back.redirect()
+        protocol = Protocol.create_new_protocol(
+            protocoltype, form.date.data, form.start_time.data)
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     type_id = request.args.get("protocoltype_id")
     if type_id is not None:
         form.protocoltype.data = type_id
         upload_form.protocoltype.data = type_id
-    return render_template("protocol-new.html", form=form, upload_form=upload_form, file_upload_form=file_upload_form, protocoltypes=protocoltypes)
+    return render_template(
+        "protocol-new.html", form=form,
+        upload_form=upload_form, file_upload_form=file_upload_form,
+        protocoltypes=protocoltypes)
+
 
 @app.route("/protocol/show/<int:protocol_id>")
+@back.anchor
 @db_lookup(Protocol)
 def show_protocol(protocol):
     user = current_user()
     errors_table = ErrorsTable(protocol.errors)
-    if not protocol.protocoltype.has_public_view_right(user, check_networks=False):
+    if not protocol.protocoltype.has_public_view_right(
+            user, check_networks=False):
         flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
         if check_login():
             return redirect(url_for("index"))
-        return redirect(request.args.get("next") or url_for("login", next=request.url))
+        return redirect(url_for("login"))
     visible_documents = [
         document for document in protocol.documents
-        if (not document.is_private and document.protocol.has_public_view_right(user))
-        or (document.is_private and document.protocol.protocoltype.has_private_view_right(user))
+        if (not document.is_private
+            and document.protocol.has_public_view_right(user))
+        or (document.is_private
+            and document.protocol.protocoltype.has_private_view_right(user))
     ]
     documents_table = DocumentsTable(visible_documents, protocol)
     document_upload_form = DocumentUploadForm()
     source_upload_form = KnownProtocolSourceUploadForm()
     time_diff = protocol.date - datetime.now().date()
     large_time_diff = not protocol.is_done() and time_diff.days > 0
-    content_html = (protocol.content_html_private
+    content_html = (
+        protocol.content_html_private
         if protocol.has_private_view_right(user)
         else protocol.content_html_public)
     if content_html is not None:
         content_html = Markup(content_html)
-    return render_template("protocol-show.html", protocol=protocol, errors_table=errors_table, documents_table=documents_table, document_upload_form=document_upload_form, source_upload_form=source_upload_form, time_diff=time_diff, large_time_diff=large_time_diff, content_html=content_html)
+    return render_template(
+        "protocol-show.html", protocol=protocol,
+        errors_table=errors_table, documents_table=documents_table,
+        document_upload_form=document_upload_form,
+        source_upload_form=source_upload_form, time_diff=time_diff,
+        large_time_diff=large_time_diff, content_html=content_html)
+
 
 @app.route("/protocol/delete/<int:protocol_id>")
 @login_required
-@group_required(config.ADMIN_GROUP)
+@protect_csrf
 @db_lookup(Protocol)
+@require_admin_right()
 @require_modify_right()
 def delete_protocol(protocol):
     name = protocol.get_short_identifier()
@@ -646,21 +752,24 @@ def delete_protocol(protocol):
     db.session.delete(protocol)
     db.session.commit()
     flash("Protokoll {} ist gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("list_protocols"))
+    return back.redirect("list_protocols")
+
 
 @app.route("/protocol/etherpull/<int:protocol_id>")
 @login_required
+@protect_csrf
 @db_lookup(Protocol)
 @require_modify_right()
 def etherpull_protocol(protocol):
     if not config.ETHERPAD_ACTIVE:
         flash("Die Etherpadfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     protocol.source = get_etherpad_text(protocol.get_identifier())
     db.session.commit()
     tasks.parse_protocol(protocol)
     flash("Das Protokoll wird kompiliert.", "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/protocol/upload/known/<int:protocol_id>", methods=["POST"])
 @login_required
@@ -682,7 +791,8 @@ def upload_source_to_known_protocol(protocol):
                 db.session.commit()
                 tasks.parse_protocol(protocol)
                 flash("Das Protokoll wird kompiliert.", "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/protocol/upload/new/", methods=["POST"])
 @login_required
@@ -693,26 +803,31 @@ def upload_new_protocol():
     if form.validate_on_submit():
         if form.source.data is None:
             flash("Es wurde keine Datei ausgewählt.", "alert-error")
-            return redirect(request.args.get("fail") or url_for("new_protocol"))
+            return redirect(request.args.get("fail")
+                            or url_for("new_protocol"))
         file = form.source.data
         if file.filename == "":
             flash("Es wurde keine Datei ausgewählt.", "alert-error")
-            return redirect(request.args.get("fail") or url_for("new_protocol"))
+            return redirect(request.args.get("fail")
+                            or url_for("new_protocol"))
         source = file.stream.read().decode("utf-8")
-        protocoltype = ProtocolType.query.filter_by(id=form.protocoltype_id.data).first()
+        protocoltype = ProtocolType.query.filter_by(
+            id=form.protocoltype_id.data).first()
         if protocoltype is None or not protocoltype.has_modify_right(user):
             flash("Invalider Protokolltyp oder keine Rechte.", "alert-error")
-            return redirect(request.args.get("fail") or url_for("new_protocol"))
+            return redirect(request.args.get("fail")
+                            or url_for("new_protocol"))
         protocol = Protocol(protocoltype_id=protocoltype.id, source=source)
         db.session.add(protocol)
         db.session.commit()
-        for local_top in protocol.create_localtops:
+        for local_top in protocol.create_localtops():
             db.session.add(local_top)
         db.session.commit()
         tasks.parse_protocol(protocol)
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     return redirect(request.args.get("fail") or url_for("new_protocol"))
 
+
 @app.route("/protocol/upload/new/file/", methods=["POST"])
 @login_required
 def upload_new_protocol_by_file():
@@ -722,42 +837,53 @@ def upload_new_protocol_by_file():
     if form.validate_on_submit():
         if form.file.data is None:
             flash("Es wurde keine Datei ausgewählt.", "alert-error")
-            return redirect(request.args.get("fail") or url_for("new_protocol"))
+            return redirect(request.args.get("fail")
+                            or url_for("new_protocol"))
         file = form.file.data
         if file.filename == "":
             flash("Es wurde keine Datei ausgewählt.", "alert-error")
-            return redirect(request.args.get("fail") or url_for("new_protocol"))
+            return redirect(request.args.get("fail")
+                            or url_for("new_protocol"))
         filename = secure_filename(file.filename)
-        protocoltype = ProtocolType.query.filter_by(id=form.protocoltype_id.data).first()
+        protocoltype = ProtocolType.query.filter_by(
+            id=form.protocoltype_id.data).first()
         if protocoltype is None or not protocoltype.has_modify_right(user):
             flash("Invalider Protokolltyp oder keine Rechte.", "alert-error")
-            return redirect(request.args.get("fail") or url_for("new_protocol"))
-        protocol = Protocol(protocoltype_id=protocoltype.id, date=datetime.now().date(), done=True)
+            return redirect(request.args.get("fail")
+                            or url_for("new_protocol"))
+        protocol = Protocol(
+            protocoltype_id=protocoltype.id,
+            date=datetime.now().date(), done=True)
         db.session.add(protocol)
         db.session.commit()
-        for local_top in protocol.create_localtops:
+        for local_top in protocol.create_localtops():
             db.session.add(local_top)
         db.session.commit()
-        document = Document(protocol_id=protocol.id, name=filename,
+        document = Document(
+            protocol_id=protocol.id, name=filename,
             filename="", is_compiled=False)
         form.populate_obj(document)
         db.session.add(document)
         db.session.commit()
-        internal_filename = "{}-{}-{}".format(protocol.id, document.id, filename)
+        internal_filename = get_internal_filename(
+            protocol, document.id, filename)
         document.filename = internal_filename
         file.save(os.path.join(config.DOCUMENTS_PATH, internal_filename))
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     return redirect(request.args.get("fail") or url_for("new_protocol"))
 
+
 @app.route("/protocol/recompile/<int:protocol_id>")
 @login_required
-@group_required(config.ADMIN_GROUP)
+@protect_csrf
 @db_lookup(Protocol)
+@require_admin_right()
 @require_modify_right()
 def recompile_protocol(protocol):
     tasks.parse_protocol(protocol)
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/protocol/source/<int:protocol_id>")
 @login_required
@@ -765,7 +891,10 @@ def recompile_protocol(protocol):
 @require_modify_right()
 def get_protocol_source(protocol):
     file_like = BytesIO(protocol.source.encode("utf-8"))
-    return send_file(file_like, cache_timeout=1, as_attachment=True, attachment_filename="{}.txt".format(protocol.get_short_identifier()))
+    return send_file(
+        file_like, cache_timeout=1, as_attachment=True,
+        attachment_filename="{}.txt".format(protocol.get_short_identifier()))
+
 
 @app.route("/protocol/template/<int:protocol_id>")
 @login_required
@@ -773,19 +902,25 @@ def get_protocol_source(protocol):
 @require_modify_right()
 def get_protocol_template(protocol):
     file_like = BytesIO(protocol.get_template().encode("utf-8"))
-    return send_file(file_like, cache_timeout=1, as_attachment=True, attachment_filename="{}-template.txt".format(protocol.get_short_identifier()))
+    return send_file(
+        file_like, cache_timeout=1, as_attachment=True,
+        attachment_filename="{}-template.txt".format(
+            protocol.get_short_identifier()))
+
 
 @app.route("/protocol/etherpush/<int:protocol_id>")
 @login_required
+@protect_csrf
 @db_lookup(Protocol)
 @require_modify_right()
 def etherpush_protocol(protocol):
     if not config.ETHERPAD_ACTIVE:
         flash("Die Etherpadfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     if not protocol.is_done():
         tasks.set_etherpad_content(protocol)
-    return redirect(request.args.get("next") or protocol.get_etherpad_link())
+    return redirect(protocol.get_etherpad_link())
+
 
 @app.route("/protocol/update/<int:protocol_id>", methods=["GET", "POST"])
 @login_required
@@ -800,70 +935,82 @@ def update_protocol(protocol):
             meta.value = getattr(edit_form.metas, meta.name).data
         db.session.commit()
         tasks.push_tops_to_calendar(protocol)
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     for meta in protocol.metas:
         getattr(edit_form.metas, meta.name).data = meta.value
-    return render_template("protocol-update.html", upload_form=upload_form, edit_form=edit_form, protocol=protocol)
+    return render_template(
+        "protocol-update.html", upload_form=upload_form,
+        edit_form=edit_form, protocol=protocol)
+
 
 @app.route("/protocol/publish/<int:protocol_id>")
 @login_required
+@protect_csrf
 @db_lookup(Protocol)
 @require_publish_right()
 def publish_protocol(protocol):
     protocol.public = True
     db.session.commit()
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/prococol/send/private/<int:protocol_id>")
 @login_required
+@protect_csrf
 @db_lookup(Protocol)
 @require_modify_right()
 def send_protocol_private(protocol):
     if not config.MAIL_ACTIVE:
         flash("Die Mailfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     tasks.send_protocol_private(protocol)
     flash("Das Protokoll wurde versandt.", "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/prococol/send/public/<int:protocol_id>")
 @login_required
+@protect_csrf
 @db_lookup(Protocol)
 @require_publish_right()
 def send_protocol_public(protocol):
     if not config.MAIL_ACTIVE:
         flash("Die Mailfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     tasks.send_protocol_public(protocol)
     flash("Das Protokoll wurde versandt.", "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/protocol/reminder/<int:protocol_id>")
 @login_required
+@protect_csrf
 @db_lookup(Protocol)
 @require_modify_right()
 def send_protocol_reminder(protocol):
     if not config.MAIL_ACTIVE:
         flash("Die Mailfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id))
-    meetingreminders = MeetingReminder.query.filter_by(protocoltype_id=protocol.protocoltype.id).all()
+        return back.redirect("show_protocol", protocol_id=protocol.id)
+    meetingreminders = protocol.reminders
     if len(meetingreminders) == 0:
-        flash("Für diesen Protokolltyp sind keine Einladungsmails konfiguriert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id))
+        flash("Für diesen Protokolltyp sind keine Einladungsmails "
+              "konfiguriert.", "alert-error")
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     day_difference = (protocol.date - datetime.now().date()).days
     past_reminders = [
         meetingreminder for meetingreminder in meetingreminders
         if meetingreminder.days_before > day_difference
     ]
     if len(past_reminders) == 0:
-        flash("Bisher hätte keine Einladungsmail verschickt werden sollen, schicke letzte.", "alert-info")
+        flash("Bisher hätte keine Einladungsmail verschickt werden sollen, "
+              "schicke letzte.", "alert-info")
         past_reminders = meetingreminders
     past_reminders = sorted(past_reminders, key=lambda r: r.days_before)
     choosen_reminder = past_reminders[0]
     tasks.send_reminder(choosen_reminder, protocol)
     flash("Einladungsmail ist versandt.", "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
-        
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/protocol/tops/new/<int:protocol_id>", methods=["GET", "POST"])
 @login_required
@@ -877,13 +1024,14 @@ def new_top(protocol):
         db.session.add(top)
         db.session.commit()
         tasks.push_tops_to_calendar(top.protocol)
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+        return back.redirect("show_protocol", protocol_id=protocol.id)
     else:
         current_numbers = list(map(lambda t: t.number, protocol.tops))
         suggested_number = get_first_unused_int(current_numbers)
         form.number.data = suggested_number
     return render_template("top-new.html", form=form, protocol=protocol)
 
+
 @app.route("/protocol/top/edit/<int:top_id>", methods=["GET", "POST"])
 @login_required
 @db_lookup(TOP)
@@ -894,11 +1042,13 @@ def edit_top(top):
         form.populate_obj(top)
         db.session.commit()
         tasks.push_tops_to_calendar(top.protocol)
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id))
+        return back.redirect("show_protocol", protocol_id=top.protocol.id)
     return render_template("top-edit.html", form=form, top=top)
 
+
 @app.route("/protocol/top/delete/<int:top_id>")
 @login_required
+@protect_csrf
 @db_lookup(TOP)
 @require_modify_right()
 def delete_top(top):
@@ -908,10 +1058,12 @@ def delete_top(top):
     db.session.commit()
     tasks.push_tops_to_calendar(protocol)
     flash("Der TOP {} wurde gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/protocol/top/move/<int:top_id>/<diff>")
 @login_required
+@protect_csrf
 @db_lookup(TOP)
 @require_modify_right()
 def move_top(top, diff):
@@ -921,9 +1073,11 @@ def move_top(top, diff):
         tasks.push_tops_to_calendar(top.protocol)
     except ValueError:
         flash("Die angegebene Differenz ist keine Zahl.", "alert-error")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id))
+    return back.redirect("show_protocol", protocol_id=top.protocol.id)
+
 
-@app.route("/protocol/localtop/edit/<int:localtop_id>", methods=["GET", "POST"])
+@app.route("/protocol/localtop/edit/<int:localtop_id>",
+           methods=["GET", "POST"])
 @login_required
 @db_lookup(LocalTOP)
 @require_modify_right()
@@ -932,9 +1086,10 @@ def edit_localtop(localtop):
     if form.validate_on_submit():
         form.populate_obj(localtop)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=localtop.protocol.id))
+        return back.redirect("show_protocol", protocol_id=localtop.protocol.id)
     return render_template("localtop-edit.html", form=form, localtop=localtop)
 
+
 def _get_page():
     try:
         page = request.args.get("page")
@@ -944,6 +1099,7 @@ def _get_page():
     except ValueError:
         return 0
 
+
 def _get_page_length():
     try:
         page_length = request.args.get("page_length")
@@ -953,11 +1109,12 @@ def _get_page_length():
     except ValueError:
         return config.PAGE_LENGTH
 
+
 @app.route("/todos/list")
+@back.anchor
 @login_required
 def list_todos():
     user = current_user()
-    protocoltype = None
     protocoltype_id = None
     try:
         protocoltype_id = int(request.args.get("protocoltype_id"))
@@ -973,7 +1130,6 @@ def list_todos():
     search_form = TodoSearchForm(protocoltypes)
     if protocoltype_id is not None:
         search_form.protocoltype_id.data = protocoltype_id
-        protocoltype = ProtocolType.query.filter_by(id=protocoltype_id).first()
     if state_open is not None:
         search_form.state_open.data = state_open
     if search_term is not None:
@@ -999,6 +1155,7 @@ def list_todos():
             if search_term.lower() in todo.description.lower()
             or search_term.lower() in todo.who.lower()
         ]
+
     def _sort_key(todo):
         return (not todo.is_done(), todo.get_id())
     todos = sorted(todos, key=_sort_key, reverse=True)
@@ -1009,10 +1166,17 @@ def list_todos():
         page = 0
     begin_index = page * page_length
     end_index = (page + 1) * page_length
-    max_page_length_exp = math.ceil(math.log10(len(todos))) if len(todos) > 0 else 1
+    max_page_length_exp = get_max_page_length_exp(todos)
     todos = todos[begin_index:end_index]
     todos_table = TodosTable(todos)
-    return render_template("todos-list.html", todos=todos, todos_table=todos_table, search_form=search_form, page=page, page_count=page_count, page_diff=config.PAGE_DIFF, protocoltype_id=protocoltype_id, search_term=search_term, state_open=state_open, page_length=page_length, max_page_length_exp=max_page_length_exp)
+    return render_template(
+        "todos-list.html", todos=todos,
+        todos_table=todos_table, search_form=search_form, page=page,
+        page_count=page_count, page_diff=config.PAGE_DIFF,
+        protocoltype_id=protocoltype_id, search_term=search_term,
+        state_open=state_open, page_length=page_length,
+        max_page_length_exp=max_page_length_exp)
+
 
 @app.route("/todo/new", methods=["GET", "POST"])
 @login_required
@@ -1025,16 +1189,18 @@ def new_todo():
     if protocoltype is not None and protocol is not None:
         if protocol.protocoltype != protocoltype:
             flash("Ungültige Protokoll-Typ-Kombination", "alert-error")
-            return redirect(request.args.get("next") or url_for("index"))
+            return back.redirect()
     if protocoltype is None and protocol is not None:
         protocoltype = protocol.protocoltype
     protocoltypes = ProtocolType.get_modifiable_protocoltypes(user)
     form = NewTodoForm(protocoltypes)
     if form.validate_on_submit():
-        added_protocoltype = ProtocolType.query.filter_by(id=form.protocoltype_id.data).first()
-        if added_protocoltype is None or not added_protocoltype.has_modify_right(user):
+        added_protocoltype = ProtocolType.query.filter_by(
+            id=form.protocoltype_id.data).first()
+        if (added_protocoltype is None
+                or not added_protocoltype.has_modify_right(user)):
             flash("Invalider Protokolltyp.")
-            return redirect(request.args.get("next") or url_for("index"))
+            return back.redirect()
         todo = Todo()
         form.populate_obj(todo)
         if protocol is not None:
@@ -1045,13 +1211,16 @@ def new_todo():
         db.session.commit()
         flash("Todo wurde angelegt.", "alert-success")
         if protocol is not None:
-            return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+            return back.redirect("show_protocol", protocol_id=protocol.id)
         else:
-            return redirect(request.args.get("next") or url_for("list_todos", protocoltype_id=protocoltype_id))
+            return back.redirect("list_todos", protocoltype_id=protocoltype_id)
     else:
         if protocoltype is not None:
             form.protocoltype_id.data = protocoltype.id
-    return render_template("todo-new.html", form=form, protocol=protocol, protocoltype=protocoltype)
+    return render_template(
+        "todo-new.html", form=form, protocol=protocol,
+        protocoltype=protocoltype)
+
 
 @app.route("/todo/edit/<int:todo_id>", methods=["GET", "POST"])
 @login_required
@@ -1062,10 +1231,13 @@ def edit_todo(todo):
     if form.validate_on_submit():
         form.populate_obj(todo)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("list_todos", protocoltype_id=todo.protocoltype.id))
+        return back.redirect(
+            "list_todos", protocoltype_id=todo.protocoltype.id)
     return render_template("todo-edit.html", form=form, todo=todo)
 
+
 @app.route("/todo/show/<int:todo_id>")
+@back.anchor
 @login_required
 @db_lookup(Todo)
 @require_private_view_right()
@@ -1073,8 +1245,10 @@ def show_todo(todo):
     todo_table = TodoTable(todo)
     return render_template("todo-show.html", todo=todo, todo_table=todo_table)
 
+
 @app.route("/todo/delete/<int:todo_id>")
 @login_required
+@protect_csrf
 @db_lookup(Todo)
 @require_private_view_right()
 def delete_todo(todo):
@@ -1082,16 +1256,17 @@ def delete_todo(todo):
     db.session.delete(todo)
     db.session.commit()
     flash("Todo gelöscht.", "alert-success")
-    return redirect(request.args.get("next") or url_for("list_todos", protocoltype_id=type_id))
+    return back.redirect("list_todos", protocoltype_id=type_id)
+
 
 @app.route("/todo/merge", methods=["GET", "POST"])
 @login_required
-@group_required(config.ADMIN_GROUP)
+@require_admin_right()
 def merge_todos():
     form = MergeTodosForm(request.args.get("todo_id"))
     if form.validate_on_submit():
         todo1 = Todo.query.filter_by(id=form.todo1.data).first()
-        todo2 = Todo.query.filter_by(id=todo.todo2.data).first()
+        todo2 = Todo.query.filter_by(id=form.todo2.data).first()
         if todo1 is None or todo2 is None:
             flash("Missing todos.", "alert-error")
         else:
@@ -1104,16 +1279,15 @@ def merge_todos():
             db.session.delete(todo2)
             db.session.commit()
             flash("Merged todos {} and {}.".format(id1, id2), "alert-success")
-            return redirect(request.args.get("next") or url_for("list_todos"))
-    return render_template("todos-merge.html", form=form, next_url=request.args.get("next"))
+            return back.redirect("list_todos")
+    return render_template("todos-merge.html", form=form)
+
 
 @app.route("/decisions/list")
+@back.anchor
 def list_decisions():
-    is_logged_In = check_login()
     user = current_user()
-    protocoltype = None
     protocoltype_id = None
-    decisioncategory = None
     decisioncategory_id = None
     try:
         protocoltype_id = int(request.args.get("protocoltype_id"))
@@ -1124,7 +1298,8 @@ def list_decisions():
     except (ValueError, TypeError):
         pass
     search_term = request.args.get("search")
-    protocoltypes = ProtocolType.get_public_protocoltypes(user, check_networks=False)
+    protocoltypes = ProtocolType.get_public_protocoltypes(
+        user, check_networks=False)
     decisioncategories = [
         category
         for protocoltype in protocoltypes
@@ -1133,10 +1308,8 @@ def list_decisions():
     search_form = DecisionSearchForm(protocoltypes, decisioncategories)
     if protocoltype_id is not None:
         search_form.protocoltype_id.data = protocoltype_id
-        protocoltype = ProtocolType.query.filter_by(id=protocoltype_id).first()
     if decisioncategory_id is not None:
         search_form.decisioncategory_id.data = decisioncategory_id
-        decisioncategory = DecisionCategory.query.filter_by(id=decisioncategory_id).first()
     if search_term is not None:
         search_form.search.data = search_term
     decisions = [
@@ -1145,7 +1318,7 @@ def list_decisions():
     ]
     if protocoltype_id is not None and protocoltype_id != -1:
         decisions = [
-            decision for decision in decisions 
+            decision for decision in decisions
             if decision.protocol.protocoltype.id == protocoltype_id
         ]
     if decisioncategory_id is not None and decisioncategory_id != -1:
@@ -1159,7 +1332,7 @@ def list_decisions():
             if search_term.lower() in decision.content.lower()
         ]
     decisions = sorted(decisions, key=lambda d: d.protocol.date, reverse=True)
-        
+
     page = _get_page()
     page_length = _get_page_length()
 
@@ -1168,22 +1341,33 @@ def list_decisions():
         page = 0
     begin_index = page * page_length
     end_index = (page + 1) * page_length
-    max_page_length_exp = math.ceil(math.log10(len(decisions))) if len(decisions) > 0 else 1
+    max_page_length_exp = get_max_page_length_exp(decisions)
     decisions = decisions[begin_index:end_index]
     decisions_table = DecisionsTable(decisions)
-    return render_template("decisions-list.html", decisions=decisions, decisions_table=decisions_table, search_form=search_form, page=page, page_count=page_count, page_diff=config.PAGE_DIFF, protocoltype_id=protocoltype_id, search_term=search_term, decisioncategory_id=decisioncategory_id, page_length=page_length, max_page_length_exp=max_page_length_exp)
+    return render_template(
+        "decisions-list.html", decisions=decisions,
+        decisions_table=decisions_table, search_form=search_form, page=page,
+        page_count=page_count, page_diff=config.PAGE_DIFF,
+        protocoltype_id=protocoltype_id, search_term=search_term,
+        decisioncategory_id=decisioncategory_id, page_length=page_length,
+        max_page_length_exp=max_page_length_exp)
+
 
 @app.route("/document/download/<int:document_id>")
 @db_lookup(Document)
 def download_document(document):
     user = current_user()
     if ((document.is_private
-            and not document.protocol.protocoltype.has_private_view_right(user))
+            and not document.protocol.protocoltype
+            .has_private_view_right(user))
         or (not document.is_private
             and not document.protocol.has_public_view_right(user))):
         flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
-        return redirect(request.args.get("next") or url_for("index"))
-    return send_file(document.as_file_like(), cache_timeout=1, as_attachment=True, attachment_filename=document.name)
+        return back.redirect()
+    return send_file(
+        document.as_file_like(), cache_timeout=1,
+        as_attachment=True, attachment_filename=document.name)
+
 
 @app.route("/document/upload/<int:protocol_id>", methods=["POST"])
 @login_required
@@ -1193,26 +1377,28 @@ def upload_document(protocol):
     form = DocumentUploadForm()
     if form.document.data is None:
         flash("Es wurde keine Datei ausgewählt.", "alert-error")
-        return redirect(request.args.get("next") or url_for("index"))
+        return back.redirect()
     file = form.document.data
     if file.filename == "":
         flash("Es wurde keine Datei ausgewählt.", "alert-error")
-        return redirect(request.args.get("next") or url_for("index"))
-    # todo: Dateitypen einschränken?
+        return back.redirect()
     if file:
         filename = secure_filename(file.filename)
-        document = Document(protocol_id=protocol.id, name=filename,
+        document = Document(
+            protocol_id=protocol.id, name=filename,
             filename="", is_compiled=False)
         form.populate_obj(document)
         db.session.add(document)
         db.session.commit()
-        internal_filename = "{}-{}-{}".format(protocol.id, document.id, filename)
+        internal_filename = get_internal_filename(
+            protocol, document, filename)
         document.filename = internal_filename
         file.save(os.path.join(config.DOCUMENTS_PATH, internal_filename))
         if datetime.now().date() >= protocol.date:
             protocol.done = True
         db.session.commit()
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/document/edit/<int:document_id>", methods=["GET", "POST"])
 @login_required
@@ -1223,13 +1409,15 @@ def edit_document(document):
     if form.validate_on_submit():
         form.populate_obj(document)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=document.protocol.id))
+        return back.redirect("show_protocol", protocol_id=document.protocol.id)
     return render_template("document-edit.html", document=document, form=form)
 
+
 @app.route("/document/delete/<int:document_id>")
 @login_required
-@group_required(config.ADMIN_GROUP)
+@protect_csrf
 @db_lookup(Document)
+@require_admin_right()
 @require_modify_right()
 def delete_document(document):
     name = document.name
@@ -1237,34 +1425,46 @@ def delete_document(document):
     db.session.delete(document)
     db.session.commit()
     flash("Das Dokument {} wurde gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
+    return back.redirect("show_protocol", protocol_id=protocol.id)
+
 
 @app.route("/document/print/<int:document_id>")
 @login_required
+@protect_csrf
 @db_lookup(Document)
 @require_modify_right()
 def print_document(document):
     if not config.PRINTING_ACTIVE:
         flash("Die Druckfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=document.protocol.id))
+        return back.redirect("show_protocol", protocol_id=document.protocol.id)
     tasks.print_file(document.get_filename(), document.protocol)
-    flash("Das Dokument {} wird gedruckt.".format(document.name), "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=document.protocol.id))
+    flash("Das Dokument {} wird gedruckt.".format(document.name),
+          "alert-success")
+    return back.redirect("show_protocol", protocol_id=document.protocol.id)
+
 
 @app.route("/decision/print/<int:decisiondocument_id>")
 @login_required
+@protect_csrf
 @db_lookup(DecisionDocument)
 @require_modify_right()
 def print_decision(decisiondocument):
-    user = current_user()
     if not config.PRINTING_ACTIVE:
         flash("Die Druckfunktion ist nicht aktiviert.", "alert-error")
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=decisiondocument.decision.protocol.id))
-    tasks.print_file(decisiondocument.get_filename(), decisiondocument.decision.protocol)
-    flash("Das Dokument {} wird gedruckt.".format(decisiondocument.name), "alert-success")
-    return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=decisiondocument.decision.protocol.id))
+        return back.redirect(
+            "show_protocol",
+            protocol_id=decisiondocument.decision.protocol.id)
+    tasks.print_file(
+        decisiondocument.get_filename(),
+        decisiondocument.decision.protocol)
+    flash("Das Dokument {} wird gedruckt.".format(decisiondocument.name),
+          "alert-success")
+    return back.redirect(
+        "show_protocol", protocol_id=decisiondocument.decision.protocol.id)
+
 
 @app.route("/errors/list")
+@back.anchor
 @login_required
 def list_errors():
     user = current_user()
@@ -1273,18 +1473,24 @@ def list_errors():
         if error.protocol.protocoltype.has_private_view_right(user)
     ]
     errors_table = ErrorsTable(errors)
-    return render_template("errors-list.html", errros=errors, errors_table=errors_table)
+    return render_template(
+        "errors-list.html", erros=errors, errors_table=errors_table)
+
 
 @app.route("/error/show/<int:error_id>")
+@back.anchor
 @login_required
 @db_lookup(Error)
 @require_modify_right()
 def show_error(error):
     error_table = ErrorTable(error)
-    return render_template("error-show.html", error=error, error_table=error_table)
+    return render_template(
+        "error-show.html", error=error, error_table=error_table)
+
 
 @app.route("/error/delete/<int:error_id>")
 @login_required
+@protect_csrf
 @db_lookup(Error)
 @require_modify_right()
 def delete_error(error):
@@ -1292,14 +1498,19 @@ def delete_error(error):
     db.session.delete(error)
     db.session.commit()
     flash("Fehler {} gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("list_errors"))
+    return back.redirect("list_errors")
+
 
 @app.route("/todomails/list")
+@back.anchor
 @login_required
 def list_todomails():
     todomails = sorted(TodoMail.query.all(), key=lambda tm: tm.name.lower())
     todomails_table = TodoMailsTable(todomails)
-    return render_template("todomails-list.html", todomails=todomails, todomails_table=todomails_table)
+    return render_template(
+        "todomails-list.html", todomails=todomails,
+        todomails_table=todomails_table)
+
 
 @app.route("/todomail/new", methods=["GET", "POST"])
 @login_required
@@ -1310,10 +1521,12 @@ def new_todomail():
         form.populate_obj(todomail)
         db.session.add(todomail)
         db.session.commit()
-        flash("Die Todomailzuordnung für {} wurde angelegt.".format(todomail.name), "alert-success")
-        return redirect(request.args.get("next") or url_for("list_todomails"))
+        flash("Die Todomailzuordnung für {} wurde angelegt.".format(
+            todomail.name), "alert-success")
+        return back.redirect("list_todomails")
     return render_template("todomail-new.html", form=form)
 
+
 @app.route("/todomail/edit/<int:todomail_id>", methods=["GET", "POST"])
 @login_required
 @db_lookup(TodoMail)
@@ -1323,19 +1536,23 @@ def edit_todomail(todomail):
         form.populate_obj(todomail)
         db.session.commit()
         flash("Die Todo-Mail-Zuordnung wurde geändert.", "alert-success")
-        return redirect(request.args.get("next") or url_for("list_todomails"))
+        return back.redirect("list_todomails")
     return render_template("todomail-edit.html", todomail=todomail, form=form)
 
+
 @app.route("/todomail/delete/<int:todomail_id>")
 @login_required
+@protect_csrf
 @db_lookup(TodoMail)
 def delete_todomail(todomail):
     name = todomail.name
     db.session.delete(todomail)
     db.session.commit()
-    flash("Die Todo-Mail-Zuordnung für {} wurde gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("list_todomails"))
-    
+    flash("Die Todo-Mail-Zuordnung für {} wurde gelöscht.".format(name),
+          "alert-success")
+    return back.redirect("list_todomails")
+
+
 @app.route("/defaultmeta/new/<int:protocoltype_id>", methods=["GET", "POST"])
 @login_required
 @db_lookup(ProtocolType)
@@ -1348,8 +1565,10 @@ def new_defaultmeta(protocoltype):
         db.session.add(meta)
         db.session.commit()
         flash("Metadatenfeld hinzugefügt.", "alert-success")
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
-    return render_template("defaultmeta-new.html", form=form, protocoltype=protocoltype)
+        return back.redirect("show_type", protocoltype_id=protocoltype.id)
+    return render_template(
+        "defaultmeta-new.html", form=form, protocoltype=protocoltype)
+
 
 @app.route("/defaultmeta/edit/<int:defaultmeta_id>", methods=["GET", "POST"])
 @login_required
@@ -1360,13 +1579,17 @@ def edit_defaultmeta(defaultmeta):
     if form.validate_on_submit():
         form.populate_obj(defaultmeta)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=defaultmeta.protocoltype.id))
-    return render_template("defaultmeta-edit.html", form=form, defaultmeta=defaultmeta)
+        return back.redirect(
+            "show_type", protocoltype_id=defaultmeta.protocoltype.id)
+    return render_template(
+        "defaultmeta-edit.html", form=form, defaultmeta=defaultmeta)
+
 
 @app.route("/defaultmeta/delete/<int:defaultmeta_id>")
 @login_required
-@group_required(config.ADMIN_GROUP)
+@protect_csrf
 @db_lookup(DefaultMeta)
+@require_admin_right()
 @require_modify_right()
 def delete_defaultmeta(defaultmeta):
     name = defaultmeta.name
@@ -1374,9 +1597,11 @@ def delete_defaultmeta(defaultmeta):
     db.session.delete(defaultmeta)
     db.session.commit()
     flash("Metadatenfeld '{}' gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=type_id))
+    return back.redirect("show_type", protocoltype_id=type_id)
+
 
-@app.route("/decisioncategory/new/<int:protocoltype_id>", methods=["GET", "POST"])
+@app.route("/decisioncategory/new/<int:protocoltype_id>",
+           methods=["GET", "POST"])
 @login_required
 @db_lookup(ProtocolType)
 @require_modify_right()
@@ -1388,10 +1613,13 @@ def new_decisioncategory(protocoltype):
         db.session.add(category)
         db.session.commit()
         flash("Beschlusskategorie hinzugefügt.", "alert-success")
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=protocoltype.id))
-    return render_template("decisioncategory-new.html", form=form, protocoltype=protocoltype)
+        return back.redirect("show_type", protocoltype_id=protocoltype.id)
+    return render_template(
+        "decisioncategory-new.html", form=form, protocoltype=protocoltype)
+
 
-@app.route("/decisioncategory/edit/<int:decisioncategory_id>", methods=["GET", "POST"])
+@app.route("/decisioncategory/edit/<int:decisioncategory_id>",
+           methods=["GET", "POST"])
 @login_required
 @db_lookup(DecisionCategory)
 @require_modify_right()
@@ -1400,13 +1628,18 @@ def edit_decisioncategory(decisioncategory):
     if form.validate_on_submit():
         form.populate_obj(decisioncategory)
         db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=decisioncategory.protocoltype.id))
-    return render_template("decisioncategory-edit.html", form=form, decisioncategory=decisioncategory)
+        return back.redirect(
+            "show_type", protocoltype_id=decisioncategory.protocoltype.id)
+    return render_template(
+        "decisioncategory-edit.html", form=form,
+        decisioncategory=decisioncategory)
+
 
 @app.route("/decisioncategory/delete/<int:decisioncategory_id>")
 @login_required
-@group_required(config.ADMIN_GROUP)
+@protect_csrf
 @db_lookup(DecisionCategory)
+@require_admin_right()
 @require_modify_right()
 def delete_decisioncategory(decisioncategory):
     name = decisioncategory.name
@@ -1414,33 +1647,39 @@ def delete_decisioncategory(decisioncategory):
     db.session.delete(decisioncategory)
     db.session.commit()
     flash("Beschlusskategorie {} gelöscht.".format(name), "alert-success")
-    return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=type_id))
+    return back.redirect("show_type", protocoltype_id=type_id)
+
 
 def create_protocols_feed(protocoltype):
     if not protocoltype.has_public_anonymous_view_right():
         abort(403)
-    protocols = [protocol
-        for protocol in protocoltype.protocols
+    protocols = [
+        protocol for protocol in protocoltype.protocols
         if protocol.is_done()
     ]
     feed = feedgen.feed.FeedGenerator()
     feed.description(protocoltype.name)
-    feed.generator("Protokollsystem 3",
+    feed.generator(
+        "Protokollsystem 3",
         uri="https://git.fsmpi.rwth-aachen.de/protokollsystem/proto3")
-    feed.id(url_for("show_type", protocoltype_id=protocoltype.id, _external=True))
-    feed.link(href=url_for("list_protocols", protocoltype_id=protocoltype.id,
+    feed.id(url_for(
+        "show_type", protocoltype_id=protocoltype.id, _external=True))
+    feed.link(href=url_for(
+        "list_protocols", protocoltype_id=protocoltype.id,
         state_open=False, _external=True), rel="alternate")
     feed.title(protocoltype.short_name)
     for protocol in protocols:
         entry = feed.add_entry()
-        entry.id(url_for("show_protocol",
-            protocol_id=protocol.id, _external=True))
-        entry.link(href=url_for("show_protocol", protocol_id=protocol.id,
+        entry.id(url_for(
+            "show_protocol", protocol_id=protocol.id, _external=True))
+        entry.link(href=url_for(
+            "show_protocol", protocol_id=protocol.id,
             _external=True), rel="alternate")
         document = protocol.get_compiled_document(private=False)
         if document is not None:
-            entry.link(href=url_for("download_document",
-                document_id=document.id, _external=True), rel="enclosure",
+            entry.link(href=url_for(
+                "download_document", document_id=document.id, _external=True),
+                rel="enclosure",
                 title="Protokoll", type="application/pdf")
         entry.title(protocol.get_title())
         entry.summary(",\n".join(top.name for top in protocol.get_tops()))
@@ -1452,73 +1691,86 @@ def create_protocols_feed(protocoltype):
 def create_appointments_feed(protocoltype):
     if not protocoltype.has_public_anonymous_view_right():
         abort(403)
-    protocols = [protocol
-        for protocol in protocoltype.protocols
+    protocols = [
+        protocol for protocol in protocoltype.protocols
         if not protocol.is_done()
     ]
     feed = feedgen.feed.FeedGenerator()
     feed.description(protocoltype.name)
-    feed.generator("Protokollsystem 3",
+    feed.generator(
+        "Protokollsystem 3",
         uri="https://git.fsmpi.rwth-aachen.de/protokollsystem/proto3")
-    feed.id(url_for("show_type", protocoltype_id=protocoltype.id, _external=True))
-    feed.link(href=url_for("list_protocols", protocoltype_id=protocoltype.id,
+    feed.id(url_for(
+        "show_type", protocoltype_id=protocoltype.id, _external=True))
+    feed.link(href=url_for(
+        "list_protocols", protocoltype_id=protocoltype.id,
         state_open=True, _external=True), rel="alternate")
     feed.title("{}-Termine".format(protocoltype.short_name))
     for protocol in protocols:
         entry = feed.add_entry()
-        entry.id(url_for("show_protocol",
-            protocol_id=protocol.id, _external=True))
-        entry.link(href=url_for("show_protocol", protocol_id=protocol.id,
-            _external=True), rel="alternate")
+        entry.id(url_for(
+            "show_protocol", protocol_id=protocol.id, _external=True))
+        entry.link(href=url_for(
+            "show_protocol", protocol_id=protocol.id, _external=True),
+            rel="alternate")
         entry.title(protocol.get_title())
         entry.summary("\n".join(
-            [",\n".join([
+            [
+                ",\n".join([
                     "Beginn: {}".format(protocol.get_time())
                 ] + [
                     "{}: {}".format(meta.name, meta.value)
                     for meta in protocol.metas
                     if not meta.internal
-                ]
-            ),
-            "Tagesordnung:",
-            ",\n".join(
-                "TOP {}".format(top.name)
-                for top in protocol.get_tops()
-            )]))
+                ]),
+                "Tagesordnung:",
+                ",\n".join(
+                    "TOP {}".format(top.name)
+                    for top in protocol.get_tops()
+                )
+            ]))
     return feed
 
 
 @app.route("/feed/protocols/rss/<int:protocoltype_id>")
 @db_lookup(ProtocolType)
 def feed_protocols_rss(protocoltype):
-    return Response(create_protocols_feed(protocoltype).rss_str(),
+    return Response(
+        create_protocols_feed(protocoltype).rss_str(),
         mimetype="application/rss+xml")
 
+
 @app.route("/feed/protocols/atom/<int:protocoltype_id>")
 @db_lookup(ProtocolType)
 def feed_protocols_atom(protocoltype):
-    return Response(create_protocols_feed(protocoltype).atom_str(),
+    return Response(
+        create_protocols_feed(protocoltype).atom_str(),
         mimetype="application/atom+xml")
 
+
 @app.route("/feed/appointments/rss/<int:protocoltype_id>")
 @db_lookup(ProtocolType)
 def feed_appointments_rss(protocoltype):
-    return Response(create_appointments_feed(protocoltype).rss_str(),
+    return Response(
+        create_appointments_feed(protocoltype).rss_str(),
         mimetype="application/rss+xml")
 
+
 @app.route("/feed/appointments/atom/<int:protocoltype_id>")
 @db_lookup(ProtocolType)
 def feed_appointments_atom(protocoltype):
-    return Response(create_appointments_feed(protocoltype).atom_str(),
+    return Response(
+        create_appointments_feed(protocoltype).atom_str(),
         mimetype="application/atom+xml")
 
+
 @app.route("/feed/appointments/ical/<int:protocoltype_id>")
 @db_lookup(ProtocolType)
 def feed_appointments_ical(protocoltype):
     if not protocoltype.has_public_anonymous_view_right():
         abort(403)
-    protocols = [protocol
-        for protocol in protocoltype.protocols
+    protocols = [
+        protocol for protocol in protocoltype.protocols
         if not protocol.is_done()
     ]
     calendar = icalendar.Calendar()
@@ -1534,41 +1786,43 @@ def feed_appointments_ical(protocoltype):
         event["dtstart"] = to_datetime(start)
         event["dtend"] = to_datetime(start + timedelta(hours=3))
         event["summary"] = protocoltype.short_name
-        event["description"] = "\n".join(top.name
-            for top in protocol.get_tops())
+        event["description"] = "\n".join(
+            top.name for top in protocol.get_tops())
         calendar.add_component(event)
     content = calendar.to_ical().decode("utf-8")
     for key in config.CALENDAR_TIMEZONE_MAP:
-        content = content.replace("TZID={}:".format(key),
+        content = content.replace(
+            "TZID={}:".format(key),
             "TZID={}:".format(config.CALENDAR_TIMEZONE_MAP[key]))
     return Response(content.encode("utf-8"), mimetype="text/calendar")
 
 
 @app.route("/like/new")
 @login_required
+@protect_csrf
 def new_like():
     user = current_user()
     parent = None
     if "protocol_id" in request.args:
-        parent = Protocol.query.filter_by(id=request.args.get("protocol_id")).first()
+        parent = Protocol.first_by_id(request.args.get("protocol_id"))
     elif "todo_id" in request.args:
-        parent = Todo.query.filter_by(id=request.args.get("todo_id")).first()
+        parent = Todo.first_by_id(request.args.get("todo_id"))
     elif "decision_id" in request.args:
-        parent = Decision.query.filter_by(id=request.args.get("decision_id")).first()
+        parent = Decision.first_by_id(request.args.get("decision_id"))
     elif "top_id" in request.args:
-        parent = TOP.query.filter_by(id=request.args.get("top_id")).first()
+        parent = TOP.first_by_id(request.args.get("top_id"))
     if parent is None or not parent.has_public_view_right(user):
         flash("Missing object to like.", "alert-error")
-        return redirect(request.args.get("next") or url_for("index"))
+        return back.redirect()
     if len([like for like in parent.likes if like.who == user.username]) > 0:
         flash("You have liked this already!", "alert-error")
-        return redirect(request.args.get("next") or url_for("index"))
+        return back.redirect()
     like = Like(who=user.username)
     db.session.add(like)
     parent.likes.append(like)
     db.session.commit()
     flash("Like!", "alert-success")
-    return redirect(request.args.get("next") or url_for("index"))
+    return back.redirect()
 
 
 @app.route("/login", methods=["GET", "POST"])
@@ -1578,18 +1832,23 @@ def login():
         return redirect(url_for("index"))
     form = LoginForm()
     if form.validate_on_submit():
-        user = user_manager.login(form.username.data, form.password.data, permanent=form.permanent.data)
+        user = user_manager.login(
+            form.username.data, form.password.data,
+            permanent=form.permanent.data)
         if user is not None:
             session["auth"] = security_manager.hash_user(user)
             session.permanent = form.permanent.data
-            flash("Login successful, {}!".format(user.username), "alert-success")
-            return redirect(request.args.get("next") or url_for("index"))
+            flash("Login successful, {}!".format(user.username),
+                  "alert-success")
+            return back.redirect()
         else:
             flash("Wrong login data. Try again.", "alert-error")
     return render_template("login.html", form=form)
 
+
 @app.route("/logout")
 @login_required
+@protect_csrf
 def logout():
     if "auth" in session:
         session.pop("auth")
@@ -1597,18 +1856,19 @@ def logout():
         flash("You are not logged in.", "alert-error")
     return redirect(url_for(".index"))
 
-def make_scheduler():
-    pass
+
 try:
-    from uwsgidecorators import timer as uwsgitimer, signal as uwsgisignal, cron as uwsgicron
-    print("using uwsgi for cron-like tasks")
+    from uwsgidecorators import cron as uwsgicron
+
     @uwsgicron(30, -1, -1, -1, -1, target="mule")
     def uwsgi_timer(signum):
         if signum == 0:
             check_and_send_reminders()
+
+    def make_scheduler():
+        pass
 except ImportError as exc:
     def make_scheduler():
-        print("uwsgi not found, falling back to apscheduler for cron-like tasks")
         scheduler = BackgroundScheduler()
         scheduler.start()
         scheduler.add_job(
@@ -1619,24 +1879,29 @@ except ImportError as exc:
             replace_existing=True)
         atexit.register(scheduler.shutdown)
 
+
 def check_and_send_reminders():
     if not config.MAIL_ACTIVE:
         return
     with app.app_context():
         current_time = datetime.now()
         current_day = current_time.date()
-        for protocol in Protocol.query.filter(Protocol.done != True).all():
+        for protocol in Protocol.query.filter(not Protocol.done).all():
             day_difference = (protocol.date - current_day).days
             usual_time = protocol.get_time()
-            protocol_time = datetime(1, 1, 1, usual_time.hour, usual_time.minute)
+            protocol_time = datetime(
+                1, 1, 1, usual_time.hour, usual_time.minute)
             hour_difference = (protocol_time - current_time).seconds // 3600
             for reminder in protocol.protocoltype.reminders:
-                if day_difference == reminder.days_before and hour_difference == 0:
+                if (day_difference == reminder.days_before
+                        and hour_difference == 0):
                     tasks.send_reminder(reminder, protocol)
             if (day_difference < 0
-                and -day_difference > config.MAX_PAST_INDEX_DAYS_BEFORE_REMINDER
-                and hour_difference == 0): # once per day
-                tasks.remind_finishing(protocol, -day_difference,
+                    and (-day_difference
+                         > config.MAX_PAST_INDEX_DAYS_BEFORE_REMINDER)
+                    and hour_difference == 0):  # once per day
+                tasks.remind_finishing(
+                    protocol, -day_difference,
                     config.MAX_PAST_INDEX_DAYS_BEFORE_REMINDER)
 
 
diff --git a/shared.py b/shared.py
index 3e091c4303424a19004383c6715b722aea7564b4..83c3ed00611ab12345df0489de2b130dc0f279e1 100644
--- a/shared.py
+++ b/shared.py
@@ -1,17 +1,20 @@
 from flask_sqlalchemy import SQLAlchemy
-from flask import session, redirect, url_for, request
+from flask import session, redirect, url_for, flash
 
 import re
 from functools import wraps
 from enum import Enum
 
+import back
+
 import config
 
 db = SQLAlchemy()
 
-# the following code is written by Lars Beckers and not to be published without permission
+# the following code escape_tex is written by Lars Beckers
+# and not to be published without permission
 latex_chars = [
-    ("\\", "\\backslash"), # this needs to be first
+    ("\\", "\\backslash"),  # this needs to be first
     ("$", "\$"),
     ('%', '\\%'),
     ('&', '\\&'),
@@ -21,7 +24,6 @@ latex_chars = [
     ('}', '\\}'),
     ('[', '\\['),
     (']', '\\]'),
-    #('"', '"\''),
     ('~', r'$\sim{}$'),
     ('^', r'\textasciicircum{}'),
     ('Ë„', r'\textasciicircum{}'),
@@ -35,84 +37,119 @@ latex_chars = [
     ('<', '$<$'),
     ('>', '$>$'),
     ('\\backslashin', '$\\in$'),
-    ('\\backslash', '$\\backslash$') # this needs to be last
+    ('\\backslash', '$\\backslash$')  # this needs to be last
 ]
 
+
 def escape_tex(text):
     out = text
     for old, new in latex_chars:
         out = out.replace(old, new)
     # beware, the following is carefully crafted code
     res = ''
-    k, l = (0, -1)
-    while k >= 0:
-        k = out.find('"', l+1)
-        if k >= 0:
-            res += out[l+1:k]
-            l = out.find('"', k+1)
-            if l >= 0:
-                res += '\\enquote{' + out[k+1:l] + '}'
+    start, end = (0, -1)
+    while start >= 0:
+        start = out.find('"', end + 1)
+        if start >= 0:
+            res += out[end + 1:start]
+            end = out.find('"', start + 1)
+            if end >= 0:
+                res += '\\enquote{' + out[start + 1:end] + '}'
             else:
-                res += '"\'' + out[k+1:]
-            k = l
+                res += '"\'' + out[start + 1:]
+            start = end
         else:
-            res += out[l+1:]
+            res += out[end + 1:]
     # yes, this is not quite escaping latex chars, but anyway...
     res = re.sub('([a-z])\(', '\\1 (', res)
     res = re.sub('\)([a-z])', ') \\1', res)
-    #logging.debug('escape latex ({0}/{1}): {2} --> {3}'.format(len(text), len(res), text.split('\n')[0], res.split('\n')[0]))
     return res
 
+
 def unhyphen(text):
     return " ".join([r"\mbox{" + word + "}" for word in text.split(" ")])
 
+
 def date_filter(date):
     return date.strftime("%d. %B %Y")
+
+
 def datetime_filter(date):
+    if date is None:
+        return ""
     return date.strftime("%d. %B %Y, %H:%M")
+
+
 def date_filter_long(date):
+    if date is None:
+        return ""
     return date.strftime("%A, %d.%m.%Y, Kalenderwoche %W")
+
+
 def date_filter_short(date):
+    if date is None:
+        return ""
     return date.strftime("%d.%m.%Y")
+
+
 def time_filter(time):
+    if time is None:
+        return ""
     return time.strftime("%H:%M Uhr")
+
+
 def time_filter_short(time):
+    if time is None:
+        return ""
     return time.strftime("%H:%M")
 
+
 def needs_date_test(todostate):
     return todostate.needs_date()
+
+
 def todostate_name_filter(todostate):
     return todostate.get_name()
 
+
 def indent_tab_filter(text):
     return "\n".join(map(lambda l: "\t{}".format(l), text.splitlines()))
 
+
 def class_filter(obj):
     return obj.__class__.__name__
+
+
 def code_filter(text):
     return "<code>{}</code>".format(text)
 
+
 from auth import UserManager, SecurityManager, User
 max_duration = getattr(config, "AUTH_MAX_DURATION")
 user_manager = UserManager(backends=config.AUTH_BACKENDS)
 security_manager = SecurityManager(config.SECURITY_KEY, max_duration)
 
+
 def check_login():
     return "auth" in session and security_manager.check_user(session["auth"])
+
+
 def current_user():
     if not check_login():
         return None
     return User.from_hashstring(session["auth"])
 
+
 def login_required(function):
     @wraps(function)
     def decorated_function(*args, **kwargs):
         if check_login():
             return function(*args, **kwargs)
         else:
-            return redirect(url_for("login", next=request.url))
+            return redirect(url_for("login"))
     return decorated_function
 
+
 def group_required(group):
     def decorator(function):
         @wraps(function)
@@ -120,16 +157,19 @@ def group_required(group):
             if group in current_user().groups:
                 return function(*args, **kwargs)
             else:
-                flash("You do not have the necessary permissions to view this page.")
-                return redirect(request.args.get("next") or url_for("index"))
+                flash("You do not have the necessary permissions to "
+                      "view this page.")
+                return back.redirect()
         return decorated_function
     return decorator
 
+
 DATE_KEY = "Datum"
 START_TIME_KEY = "Beginn"
 END_TIME_KEY = "Ende"
 KNOWN_KEYS = [DATE_KEY, START_TIME_KEY, END_TIME_KEY]
 
+
 class WikiType(Enum):
     MEDIAWIKI = 0
     DOKUWIKI = 1
diff --git a/tasks.py b/tasks.py
index 852c3b48b565e716c1bcf8a44308e9562996abda..50d047684b23ee32a6e22e8cf71414cd8f05d8ba 100644
--- a/tasks.py
+++ b/tasks.py
@@ -9,15 +9,21 @@ import traceback
 from copy import copy
 import xmlrpc.client
 
-from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail, DecisionDocument, TodoState, OldTodo, DecisionCategory
+from models.database import (
+    Document, Protocol, Todo, Decision, TOP, MeetingReminder,
+    TodoMail, DecisionDocument, TodoState, OldTodo, DecisionCategory)
 from models.errors import DateNotMatchingException
 from server import celery, app
-from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, class_filter, KNOWN_KEYS, WikiType
-from utils import mail_manager, encode_kwargs, decode_kwargs, add_line_numbers, set_etherpad_text, get_etherpad_text, footnote_hash, parse_datetime_from_string
-from protoparser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork, RenderType
+from shared import (
+    db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long,
+    date_filter_short, time_filter, class_filter, KNOWN_KEYS, WikiType)
+from utils import (
+    mail_manager, add_line_numbers,
+    set_etherpad_text, parse_datetime_from_string)
+from protoparser import parse, ParserException, Tag, Remark, Fork, RenderType
 from wiki import WikiClient, WikiException
 from calendarpush import Client as CalendarClient, CalendarException
-from legacy import lookup_todo_id, import_old_todos
+from legacy import lookup_todo_id
 
 import config
 
@@ -32,7 +38,6 @@ texenv.filters["escape_tex"] = escape_tex
 texenv.filters["unhyphen"] = unhyphen
 texenv.trim_blocks = True
 texenv.lstrip_blocks = True
-#texenv.filters["url_complete"] = url_manager.complete
 texenv.filters["datify"] = date_filter
 texenv.filters["datify_long"] = date_filter_long
 texenv.filters["datify_short"] = date_filter_short
@@ -42,7 +47,9 @@ texenv.filters["class"] = class_filter
 logo_template = getattr(config, "LATEX_LOGO_TEMPLATE", None)
 if logo_template is not None:
     texenv.globals["logo_template"] = logo_template
-latex_geometry = getattr(config, "LATEX_GEOMETRY", "vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm")
+latex_geometry = getattr(
+    config, "LATEX_GEOMETRY",
+    "vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm")
 texenv.globals["latex_geometry"] = latex_geometry
 raw_additional_packages = getattr(config, "LATEX_ADDITIONAL_PACKAGES", None)
 additional_packages = []
@@ -59,51 +66,67 @@ latex_header_footer = getattr(config, "LATEX_HEADER_FOOTER", False)
 texenv.globals["latex_header_footer"] = latex_header_footer
 latex_templates = getattr(config, "LATEX_TEMPLATES", None)
 
+
 def provide_latex_template(template, documenttype):
-	_latex_template_documenttype_filename = {
-		"class": "protokoll2.cls",
-		"protocol": "protocol.tex",
-		"decision": "decision.tex"
-	}
-	_latex_template_filename = _latex_template_documenttype_filename[documenttype]
-	_latex_template_foldername = ""
-	if logo_template is not None:
-		texenv.globals["logo_template"] = logo_template
-	texenv.globals["latex_geometry"] = latex_geometry
-	texenv.globals["additional_packages"] = additional_packages
-	if latex_pagestyle is not None and latex_pagestyle:
-		texenv.globals["latex_pagestyle"] = latex_pagestyle
-	elif "latex_pagestyle" in texenv.globals:
-		del texenv.globals["latex_pagestyle"]
-	texenv.globals["latex_header_footer"] = latex_header_footer
-	if (latex_templates is not None) and (template is not ""):
-		if template in latex_templates:
-			if "provides" in latex_templates[template]:
-				_latex_template_foldername = (template + "/") if documenttype in latex_templates[template]["provides"] else ""
-			if "logo" in latex_templates[template]:
-				texenv.globals["logo_template"] = template + "/" + latex_templates[template]["logo"]
-			if "geometry" in latex_templates[template]:
-				texenv.globals["latex_geometry"] = latex_templates[template]["geometry"]
-			if "pagestyle" in latex_templates[template]:
-				if latex_templates[template]["pagestyle"]:
-					texenv.globals["latex_pagestyle"] = latex_templates[template]["pagestyle"]
-			if "additionalpackages" in latex_templates[template]:
-				_raw_additional_packages = latex_templates[template]["additionalpackages"]
-				_additional_packages = []
-				if _raw_additional_packages is not None:
-					for _package in _raw_additional_packages:
-						if "{" not in _package:
-							_package = "{{{}}}".format(_package)
-						_additional_packages.append(_package)
-				texenv.globals["additional_packages"] = _additional_packages	
-			if "headerfooter" in latex_templates[template]:
-				texenv.globals["latex_header_footer"] = latex_templates[template]["headerfooter"]
-	return _latex_template_foldername + _latex_template_filename
+    _DOCUMENTTYPE_FILENAME_MAP = {
+        "class": "protokoll2.cls",
+        "protocol": "protocol.tex",
+        "decision": "decision.tex"
+    }
+    _PROVIDES = "provides"
+    _LOGO_TEMPLATE = "logo_template"
+    _LOGO = "logo"
+    _LATEX_GEOMETRY = "latex_geometry"
+    _GEOMETRY = "geometry"
+    _ADDITIONAL_PACKAGES = "additional_packages"
+    _LATEX_PAGESTYLE = "latex_pagestyle"
+    _PAGESTYLE = "pagestyle"
+    _LATEX_HEADER_FOOTER = "latex_header_footer"
+    _HEADER_FOOTER = "headerfooter"
+    _latex_template_filename = _DOCUMENTTYPE_FILENAME_MAP[documenttype]
+    _latex_template_foldername = ""
+    if logo_template is not None:
+        texenv.globals[_LOGO_TEMPLATE] = logo_template
+    texenv.globals[_LATEX_GEOMETRY] = latex_geometry
+    texenv.globals[_ADDITIONAL_PACKAGES] = additional_packages
+    if latex_pagestyle:
+        texenv.globals[_LATEX_PAGESTYLE] = latex_pagestyle
+    elif _LATEX_PAGESTYLE in texenv.globals:
+        del texenv.globals[_LATEX_PAGESTYLE]
+    texenv.globals[_LATEX_HEADER_FOOTER] = latex_header_footer
+    if latex_templates is not None and template != "":
+        if template in latex_templates:
+            template_data = latex_templates[template]
+            if _PROVIDES in template_data:
+                if documenttype in template_data[_PROVIDES]:
+                    _latex_template_foldername = template
+            if _LOGO in template_data:
+                texenv.globals[_LOGO_TEMPLATE] = os.path.join(
+                    template, template_data[_LOGO])
+            if _GEOMETRY in template_data:
+                texenv.globals[_LATEX_GEOMETRY] = template_data[_GEOMETRY]
+            if _PAGESTYLE in template_data:
+                if template_data[_PAGESTYLE]:
+                    texenv.globals[_LATEX_PAGESTYLE] = (
+                        template_data[_PAGESTYLE])
+            if _ADDITIONAL_PACKAGES in template_data:
+                _raw_additional_packages = template_data[_ADDITIONAL_PACKAGES]
+                _additional_packages = []
+                if _raw_additional_packages is not None:
+                    for _package in _raw_additional_packages:
+                        if "{" not in _package:
+                            _package = "{{{}}}".format(_package)
+                        _additional_packages.append(_package)
+                texenv.globals[_ADDITIONAL_PACKAGES] = _additional_packages
+            if _HEADER_FOOTER in latex_templates[template]:
+                texenv.globals[_LATEX_HEADER_FOOTER] = (
+                    template_data[_HEADER_FOOTER])
+    return os.path.join(_latex_template_foldername, _latex_template_filename)
+
 
 mailenv = app.create_jinja_environment()
 mailenv.trim_blocks = True
 mailenv.lstrip_blocks = True
-#mailenv.filters["url_complete"] = url_manager.complete
 mailenv.filters["datify"] = date_filter
 mailenv.filters["datetimify"] = datetime_filter
 
@@ -124,43 +147,46 @@ wikienv.filters["timify"] = time_filter
 wikienv.filters["class"] = class_filter
 
 
+def _make_error(protocol, *args):
+    error = protocol.create_error(*args)
+    db.session.add(error)
+    db.session.commit()
+
+
 ID_FIELD_BEGINNING = "id "
 
-def parse_protocol(protocol, **kwargs):
-    parse_protocol_async.delay(protocol.id, encode_kwargs(kwargs))
+
+def parse_protocol(protocol):
+    parse_protocol_async.delay(protocol.id)
+
 
 @celery.task
-def parse_protocol_async(protocol_id, encoded_kwargs):
+def parse_protocol_async(protocol_id):
     with app.app_context():
         with app.test_request_context("/"):
             try:
-                kwargs = decode_kwargs(encoded_kwargs)
-                protocol = Protocol.query.filter_by(id=protocol_id).first()
+                protocol = Protocol.first_by_id(protocol_id)
                 if protocol is None:
                     raise Exception("No protocol given. Aborting parsing.")
-                parse_protocol_async_inner(protocol, encoded_kwargs)
+                parse_protocol_async_inner(protocol)
             except Exception as exc:
                 stacktrace = traceback.format_exc()
-                error = protocol.create_error("Parsing", "Exception",
+                return _make_error(
+                    protocol, "Parsing", "Exception",
                     "{}\n\n{}".format(str(exc), stacktrace))
-                db.session.add(error)
-                db.session.commit()
 
-def parse_protocol_async_inner(protocol, encoded_kwargs):
+
+def parse_protocol_async_inner(protocol):
     old_errors = list(protocol.errors)
     for error in old_errors:
         protocol.errors.remove(error)
     db.session.commit()
     if protocol.source is None or len(protocol.source.strip()) == 0:
-        error = protocol.create_error("Parsing", "Protocol source is empty", "")
-        db.session.add(error)
-        db.session.commit()
-        return
+        return _make_error(protocol, "Parsing", "Protocol source is empty", "")
     if protocol.source == config.EMPTY_ETHERPAD:
-        error = protocol.create_error("Parsing", "The etherpad is unmodified and does not contain a protocol.", protocol.source)
-        db.session.add(error)
-        db.session.commit()
-        return
+        return _make_error(
+            protocol, "Parsing", "The etherpad is unmodified and does not "
+            "contain a protocol.", protocol.source)
     tree = None
     try:
         tree = parse(protocol.source)
@@ -169,59 +195,64 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         if exc.linenumber is not None:
             source_lines = protocol.source.splitlines()
             start_index = max(0, exc.linenumber - config.ERROR_CONTEXT_LINES)
-            end_index = min(len(source_lines) - 1, exc.linenumber + config.ERROR_CONTEXT_LINES)
+            end_index = min(
+                len(source_lines) - 1,
+                exc.linenumber + config.ERROR_CONTEXT_LINES)
             context = "\n".join(source_lines[start_index:end_index])
         if exc.tree is not None:
             context += "\n\nParsed syntax tree was:\n" + str(exc.tree.dump())
-        error = protocol.create_error("Parsing", str(exc), context)
-        db.session.add(error)
-        db.session.commit()
-        return
-    remarks = {element.name: element for element in tree.children if isinstance(element, Remark)}
+        return _make_error(protocol, "Parsing", str(exc), context)
+    remarks = {
+        element.name: element
+        for element in tree.children
+        if isinstance(element, Remark)
+    }
     required_fields = copy(KNOWN_KEYS)
     for default_meta in protocol.protocoltype.metas:
         required_fields.append(default_meta.key)
     if not config.PARSER_LAZY:
-        missing_fields = [field for field in required_fields if field not in remarks]
+        missing_fields = [
+            field
+            for field in required_fields
+            if field not in remarks
+        ]
         if len(missing_fields) > 0:
-            error = protocol.create_error("Parsing", "Du hast vergessen, Metadaten anzugeben.", ", ".join(missing_fields))
-            db.session.add(error)
-            db.session.commit()
-            return
+            return _make_error(
+                protocol, "Parsing", "Du hast vergessen, Metadaten anzugeben.",
+                ", ".join(missing_fields))
     try:
         protocol.fill_from_remarks(remarks)
     except ValueError:
-        error = protocol.create_error(
-            "Parsing", "Invalid fields",
+        return _make_error(
+            protocol, "Parsing", "Invalid fields",
             "Date or time fields are not '%d.%m.%Y' respectively '%H:%M', "
             "but rather {}".format(
-            ", ".join([
-                remarks["Datum"].value.strip(),
-                remarks["Beginn"].value.strip(),
-                remarks["Ende"].value.strip()
-            ])))
-        db.session.add(error)
-        db.session.commit()
-        return
+                ", ".join([
+                    remarks["Datum"].value.strip(),
+                    remarks["Beginn"].value.strip(),
+                    remarks["Ende"].value.strip()
+                ])))
     except DateNotMatchingException as exc:
-        error = protocol.create_error("Parsing", "Date not matching",
-            "This protocol's date should be {}, but the protocol source says {}.".format(date_filter(exc.original_date) if exc.original_date is not None else "not present", date_filter(exc.protocol_date) if exc.protocol_date is not None else "not present"))
-        db.session.add(error)
-        db.session.commit()
-        return
-    # tags 
+        return _make_error(
+            protocol, "Parsing", "Date not matching",
+            "This protocol's date should be {}, but the protocol source "
+            "says {}.".format(
+                date_filter(exc.original_date)
+                if exc.original_date is not None
+                else "not present",
+                date_filter(exc.protocol_date)
+                if exc.protocol_date is not None
+                else "not present"))
+    # tags
     tags = tree.get_tags()
-    elements = tree.get_visible_elements(show_private=True)
     public_elements = tree.get_visible_elements(show_private=False)
     for tag in tags:
         if tag.name not in Tag.KNOWN_TAGS:
-            error = protocol.create_error("Parsing", "Invalid tag",
+            return _make_error(
+                protocol, "Parsing", "Invalid tag",
                 "The tag in line {} has the kind '{}', which is "
                 "not defined. This is probably an error mit a missing "
                 "semicolon.".format(tag.linenumber, tag.name))
-            db.session.add(error)
-            db.session.commit()
-            return
     # todos
     old_todo_number_map = {}
     for todo in protocol.todos:
@@ -233,14 +264,12 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
     raw_todos = []
     for todo_tag in todo_tags:
         if len(todo_tag.values) < 2:
-            error = protocol.create_error("Parsing", "Invalid todo-tag",
+            return _make_error(
+                protocol, "Parsing", "Invalid todo-tag",
                 "The todo tag in line {} needs at least "
                 "information on who and what, "
                 "but has less than that. This is probably "
                 "a missing semicolon.".format(todo_tag.linenumber))
-            db.session.add(error)
-            db.session.commit()
-            return
         who = todo_tag.values[0]
         what = todo_tag.values[1]
         field_id = None
@@ -254,39 +283,45 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
                 try:
                     field_id = int(other_field[len(ID_FIELD_BEGINNING):])
                 except ValueError:
-                    error = protocol.create_error("Parsing", "Non-numerical todo ID",
-                    "The todo in line {} has a nonnumerical ID, but needs "
-                    "something like \"id 1234\"".format(todo_tag.linenumber))
-                    db.session.add(error)
-                    db.session.commit()
-                    return
+                    return _make_error(
+                        protocol, "Parsing", "Non-numerical todo ID",
+                        "The todo in line {} has a nonnumerical ID, but needs "
+                        "something like \"id 1234\"".format(
+                            todo_tag.linenumber))
             else:
                 try:
                     field_state = TodoState.from_name(other_field)
+                    continue
+                except ValueError:
+                    pass
+                try:
+                    field_date = datetime.strptime(other_field, "%d.%m.%Y")
+                    continue
                 except ValueError:
-                    try:
-                        field_date = datetime.strptime(other_field, "%d.%m.%Y")
-                    except ValueError:
-                        try:
-                            field_state, field_date = TodoState.from_name_with_date(other_field.strip(), protocol=protocol)
-                        except ValueError:
-                            try:
-                                field_state = TodoState.from_name_lazy(other_field)
-                            except ValueError:
-                                error = protocol.create_error("Parsing",
-                                "Invalid field",
-                                "The todo in line {} has the field '{}', but "
-                                "this does neither match a date (\"%d.%m.%Y\") "
-                                "nor a state.".format(
-                                    todo_tag.linenumber, other_field))
-                                db.session.add(error)
-                                db.session.commit()
-                                return
-        raw_todos.append((who, what, field_id, field_state, field_date, todo_tag))
+                    pass
+                try:
+                    field_state, field_date = TodoState.from_name_with_date(
+                        other_field.strip(), protocol=protocol)
+                    continue
+                except ValueError:
+                    pass
+                try:
+                    field_state = TodoState.from_name_lazy(other_field)
+                except ValueError:
+                    return _make_error(
+                        protocol, "Parsing", "Invalid field",
+                        "The todo in line {} has the field '{}', but "
+                        "this does neither match a date (\"%d.%m.%Y\") "
+                        "nor a state.".format(
+                            todo_tag.linenumber, other_field))
+        raw_todos.append(
+            (who, what, field_id, field_state, field_date, todo_tag))
     for (_, _, field_id, _, _, _) in raw_todos:
         if field_id is not None:
-            old_todos = [todo for todo in old_todos
-                if todo.id != field_id]
+            old_todos = [
+                todo for todo in old_todos
+                if todo.id != field_id
+            ]
     for todo in old_todos:
         protocol.todos.remove(todo)
     db.session.commit()
@@ -294,28 +329,23 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         if field_state is None:
             field_state = TodoState.open
         if field_state.needs_date() and field_date is None:
-            error = protocol.create_error("Parsing",
-                "Todo missing date",
+            return _make_error(
+                protocol, "Parsing", "Todo missing date",
                 "The todo in line {} has a state that needs a date, "
                 "but the todo does not have one.".format(todo_tag.linenumber))
-            db.session.add(error)
-            db.session.commit()
-            return
         who = who.strip()
         what = what.strip()
         todo = None
         if field_id is not None:
             todo = Todo.query.filter_by(number=field_id).first()
             if todo is None and not config.PARSER_LAZY:
-                error = protocol.create_error("Parsing",
-                "Invalid Todo ID",
-                "The todo in line {} has the ID {}, but there is no "
-                "Todo with that ID.".format(todo_tag.linenumber, field_id))
-                db.session.add(error)
-                db.session.commit()
-                return
+                return _make_error(
+                    protocol, "Parsing", "Invalid Todo ID",
+                    "The todo in line {} has the ID {}, but there is no "
+                    "Todo with that ID.".format(todo_tag.linenumber, field_id))
         if todo is None and field_id is None and what in old_todo_number_map:
-            todo = Todo(protocoltype_id=protocol.protocoltype.id,
+            todo = Todo(
+                protocoltype_id=protocol.protocoltype.id,
                 who=who, description=what, state=field_state,
                 date=field_date, number=old_todo_number_map[what])
             db.session.add(todo)
@@ -326,7 +356,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
                 OldTodo.protocol_key == protocol_key).all()
             if len(old_candidates) == 0:
                 # new protocol
-                todo = Todo(protocoltype_id=protocol.protocoltype.id,
+                todo = Todo(
+                    protocoltype_id=protocol.protocoltype.id,
                     who=who, description=what, state=field_state,
                     date=field_date)
                 db.session.add(todo)
@@ -338,7 +369,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
                 number = field_id or lookup_todo_id(old_candidates, who, what)
                 todo = Todo.query.filter_by(number=number).first()
                 if todo is None:
-                    todo = Todo(protocoltype_id=protocol.protocoltype.id,
+                    todo = Todo(
+                        protocoltype_id=protocol.protocoltype.id,
                         who=who, description=what, state=field_state,
                         date=field_date, number=number)
                     db.session.add(todo)
@@ -360,13 +392,12 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
     decision_tags = [tag for tag in tags if tag.name == "beschluss"]
     for decision_tag in decision_tags:
         if decision_tag not in public_elements:
-            error = protocol.create_error("Parsing", "Decision in private context.",
-                "The decision in line {} is in a private context, but decisions are "
-                "and have to be public. Please move it to a public spot.".format(
-                decision_tag.linenumber))
-            db.session.add(error)
-            db.session.commit()
-            return
+            return _make_error(
+                protocol, "Parsing", "Decision in private context.",
+                "The decision in line {} is in a private context, but "
+                "decisions are and have to be public. "
+                "Please move it to a public spot.".format(
+                    decision_tag.linenumber))
     old_decisions = list(protocol.decisions)
     for decision in old_decisions:
         protocol.decisions.remove(decision)
@@ -374,36 +405,35 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
     decisions_to_render = []
     for decision_tag in decision_tags:
         if len(decision_tag.values) == 0:
-            error = protocol.create_error("Parsing", "Empty decision found.",
-                "The decision in line {} is empty.".format(decision_tag.linenumber))
-            db.session.add(error)
-            db.session.commit()
-            return
+            return _make_error(
+                protocol, "Parsing", "Empty decision found.",
+                "The decision in line {} is empty.".format(
+                    decision_tag.linenumber))
         decision_content = decision_tag.values[0]
         decision_categories = []
         for decision_category_name in decision_tag.values[1:]:
-            decision_category = DecisionCategory.query.filter_by(protocoltype_id=protocol.protocoltype.id, name=decision_category_name).first()
+            decision_category = DecisionCategory.query.filter_by(
+                protocoltype_id=protocol.protocoltype.id,
+                name=decision_category_name).first()
             if decision_category is None:
-                category_candidates = DecisionCategory.query.filter_by(protocoltype_id=protocol.protocoltype.id).all()
+                category_candidates = DecisionCategory.query.filter_by(
+                    protocoltype_id=protocol.protocoltype.id).all()
                 category_names = [
                     "'{}'".format(category.name)
                     for category in category_candidates
                 ]
-                error = protocol.create_error("Parsing",
-                    "Unknown decision category",
+                return _make_error(
+                    protocol, "Parsing", "Unknown decision category",
                     "The decision in line {} has the category {}, "
                     "but there is no such category. "
                     "Known categories are {}".format(
                         decision_tag.linenumber,
                         decision_category_name,
                         ", ".join(category_names)))
-                db.session.add(error)
-                db.session.commit()
-                return
             else:
                 decision_categories.append(decision_category)
-        decision = Decision(protocol_id=protocol.id,
-            content=decision_content)
+        decision = Decision(
+            protocol_id=protocol.id, content=decision_content)
         db.session.add(decision)
         db.session.commit()
         for decision_category in decision_categories:
@@ -412,66 +442,67 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         decisions_to_render.append((decision, decision_tag))
     for decision, decision_tag in decisions_to_render:
         decision_top = decision_tag.fork.get_top()
-        decision_content = texenv.get_template(provide_latex_template(protocol.protocoltype.latex_template, "decision")).render(
-            render_type=RenderType.latex, decision=decision,
-            protocol=protocol, top=decision_top, show_private=True)
+        decision_content = texenv.get_template(provide_latex_template(
+            protocol.protocoltype.latex_template, "decision")).render(
+                render_type=RenderType.latex, decision=decision,
+                protocol=protocol, top=decision_top, show_private=True)
         maxdepth = decision_top.get_maxdepth()
         compile_decision(decision_content, decision, maxdepth=maxdepth)
 
     # Footnotes
-    footnote_tags = [tag for tag in tags if tag.name == "footnote"]
-    public_footnote_tags = [tag for tag in footnote_tags if tag in public_elements]
+    footnote_tags = [
+        tag for tag in tags
+        if tag.name == "footnote"
+    ]
+    public_footnote_tags = [
+        tag for tag in footnote_tags
+        if tag in public_elements
+    ]
 
     # new Protocols
     protocol_tags = [tag for tag in tags if tag.name == "sitzung"]
     for protocol_tag in protocol_tags:
         if len(protocol_tag.values) not in {1, 2}:
-            error = protocol.create_error("Parsing",
-                "Falsche Verwendung von [sitzung;…].",
+            return _make_error(
+                protocol, "Parsing", "Falsche Verwendung von [sitzung;…].",
                 "Der Tag \"sitzung\" benötigt immer ein Datum "
                 "und optional eine Uhrzeit, also ein bis zwei Argumente. "
                 "Stattdessen wurden {} übergeben, nämlich {}".format(
-                len(protocol_tag.values),
-                protocol_tag.values))
-            db.session.add(error)
-            db.ession.commit()
-            return
+                    len(protocol_tag.values),
+                    protocol_tag.values))
         else:
             try:
-                protocol_date = parse_datetime_from_string(
-                    protocol_tag.values[0])
+                parse_datetime_from_string(protocol_tag.values[0])
             except ValueError as exc:
-                error = protocol.create_error("Parsing", "Invalides Datum",
+                return _make_error(
+                    protocol, "Parsing", "Invalides Datum",
                     "'{}' ist kein valides Datum.".format(
                         protocol_tag.values[0]))
-                db.session.add(error)
-                db.session.commit()
-                return
             if len(protocol_tag.values) > 1:
                 try:
-                    protocol_time = datetime.strptime(protocol_tag.values[1], "%H:%M")
+                    datetime.strptime(protocol_tag.values[1], "%H:%M")
                 except ValueError:
-                    error = protocol.create_error("Parsing", "Invalide Uhrzeit",
+                    return _make_error(
+                        protocol, "Parsing", "Invalide Uhrzeit",
                         "'{}' ist keine valide Uhrzeit.".format(
                             protocol_tag.values[1]))
-                    db.session.add(error)
-                    db.session.commit()
-                    return
     for protocol_tag in protocol_tags:
         new_protocol_date = parse_datetime_from_string(protocol_tag.values[0])
         new_protocol_time = None
         if len(protocol_tag.values) > 1:
-            new_protocol_time = datetime.strptime(protocol_tag.values[1], "%H:%M")
-        Protocol.create_new_protocol(protocol.protocoltype,
-            new_protocol_date, new_protocol_time)
+            new_protocol_time = datetime.strptime(
+                protocol_tag.values[1], "%H:%M")
+        Protocol.create_new_protocol(
+            protocol.protocoltype, new_protocol_date, new_protocol_time)
 
     # TOPs
     old_tops = list(protocol.tops)
     for top in old_tops:
         protocol.tops.remove(top)
-    tops = []
-    for index, fork in enumerate((child for child in tree.children if isinstance(child, Fork))):
-        top = TOP(protocol_id=protocol.id, name=fork.name, number=index,
+    for index, fork in enumerate(
+            (child for child in tree.children if isinstance(child, Fork))):
+        top = TOP(
+            protocol_id=protocol.id, name=fork.name, number=index,
             planned=False)
         db.session.add(top)
     db.session.commit()
@@ -485,26 +516,35 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
     public_render_kwargs = copy(private_render_kwargs)
     public_render_kwargs["footnotes"] = public_footnote_tags
     render_kwargs = {True: private_render_kwargs, False: public_render_kwargs}
-    
+
     maxdepth = tree.get_maxdepth()
     privacy_states = [False]
-    content_private = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=True, **private_render_kwargs)
-    content_public = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=False, **public_render_kwargs)
+    content_private = render_template(
+        "protocol.txt", render_type=RenderType.plaintext, show_private=True,
+        **private_render_kwargs)
+    content_public = render_template(
+        "protocol.txt", render_type=RenderType.plaintext, show_private=False,
+        **public_render_kwargs)
     if content_private != content_public:
         privacy_states.append(True)
     protocol.content_private = content_private
     protocol.content_public = content_public
-    protocol.content_html_private = render_template("protocol.html",
-        render_type=RenderType.html, show_private=True, **private_render_kwargs)
-    protocol.content_html_public = render_template("protocol.html",
-        render_type=RenderType.html, show_private=False, **public_render_kwargs)
+    protocol.content_html_private = render_template(
+        "protocol.html", render_type=RenderType.html, show_private=True,
+        **private_render_kwargs)
+    protocol.content_html_public = render_template(
+        "protocol.html", render_type=RenderType.html, show_private=False,
+        **public_render_kwargs)
 
     for show_private in privacy_states:
-        latex_source = texenv.get_template(provide_latex_template(protocol.protocoltype.latex_template, "protocol")).render(
-            render_type=RenderType.latex,
-            show_private=show_private,
-            **render_kwargs[show_private])
-        compile(latex_source, protocol, show_private=show_private, maxdepth=maxdepth)
+        latex_source = texenv.get_template(provide_latex_template(
+            protocol.protocoltype.latex_template, "protocol")).render(
+                render_type=RenderType.latex,
+                show_private=show_private,
+                **render_kwargs[show_private])
+        compile(
+            latex_source, protocol, show_private=show_private,
+            maxdepth=maxdepth)
 
     if protocol.protocoltype.use_wiki:
         wiki_type = WikiType[getattr(config, "WIKI_TYPE", "MEDIAWIKI")]
@@ -525,17 +565,21 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         if wiki_type == WikiType.MEDIAWIKI:
             wiki_infobox_source = wikienv.get_template("infobox.wiki").render(
                 protocoltype=protocol.protocoltype)
-            push_to_wiki(protocol, wiki_source, wiki_infobox_source,
+            push_to_wiki(
+                protocol, wiki_source, wiki_infobox_source,
                 "Automatisch generiert vom Protokollsystem 3.0")
         elif wiki_type == WikiType.DOKUWIKI:
-            push_to_dokuwiki(protocol, wiki_source,
+            push_to_dokuwiki(
+                protocol, wiki_source,
                 "Automatisch generiert vom Protokollsystem 3.0")
     protocol.done = True
     db.session.commit()
 
+
 def push_to_wiki(protocol, content, infobox_content, summary):
     push_to_wiki_async.delay(protocol.id, content, infobox_content, summary)
 
+
 @celery.task
 def push_to_wiki_async(protocol_id, content, infobox_content, summary):
     with WikiClient() as wiki_client, app.app_context():
@@ -550,40 +594,48 @@ def push_to_wiki_async(protocol_id, content, infobox_content, summary):
                 content=content,
                 summary=summary)
         except WikiException as exc:
-            error = protocol.create_error("Pushing to Wiki", "Pushing to Wiki failed.", str(exc))
-            db.session.add(error)
-            db.session.commit()
+            return _make_error(
+                protocol, "Pushing to Wiki", "Pushing to Wiki failed.",
+                str(exc))
+
 
 def push_to_dokuwiki(protocol, content, summary):
     push_to_dokuwiki_async.delay(protocol.id, content, summary)
 
+
 @celery.task
 def push_to_dokuwiki_async(protocol_id, content, summary):
     with app.app_context():
         protocol = Protocol.query.filter_by(id=protocol_id).first()
         with xmlrpc.client.ServerProxy(config.WIKI_API_URL) as proxy:
             try:
-                if not proxy.wiki.putPage(protocol.get_wiki_title(),
-                    content, {"sum": "Automatisch generiert vom Protokollsystem 3."}):
-                    error = protocol.create_error("Pushing to Wiki",
+                if not proxy.wiki.putPage(
+                    protocol.get_wiki_title(), content,
+                    {"sum":
+                        "Automatisch generiert vom Protokollsystem 3."}):
+                    return _make_error(
+                        protocol, "Pushing to Wiki",
                         "Pushing to Wiki failed." "")
-                    db.session.add(error)
-                    db.session.commit()
             except xmlrpc.client.Error as exception:
-                error = protocol.create_error("Pushing to Wiki",
-                    "XML RPC Exception",
+                return _make_error(
+                    protocol, "Pushing to Wiki", "XML RPC Exception",
                     str(exception))
-                db.session.add(error)
-                db.session.commit()
+
 
 def compile(content, protocol, show_private, maxdepth):
-   compile_async.delay(content, protocol.id, show_private=show_private, maxdepth=maxdepth)
+    compile_async.delay(
+        content, protocol.id, show_private=show_private, maxdepth=maxdepth)
+
 
 def compile_decision(content, decision, maxdepth):
-    compile_async.delay(content, decision.id, use_decision=True, maxdepth=maxdepth)
+    compile_async.delay(
+        content, decision.id, use_decision=True, maxdepth=maxdepth)
+
 
 @celery.task
-def compile_async(content, protocol_id, show_private=False, use_decision=False, maxdepth=5):
+def compile_async(
+        content, protocol_id, show_private=False, use_decision=False,
+        maxdepth=5):
     with tempfile.TemporaryDirectory() as compile_dir, app.app_context():
         decision = None
         protocol = None
@@ -598,10 +650,19 @@ def compile_async(content, protocol_id, show_private=False, use_decision=False,
             protocol_target_filename = "protocol.pdf"
             protocol_class_filename = "protokoll2.cls"
             log_filename = "protocol.log"
-            with open(os.path.join(compile_dir, protocol_source_filename), "w") as source_file:
+            with open(
+                    os.path.join(compile_dir, protocol_source_filename),
+                    "w") as source_file:
                 source_file.write(content)
-            protocol2_class_source = texenv.get_template(provide_latex_template(protocol.protocoltype.latex_template, "class")).render(fonts=config.FONTS, maxdepth=maxdepth, bulletpoints=config.LATEX_BULLETPOINTS)
-            with open(os.path.join(compile_dir, protocol_class_filename), "w") as protocol2_class_file:
+            protocol2_class_source = texenv.get_template(
+                provide_latex_template(
+                    protocol.protocoltype.latex_template,
+                    "class")).render(
+                fonts=config.FONTS, maxdepth=maxdepth,
+                bulletpoints=config.LATEX_BULLETPOINTS)
+            with open(
+                os.path.join(compile_dir, protocol_class_filename),
+                    "w") as protocol2_class_file:
                 protocol2_class_file.write(protocol2_class_source)
             os.chdir(compile_dir)
             command = [
@@ -610,15 +671,23 @@ def compile_async(content, protocol_id, show_private=False, use_decision=False,
                 "-file-line-error",
                 protocol_source_filename
             ]
-            subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-            subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+            subprocess.check_call(
+                command, universal_newlines=True, stdout=subprocess.DEVNULL,
+                stderr=subprocess.DEVNULL)
+            subprocess.check_call(
+                command, universal_newlines=True, stdout=subprocess.DEVNULL,
+                stderr=subprocess.DEVNULL)
             os.chdir(current)
             document = None
             if not use_decision:
-                for old_document in [document for document in protocol.documents if document.is_compiled and document.is_private == show_private]:
+                for old_document in [
+                    document for document in protocol.documents
+                    if document.is_compiled
+                        and document.is_private == show_private]:
                     protocol.documents.remove(old_document)
                 db.session.commit()
-                document = Document(protocol_id=protocol.id,
+                document = Document(
+                    protocol_id=protocol.id,
                     name="protokoll{}_{}_{}.pdf".format(
                         "_intern" if show_private else "",
                         protocol.protocoltype.short_name,
@@ -627,7 +696,8 @@ def compile_async(content, protocol_id, show_private=False, use_decision=False,
                     is_compiled=True,
                     is_private=show_private)
             else:
-                document = DecisionDocument(decision_id=decision.id,
+                document = DecisionDocument(
+                    decision_id=decision.id,
                     name="beschluss_{}_{}_{}.pdf".format(
                         protocol.protocoltype.short_name,
                         date_filter_short(protocol.date),
@@ -635,48 +705,60 @@ def compile_async(content, protocol_id, show_private=False, use_decision=False,
                     filename="")
             db.session.add(document)
             db.session.commit()
-            target_filename = "compiled-{}-{}.pdf".format(document.id, "internal" if show_private else "public")
+            target_filename = "compiled-{}-{}.pdf".format(
+                document.id, "internal" if show_private else "public")
             if use_decision:
-                target_filename = "decision-{}-{}-{}.pdf".format(protocol.id, decision.id, document.id)
+                target_filename = "decision-{}-{}-{}.pdf".format(
+                    protocol.id, decision.id, document.id)
             document.filename = target_filename
-            shutil.copy(os.path.join(compile_dir, protocol_target_filename), os.path.join(config.DOCUMENTS_PATH, target_filename))
+            shutil.copy(
+                os.path.join(compile_dir, protocol_target_filename),
+                os.path.join(config.DOCUMENTS_PATH, target_filename))
             db.session.commit()
-            shutil.copy(os.path.join(compile_dir, log_filename), "/tmp/proto-tex.log")
+            shutil.copy(
+                os.path.join(compile_dir, log_filename),
+                "/tmp/proto-tex.log")
         except subprocess.SubprocessError:
             log = ""
             total_log_filename = os.path.join(compile_dir, log_filename)
-            total_source_filename = os.path.join(compile_dir, protocol_source_filename)
+            total_source_filename = os.path.join(
+                compile_dir, protocol_source_filename)
             log = ""
             if os.path.isfile(total_source_filename):
                 with open(total_source_filename, "r") as source_file:
                     log += "Source:\n\n" + add_line_numbers(source_file.read())
-            total_class_filename = os.path.join(compile_dir, protocol_class_filename)
+            total_class_filename = os.path.join(
+                compile_dir, protocol_class_filename)
             if os.path.isfile(total_class_filename):
                 with open(total_class_filename, "r") as class_file:
-                    log += "\n\nClass:\n\n" + add_line_numbers(class_file.read())
+                    log += "\n\nClass:\n\n" + add_line_numbers(
+                        class_file.read())
             if os.path.isfile(total_log_filename):
                 with open(total_log_filename, "r") as log_file:
                     log += "\n\nLog:\n\n" + add_line_numbers(log_file.read())
             else:
                 log += "\n\nLogfile not found."
-            error = protocol.create_error("Compiling", "Compiling LaTeX failed", log)
-            db.session.add(error)
-            db.session.commit()
+            _make_error(protocol, "Compiling", "Compiling LaTeX failed", log)
         finally:
             os.chdir(current)
 
+
 def print_file(filename, protocol):
     if config.PRINTING_ACTIVE:
         print_file_async.delay(filename, protocol.id)
 
+
 @celery.task
 def print_file_async(filename, protocol_id):
     with app.app_context():
         protocol = Protocol.query.filter_by(id=protocol_id).first()
         if protocol.protocoltype.printer is None:
-            error = protocol.create_error("Printing", "No printer configured.", "You don't have any printer configured for the protocoltype {}. Please do so before printing a protocol.".format(protocol.protocoltype.name))
-            db.session.add(error)
-            db.session.commit()
+            return _make_error(
+                protocol, "Printing", "No printer configured.",
+                "You don't have any printer configured for the "
+                "protocoltype {}. "
+                "Please do so before printing a protocol.".format(
+                    protocol.protocoltype.name))
         try:
             command = [
                 "/usr/bin/lpr",
@@ -685,70 +767,96 @@ def print_file_async(filename, protocol_id):
                 "-U", config.PRINTING_USER,
                 "-T", protocol.get_identifier(),
             ]
-            for option in config.PRINTING_PRINTERS[protocol.protocoltype.printer]:
-                command.extend(["-o", '"{}"'.format(option) if " " in option else option])
+            for option in config.PRINTING_PRINTERS[
+                    protocol.protocoltype.printer]:
+                command.extend([
+                    "-o", '"{}"'.format(option)
+                    if " " in option else option])
             command.append(filename)
-            subprocess.check_output(command, universal_newlines=True, stderr=subprocess.STDOUT)
+            subprocess.check_output(
+                command, universal_newlines=True, stderr=subprocess.STDOUT)
         except subprocess.SubprocessError as exception:
-            error = protocol.create_error("Printing", "Printing {} failed.".format(protocol.get_identifier()), exception.stdout)
-            db.session.add(error)
-            db.session.commit()
+            return _make_error(
+                protocol, "Printing", "Printing {} failed.".format(
+                    protocol.get_identifier()), exception.stdout)
+
 
 def send_reminder(reminder, protocol):
     send_reminder_async.delay(reminder.id, protocol.id)
 
+
 @celery.task
 def send_reminder_async(reminder_id, protocol_id):
     with app.app_context():
         reminder = MeetingReminder.query.filter_by(id=reminder_id).first()
         protocol = Protocol.query.filter_by(id=protocol_id).first()
-        reminder_text = render_template("reminder-mail.txt", reminder=reminder, protocol=protocol)
+        reminder_text = render_template(
+            "reminder-mail.txt", reminder=reminder, protocol=protocol)
         if reminder.send_public:
-            print("sending public reminder mail to {}".format(protocol.protocoltype.public_mail))
-            send_mail(protocol, protocol.protocoltype.public_mail,
+            send_mail(
+                protocol, protocol.protocoltype.public_mail,
                 "Tagesordnung der {}".format(protocol.protocoltype.name),
                 reminder_text, reply_to=protocol.protocoltype.public_mail)
         if reminder.send_private:
-            print("sending private reminder mail to {}".format(protocol.protocoltype.private_mail))
-            send_mail(protocol, protocol.protocoltype.private_mail,
+            send_mail(
+                protocol, protocol.protocoltype.private_mail,
                 "Tagesordnung der {}".format(protocol.protocoltype.name),
                 reminder_text, reply_to=protocol.protocoltype.private_mail)
 
+
 def remind_finishing(protocol, delay_days, min_delay_days):
     remind_finishing_async.delay(protocol.id, delay_days, min_delay_days)
 
+
 @celery.task
 def remind_finishing_async(protocol_id, delay_days, min_delay_days):
     with app.app_context():
-        protocol = Protocol.query.filter_by(id=protocol_id).first()
-        mail_text = render_template("remind-finishing-mail.txt",
+        protocol = Protocol.first_by_id(protocol_id)
+        mail_text = render_template(
+            "remind-finishing-mail.txt",
             protocol=protocol, delay_days=delay_days,
             min_delay_days=min_delay_days)
-        send_mail(protocol, protocol.protocoltype.private_mail,
+        send_mail(
+            protocol, protocol.protocoltype.private_mail,
             "Unfertiges Protokoll der {}".format(protocol.protocoltype.name),
             mail_text, reply_to=protocol.protocoltype.private_mail)
 
+
 def send_protocol_private(protocol):
     send_protocol_async.delay(protocol.id, show_private=True)
     send_todomails_async.delay(protocol.id)
 
+
 def send_protocol_public(protocol):
     send_protocol_async.delay(protocol.id, show_private=False)
 
+
 @celery.task
 def send_protocol_async(protocol_id, show_private):
     with app.app_context():
         protocol = Protocol.query.filter_by(id=protocol_id).first()
-        next_protocol = Protocol.query.filter_by(protocoltype_id=protocol.protocoltype.id).filter_by(done=False).filter(Protocol.date > datetime.now()).order_by(Protocol.date).first()
-        to_addr = protocol.protocoltype.private_mail if show_private else protocol.protocoltype.public_mail
-        subject = "{}{}-Protokoll vom {}".format("Internes " if show_private else "", protocol.protocoltype.short_name, date_filter(protocol.date))
-        mail_content = render_template("protocol-mail.txt", protocol=protocol, show_private=show_private, next_protocol=next_protocol)
-        appendix = [(document.name, document.as_file_like())
+        next_protocol = Protocol.query.filter_by(
+            protocoltype_id=protocol.protocoltype.id).filter_by(
+            done=False).filter(
+            Protocol.date > datetime.now()).order_by(Protocol.date).first()
+        to_addr = (
+            protocol.protocoltype.private_mail
+            if show_private
+            else protocol.protocoltype.public_mail)
+        subject = "{}{}-Protokoll vom {}".format(
+            "Internes " if show_private else "",
+            protocol.protocoltype.short_name, date_filter(protocol.date))
+        mail_content = render_template(
+            "protocol-mail.txt", protocol=protocol, show_private=show_private,
+            next_protocol=next_protocol)
+        appendix = [
+            (document.name, document.as_file_like())
             for document in protocol.documents
             if show_private or not document.is_private
         ]
         send_mail(protocol, to_addr, subject, mail_content, appendix)
 
+
 @celery.task
 def send_todomails_async(protocol_id):
     with app.app_context():
@@ -764,7 +872,8 @@ def send_todomails_async(protocol_id):
             for user in users
         }
         subject = "Du hast noch was zu tun!"
-        todomail_providers = getattr(config, "ADDITIONAL_TODOMAIL_PROVIDERS", None)
+        todomail_providers = getattr(
+            config, "ADDITIONAL_TODOMAIL_PROVIDERS", None)
         additional_todomails = {}
         if todomail_providers:
             for provider in todomail_providers:
@@ -779,33 +888,42 @@ def send_todomails_async(protocol_id):
                 if user in additional_todomails:
                     todomail = additional_todomails[user]
             if todomail is None:
-                error = protocol.create_error("Sending Todomail", "Sending Todomail failed.", "User {} has no Todo-Mail-Assignment.".format(user))
-                db.session.add(error)
-                db.session.commit()
+                _make_error(
+                    protocol, "Sending Todomail", "Sending Todomail failed.",
+                    "User {} has no Todo-Mail-Assignment.".format(user))
                 continue
             to_addr = todomail.get_formatted_mail()
-            mail_content = render_template("todo-mail.txt", protocol=protocol, todomail=todomail, todos=grouped_todos[user])
-            send_mail(protocol, to_addr, subject, mail_content,
+            mail_content = render_template(
+                "todo-mail.txt", protocol=protocol, todomail=todomail,
+                todos=grouped_todos[user])
+            send_mail(
+                protocol, to_addr, subject, mail_content,
                 reply_to=protocol.protocoltype.private_mail)
 
-def send_mail(protocol, to_addr, subject, content, appendix=None, reply_to=None):
+
+def send_mail(protocol, to_addr, subject, content, appendix=None,
+              reply_to=None):
     if to_addr is not None and len(to_addr.strip()) > 0:
-        send_mail_async.delay(protocol.id, to_addr, subject, content, appendix, reply_to)
+        send_mail_async.delay(
+            protocol.id, to_addr, subject, content, appendix, reply_to)
+
 
 @celery.task
-def send_mail_async(protocol_id, to_addr, subject, content, appendix, reply_to):
+def send_mail_async(protocol_id, to_addr, subject, content, appendix,
+                    reply_to):
     with app.app_context():
         protocol = Protocol.query.filter_by(id=protocol_id).first()
         try:
             mail_manager.send(to_addr, subject, content, appendix, reply_to)
         except Exception as exc:
-            error = protocol.create_error("Sending Mail", "Sending mail failed", str(exc))
-            db.session.add(error)
-            db.session.commit()
+            return _make_error(
+                protocol, "Sending Mail", "Sending mail failed", str(exc))
+
 
 def push_tops_to_calendar(protocol):
     push_tops_to_calendar_async.delay(protocol.id)
 
+
 @celery.task
 def push_tops_to_calendar_async(protocol_id):
     if not config.CALENDAR_ACTIVE:
@@ -817,21 +935,22 @@ def push_tops_to_calendar_async(protocol_id):
         description = render_template("calendar-tops.txt", protocol=protocol)
         try:
             client = CalendarClient(protocol.protocoltype.calendar)
-            client.set_event_at(begin=protocol.get_datetime(),
+            client.set_event_at(
+                begin=protocol.get_datetime(),
                 name=protocol.protocoltype.short_name, description=description)
         except CalendarException as exc:
-            error = protocol.create_error("Calendar",
+            return _make_error(
+                protocol, "Calendar",
                 "Pushing TOPs to Calendar failed", str(exc))
-            db.session.add(error)
-            db.session.commit()
+
 
 def set_etherpad_content(protocol):
     set_etherpad_content_async.delay(protocol.id)
 
+
 @celery.task
 def set_etherpad_content_async(protocol_id):
     with app.app_context():
         protocol = Protocol.query.filter_by(id=protocol_id).first()
         identifier = protocol.get_identifier()
         return set_etherpad_text(identifier, protocol.get_template())
-    
diff --git a/templates/decision.tex b/templates/decision.tex
index 9a79b296a0bf5cecb98083733009d95db9362e23..5399e3da56fcec5c0fafe18710540dbffee45b7c 100644
--- a/templates/decision.tex
+++ b/templates/decision.tex
@@ -5,7 +5,9 @@
 \usepackage{pdfpages}
 \usepackage{eurosym}
 %\usepackage[utf8]{inputenc}
-\usepackage[pdfborder={0 0 0}]{hyperref}
+\usepackage[hyphens]{url}
+\usepackage[pdfborder={0 0 0},breaklinks=true]{hyperref}
+\def\UrlBreaks{\do\/\do-\do\&\do.\do,\do;\do\_\do?\do\#}
 %\usepackage{ngerman}
 % \usepackage[left]{lineno}
 %\usepackage{footnote}
diff --git a/templates/document-edit.html b/templates/document-edit.html
index 7b1689e7170c53d394d95f91b16606b46ae75d13..d3d30fa7571514901199a50f121cd1c8402cf1c6 100644
--- a/templates/document-edit.html
+++ b/templates/document-edit.html
@@ -4,6 +4,6 @@
 
 {% block content %}
 <div class="container">
-    {{render_form(form, action_url=url_for("edit_document", document_id=document.id, next=request.args.get("next") or url_for("show_protocol", protocol_id=document.protocol.id)), action_text="Ändern")}}
+    {{render_form(form, action_url=url_for("edit_document", document_id=document.id), action_text="Ändern")}}
 </div>
 {% endblock %}
diff --git a/templates/layout.html b/templates/layout.html
index 8cf4490de01d5598060bf10153b595631f986402..2496975f2b295f028bf7c3bc4e60ea3affb9642d 100644
--- a/templates/layout.html
+++ b/templates/layout.html
@@ -50,7 +50,7 @@
             </ul>
             <ul class="nav navbar-nav navbar-right">
                 {% if check_login() %}
-                <li><a href="{{url_for("logout")}}">Logout</a></li>
+                <li><a href="{{url_for("logout", csrf_token=get_csrf_token())}}">Logout</a></li>
                 {% else %}
                 <li><a href="{{url_for("login")}}">Login</a></li>
                 {% endif %}
diff --git a/templates/localtop-edit.html b/templates/localtop-edit.html
index c320d727b1b248718899be1aa2eadbe66fa517e0..df048b35b5b001172d7e3bc1854cf5b7bc74d25c 100644
--- a/templates/localtop-edit.html
+++ b/templates/localtop-edit.html
@@ -5,6 +5,6 @@
 {% block content %}
 <div class="container">
     <h3>{{localtop.defaulttop.name}}</h3>
-    {{render_form(form, action_url=url_for("edit_localtop", localtop_id=localtop.id, next=request.args.get("next") or url_for("show_protocol", protocol_id=localtop.protocol.id)), action_text="Ändern", textarea_rows=5)}}
+    {{render_form(form, action_url=url_for("edit_localtop", localtop_id=localtop.id), action_text="Ändern", textarea_rows=5)}}
 </div>
 {% endblock %}
diff --git a/templates/macros.html b/templates/macros.html
index 0408a6c06eaba8a19b65b4f06d895fd30d5abc94..e6d0edd7c26a03c329daaccd5abd0e6f33815956 100644
--- a/templates/macros.html
+++ b/templates/macros.html
@@ -175,7 +175,7 @@ to not render a label for the CRSFTokenField -->
             {% set verb = "likes" %}
         {% endif %}
         {% if add_link %}
-        <a href="{{url_for("new_like", next=request.url, **kwargs)}}">
+        <a href="{{url_for("new_like", csrf_token=get_csrf_token(), **kwargs)}}">
         {% endif %}
         <div class="likes-div">
             <p>{{likes|length}} <span class="like-sign">&#x1f44d;</span></p>
diff --git a/templates/protocol-show.html b/templates/protocol-show.html
index d4d99ade2f6afd87fa1affbd6d1b5fa7bc9c80fb..ba615a8260f9bb0158c176c04bbffb21a03a09c6 100644
--- a/templates/protocol-show.html
+++ b/templates/protocol-show.html
@@ -16,7 +16,7 @@
         <div class="btn-group">
             {% if has_modify_right %}
                 {% if config.ETHERPAD_ACTIVE and not protocol.public %}
-                <a class="btn {% if protocol.source is none %}btn-primary{% else %}btn-default{% endif %}" href="{{url_for("etherpull_protocol", protocol_id=protocol.id)}}">Aus Etherpad</a>
+                <a class="btn {% if protocol.source is none %}btn-primary{% else %}btn-default{% endif %}" href="{{url_for("etherpull_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}">Aus Etherpad</a>
                 {% endif %}
                 {% if protocol.source is not none %}
                     <a class="btn btn-primary" href="{{url_for("get_protocol_source", protocol_id=protocol.id)}}">Quelltext</a>
@@ -26,23 +26,23 @@
                 {% endif %}
                 {% if not protocol.public %}
                     {% if config.ETHERPAD_ACTIVE %}
-                    <a class="btn btn-primary" href="{{url_for("etherpush_protocol", protocol_id=protocol.id)}}"{% if large_time_diff %} onclick="return confirm('Bist du dir sicher, dass du das Template bereits in das Etherpad kopieren willst? Die Sitzung ist erst {% if time_diff.days != 1 %}in {{time_diff.days}} Tagen{% else %}morgen{% endif %}.');"{% endif %} target="_blank">Etherpad</a>
+                    <a class="btn btn-primary" href="{{url_for("etherpush_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}"{% if large_time_diff %} onclick="return confirm('Bist du dir sicher, dass du das Template bereits in das Etherpad kopieren willst? Die Sitzung ist erst {% if time_diff.days != 1 %}in {{time_diff.days}} Tagen{% else %}morgen{% endif %}.');"{% endif %} target="_blank">Etherpad</a>
                     {% endif %}
                 {% endif %}
                 {% if not protocol.is_done() %}
                     <a class="btn btn-default" href="{{url_for("get_protocol_template", protocol_id=protocol.id)}}">Vorlage</a>
                     {% if config.MAIL_ACTIVE %}
-                        <a class="btn btn-default" href="{{url_for("send_protocol_reminder", protocol_id=protocol.id)}}" onclick="return confirm('Bist du dir sicher, dass du manuell eine Einladung verschicken willst? Dies wird auch automatisch geschehen.');">Einladung versenden</a>
+                        <a class="btn btn-default" href="{{url_for("send_protocol_reminder", protocol_id=protocol.id, csrf_token=get_csrf_token())}}" onclick="return confirm('Bist du dir sicher, dass du manuell eine Einladung verschicken willst? Dies wird auch automatisch geschehen.');">Einladung versenden</a>
                     {% endif %}
                 {% else %}
                     {% if config.MAIL_ACTIVE %}
-                        <a class="btn btn-default" href="{{url_for("send_protocol_private", protocol_id=protocol.id)}}">Intern versenden</a>
+                        <a class="btn btn-default" href="{{url_for("send_protocol_private", protocol_id=protocol.id, csrf_token=get_csrf_token())}}">Intern versenden</a>
                         {% if protocol.public %}
-                            <a class="btn btn-default" href="{{url_for("send_protocol_public", protocol_id=protocol.id)}}">Öffentlich versenden</a>
+                            <a class="btn btn-default" href="{{url_for("send_protocol_public", protocol_id=protocol.id, csrf_token=get_csrf_token())}}">Öffentlich versenden</a>
                         {% endif %}
                     {% endif %}
                     {% if not protocol.public %}
-                        <a class="btn btn-default" href="{{url_for("publish_protocol", protocol_id=protocol.id)}}">Veröffentlichen</a>
+                        <a class="btn btn-default" href="{{url_for("publish_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}">Veröffentlichen</a>
                     {% endif %}
                 {% endif %}
                 <a class="btn btn-default" href="{{url_for("show_type", protocoltype_id=protocol.protocoltype.id)}}">Typ</a>
@@ -50,7 +50,7 @@
                     <a class="btn btn-success" href="{{url_for("download_document", document_id=protocol.get_compiled_document().id)}}">Download</a>
                 {% endif %}
                 {% if has_admin_right %}
-            <a class="btn btn-default" href="{{url_for("recompile_protocol", protocol_id=protocol.id)}}">Neu kompilieren</a>
+            <a class="btn btn-default" href="{{url_for("recompile_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}">Neu kompilieren</a>
             <a class="btn btn-danger" href="{{url_for("delete_protocol", protocol_id=protocol.id)}}" onclick="return confirm('Bist du dir sicher, dass du das Protokoll {{protocol.get_short_identifier()}} löschen möchtest?');">Löschen</a>
                 {% endif %}
             {% endif %}
@@ -104,7 +104,7 @@
                                 <li>
                                     {{decision.content}}
                                     {% if config.PRINTING_ACTIVE and has_private_view_right and decision.document is not none %}
-                                        <a href="{{url_for("print_decision", decisiondocument_id=decision.document.id)}}">Drucken</a>
+                                        <a href="{{url_for("print_decision", decisiondocument_id=decision.document.id, csrf_token=get_csrf_token())}}">Drucken</a>
                                     {% endif %}
                                     {{render_likes(decision.likes, decision_id=decision.id)}}</h2>
                                 </li>
diff --git a/templates/protocol-tops-include.html b/templates/protocol-tops-include.html
index 6be2c79f2fd726683af93a9e5132e2828f921ec4..1d9fbc21aaa11dc08148f41c011f1189fbd0e7d0 100644
--- a/templates/protocol-tops-include.html
+++ b/templates/protocol-tops-include.html
@@ -27,9 +27,9 @@
             {% endif %}
             {% if not protocol.is_done() and has_modify_right %}
                 <a href="{{url_for('edit_top', top_id=top.id)}}">Ändern</a>
-                <a href="{{url_for('move_top', top_id=top.id, diff=1)}}">Runter</a>
-                <a href="{{url_for('move_top', top_id=top.id, diff=-1)}}">Hoch</a>
-                <a href="{{url_for('delete_top', top_id=top.id)}}" onclick="return confirm('Bist du dir sicher, dass du den TOP {{top.name}} löschen möchtest?');">Löschen</a>
+                <a href="{{url_for('move_top', top_id=top.id, diff=1, csrf_token=get_csrf_token())}}">Runter</a>
+                <a href="{{url_for('move_top', top_id=top.id, diff=-1, csrf_token=get_csrf_token())}}">Hoch</a>
+                <a href="{{url_for('delete_top', top_id=top.id, csrf_token=get_csrf_token())}}" onclick="return confirm('Bist du dir sicher, dass du den TOP {{top.name}} löschen möchtest?');">Löschen</a>
             {% endif %}
             {% if has_private_view_right and top.description is not none and top.description|length > 0 %}
                 <span class="glyphicon glyphicon-info-sign"></span>
diff --git a/templates/top-edit.html b/templates/top-edit.html
index 07d56be8f257d106a9e9da490a3e2a266a5286a3..adabf99a3e46b5ceb8d63c988e97dd9b7a8f6e16 100644
--- a/templates/top-edit.html
+++ b/templates/top-edit.html
@@ -4,6 +4,6 @@
 
 {% block content %}
 <div class="container">
-    {{render_form(form, action_url=url_for("edit_top", top_id=top.id, next=request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id)), action_text="Ändern", textarea_rows=5)}}
+    {{render_form(form, action_url=url_for("edit_top", top_id=top.id), action_text="Ändern", textarea_rows=5)}}
 </div>
 {% endblock %}
diff --git a/utils.py b/utils.py
index 95baf755c9a03172d0b8a9771a8f6860b0c0cadc..5e80b9cbebc40ae8cacf6cff9201fa12c1577e17 100644
--- a/utils.py
+++ b/utils.py
@@ -1,28 +1,34 @@
-from flask import render_template, request
+from flask import request, session
 
 import random
 import string
-import regex
 import math
 import smtplib
 from email.mime.multipart import MIMEMultipart
 from email.mime.text import MIMEText
 from email.mime.application import MIMEApplication
-from datetime import datetime, date, timedelta
+from datetime import datetime
 import requests
 from io import BytesIO
 import ipaddress
 from socket import getfqdn
 from uuid import uuid4
+import subprocess
+import os
+import hashlib
 
 import config
 
+
 def random_string(length):
-    return "".join((random.choice(string.ascii_letters) for i in range(length)))
+    return "".join((random.choice(string.ascii_letters)
+                    for i in range(length)))
+
 
 def is_past(some_date):
     return (datetime.now() - some_date).total_seconds() > 0
 
+
 def encode_kwargs(kwargs):
     encoded_kwargs = {}
     for key in kwargs:
@@ -33,6 +39,7 @@ def encode_kwargs(kwargs):
             encoded_kwargs[key] = (type(value), value, False)
     return encoded_kwargs
 
+
 def decode_kwargs(encoded_kwargs):
     kwargs = {}
     for name in encoded_kwargs:
@@ -42,28 +49,7 @@ def decode_kwargs(encoded_kwargs):
         else:
             kwargs[name] = id
     return kwargs
-        
 
-class UrlManager:
-    def __init__(self, config):
-        self.pattern = regex.compile(r"(?:(?<proto>https?):\/\/)?(?<hostname>[[:alnum:]_.]+(?:\:[[:digit:]]+)?)?(?<path>(?:\/[[:alnum:]_#]*)+)?(?:\?(?<params>.*))?")
-        self.base = "{}://{}{}{}"
-        self.proto = getattr(config, "URL_PROTO", "https")
-        self.root = getattr(config, "URL_ROOT", "example.com")
-        self.path = getattr(config, "URL_PATH", "/")
-        self.params = getattr(config, "URL_PARAMS", "")
-
-    def complete(self, url):
-        match = self.pattern.match(url)
-        if match is None:
-            return None
-        proto = match.group("proto") or self.proto
-        root = match.group("hostname") or self.root
-        path = match.group("path") or self.path
-        params = match.group("params") or self.params
-        return self.base.format(proto, root, path, "?" + params if len(params) > 0 else "")
-
-#url_manager = UrlManager(config)
 
 class MailManager:
     def __init__(self, config):
@@ -75,12 +61,17 @@ class MailManager:
         self.use_tls = getattr(config, "MAIL_USE_TLS", True)
         self.use_starttls = getattr(config, "MAIL_USE_STARTTLS", False)
 
+    def _get_smtp(self):
+        if self.use_tls:
+            return smtplib.SMTP_SSL
+        return smtplib.SMTP
+
     def send(self, to_addr, subject, content, appendix=None, reply_to=None):
         if (not self.active
-            or not self.hostname
-            or not self.from_addr):
+                or not self.hostname
+                or not self.from_addr):
             return
-        msg = MIMEMultipart("mixed") # todo: test if clients accept attachment-free mails set to multipart/mixed
+        msg = MIMEMultipart("mixed")
         msg["From"] = self.from_addr
         msg["To"] = to_addr
         msg["Subject"] = subject
@@ -91,9 +82,10 @@ class MailManager:
         if appendix is not None:
             for name, file_like in appendix:
                 part = MIMEApplication(file_like.read(), "octet-stream")
-                part["Content-Disposition"] = 'attachment; filename="{}"'.format(name)
+                part["Content-Disposition"] = (
+                    'attachment; filename="{}"'.format(name))
                 msg.attach(part)
-        server = (smtplib.SMTP_SSL if self.use_tls else smtplib.SMTP)(self.hostname)
+        server = self._get_smtp()(self.hostname)
         if self.use_starttls:
             server.starttls()
         if self.username not in [None, ""] and self.password not in [None, ""]:
@@ -101,36 +93,48 @@ class MailManager:
         server.sendmail(self.from_addr, to_addr.split(","), msg.as_string())
         server.quit()
 
+
 mail_manager = MailManager(config)
 
+
 def get_first_unused_int(numbers):
     positive_numbers = [number for number in numbers if number >= 0]
     if len(positive_numbers) == 0:
         return 0
     highest = max(positive_numbers)
-    for given, linear in zip(positive_numbers, range(highest+1)):
+    for given, linear in zip(positive_numbers, range(highest + 1)):
         if linear < given:
             return linear
     return highest + 1
 
+
 def normalize_pad(pad):
     return pad.replace(" ", "_")
+
+
 def get_etherpad_url(pad):
     return "{}/p/{}".format(config.ETHERPAD_URL, normalize_pad(pad))
+
+
 def get_etherpad_export_url(pad):
     return "{}/p/{}/export/txt".format(config.ETHERPAD_URL, normalize_pad(pad))
+
+
 def get_etherpad_import_url(pad):
     return "{}/p/{}/import".format(config.ETHERPAD_URL, normalize_pad(pad))
 
+
 def get_etherpad_text(pad):
     req = requests.get(get_etherpad_export_url(pad))
     return req.text
 
+
 def set_etherpad_text(pad, text, only_if_default=True):
     print(pad)
     if only_if_default:
         current_text = get_etherpad_text(pad)
-        if current_text != config.EMPTY_ETHERPAD and len(current_text.strip()) > 0:
+        if (current_text != config.EMPTY_ETHERPAD
+                and len(current_text.strip()) > 0):
             return False
     file_like = BytesIO(text.encode("utf-8"))
     files = {"file": file_like}
@@ -138,7 +142,8 @@ def set_etherpad_text(pad, text, only_if_default=True):
     print(url)
     req = requests.post(url, files=files)
     return req.status_code == 200
-    
+
+
 def split_terms(text, quote_chars="\"'", separators=" \t\n"):
     terms = []
     in_quote = False
@@ -168,26 +173,30 @@ def split_terms(text, quote_chars="\"'", separators=" \t\n"):
         terms.append(current_term)
     return terms
 
+
 def optional_int_arg(name):
     try:
         return int(request.args.get(name))
     except (ValueError, TypeError):
         return None
 
+
 def add_line_numbers(text):
     raw_lines = text.splitlines()
     linenumber_length = math.ceil(math.log10(len(raw_lines)) + 1)
     lines = []
     for linenumber, line in enumerate(raw_lines):
         lines.append("{} {}".format(
-            str(linenumber+1).rjust(linenumber_length),
+            str(linenumber + 1).rjust(linenumber_length),
             line
         ))
     return "\n".join(lines)
 
+
 def check_ip_in_networks(networks_string):
     address = ipaddress.ip_address(request.remote_addr)
-    if address == ipaddress.ip_address("127.0.0.1") and "X-Real-Ip" in request.headers:
+    if (address == ipaddress.ip_address("127.0.0.1")
+            and "X-Real-Ip" in request.headers):
         address = ipaddress.ip_address(request.headers["X-Real-Ip"])
     try:
         for network_string in networks_string.split(","):
@@ -198,6 +207,7 @@ def check_ip_in_networks(networks_string):
     except ValueError:
         return False
 
+
 def fancy_join(values, sep1=" und ", sep2=", "):
     values = list(values)
     if len(values) <= 1:
@@ -206,20 +216,53 @@ def fancy_join(values, sep1=" und ", sep2=", "):
     start = values[:-1]
     return "{}{}{}".format(sep2.join(start), sep1, last)
 
+
 def footnote_hash(text, length=5):
     return str(sum(ord(c) * i for i, c in enumerate(text)) % 10**length)
 
+
 def parse_datetime_from_string(text):
     text = text.strip()
     for format in ("%d.%m.%Y", "%d.%m.%y", "%Y-%m-%d",
-        "%d. %B %Y", "%d. %b %Y", "%d. %B %y", "%d. %b %y"):
+                   "%d. %B %Y", "%d. %b %Y", "%d. %B %y", "%d. %b %y"):
         try:
             return datetime.strptime(text, format)
         except ValueError:
             pass
     for format in ("%d.%m.", "%d. %m.", "%d.%m", "%d.%m"):
         try:
-            return datetime.strptime(text, format).replace(year=datetime.now().year)
-        except ValueError as exc:
-            print(exc)
+            return datetime.strptime(text, format).replace(
+                year=datetime.now().year)
+        except ValueError:
+            pass
     raise ValueError("Date '{}' does not match any known format!".format(text))
+
+
+def get_git_revision():
+    try:
+        gitlab_url = "https://git.fsmpi.rwth-aachen.de/protokollsystem/proto3"
+        commit_hash = subprocess.check_output(
+            ["git", "log", "-g", "-1", "--pretty=%H"]).decode("UTF-8").strip()
+        timestamp = int(subprocess.check_output(
+            ["git", "log", "-g", "-1", "--pretty=%at"]).strip())
+        commit_date = datetime.fromtimestamp(timestamp)
+        return {"url": gitlab_url, "hash": commit_hash, "date": commit_date}
+    except subprocess.SubprocessError:
+        pass
+
+
+def get_max_page_length_exp(objects):
+    length = len(objects)
+    if length > 0:
+        return math.ceil(math.log10(length))
+    return 1
+
+
+def get_internal_filename(protocol, document, filename):
+    return "{}-{}-{}".format(protocol.id, document.id, filename)
+
+
+def get_csrf_token():
+    if "_csrf" not in session:
+        session["_csrf"] = hashlib.sha1(os.urandom(64)).hexdigest()
+    return session["_csrf"]
diff --git a/validators.py b/validators.py
index 36847e467f957d0bf43ddeae26c33df3dd061fe8..4e262bf7a04e84188083cb25f4d1694f56dd82bb 100644
--- a/validators.py
+++ b/validators.py
@@ -1,7 +1,7 @@
 from models.database import TodoState
 from wtforms import ValidationError
 from wtforms.validators import InputRequired
-from shared import db
+
 
 class CheckTodoDateByState:
     def __init__(self):
@@ -16,4 +16,3 @@ class CheckTodoDateByState:
                 date_check(form, form.date)
         except ValueError:
             raise ValidationError("Invalid state.")
-
diff --git a/views/forms.py b/views/forms.py
index 7ec3a3f63c0b1b677ce025370710b5d36ee9cc4e..843cae1b4d949b60fbcab5b41d704f8fe325722a 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -1,5 +1,7 @@
 from flask_wtf import FlaskForm
-from wtforms import StringField, PasswordField, BooleanField, HiddenField, IntegerField, SelectField, FileField, DateTimeField, TextAreaField, Field, widgets, FormField
+from wtforms import (
+    StringField, PasswordField, BooleanField, IntegerField, SelectField,
+    FileField, DateTimeField, TextAreaField, Field, FormField, widgets)
 from wtforms.fields.html5 import DateField
 from wtforms.validators import InputRequired, Optional
 
@@ -11,6 +13,7 @@ from shared import current_user
 
 import config
 
+
 def get_protocoltype_choices(protocoltypes, add_all=True):
     choices = [
         (protocoltype.id, protocoltype.short_name)
@@ -21,6 +24,7 @@ def get_protocoltype_choices(protocoltypes, add_all=True):
         choices.insert(0, (-1, "Alle Typen"))
     return choices
 
+
 def get_category_choices(categories, add_all=True):
     choices = [
         (category.id, category.name)
@@ -31,12 +35,14 @@ def get_category_choices(categories, add_all=True):
         choices.insert(0, (-1, "Alle Kategorien"))
     return choices
 
+
 def get_todostate_choices():
     return [
         (state, state.get_name())
         for state in TodoState
     ]
 
+
 def get_calendar_choices(protocoltype=None):
     from calendarpush import Client as CalendarClient
     calendars = CalendarClient().get_calendars()
@@ -46,19 +52,21 @@ def get_calendar_choices(protocoltype=None):
         choices = list(zip(calendars, calendars))
     else:
         if (protocoltype is not None
-        and protocoltype.calendar is not None
-        and protocoltype.calendar != ""):
+                and protocoltype.calendar is not None
+                and protocoltype.calendar != ""):
             choices.append((protocoltype.calendar, protocoltype.calendar))
     choices.insert(0, ("", "Kein Kalender"))
     return choices
 
+
 def get_printer_choices():
     choices = []
     if config.PRINTING_PRINTERS is not None:
         choices = list(zip(config.PRINTING_PRINTERS, config.PRINTING_PRINTERS))
     choices.insert(0, ("", "Nicht drucken"))
     return choices
-	
+
+
 def get_latex_template_choices():
     choices = []
     _latex_templates = getattr(config, "LATEX_TEMPLATES", None)
@@ -71,6 +79,7 @@ def get_latex_template_choices():
     choices.insert(0, ("", "Standardvorlage"))
     return choices
 
+
 def get_group_choices():
     user = current_user()
     groups = sorted(user.groups)
@@ -78,12 +87,14 @@ def get_group_choices():
     choices.insert(0, ("", "Keine Gruppe"))
     return choices
 
+
 def coerce_todostate(key):
     if isinstance(key, str):
         class_part, key_part = key.split(".")
         key = TodoState[key_part]
     return key
 
+
 class IPNetworkField(Field):
     widget = widgets.TextInput()
 
@@ -109,30 +120,53 @@ class IPNetworkField(Field):
             except ValueError as exc:
                 print(exc)
                 self.data = None
-                raise ValueError(self.gettext("Not a valid IP Network: {}".format(str(exc))))
+                raise ValueError(
+                    self.gettext("Not a valid IP Network: {}".format(
+                        str(exc))))
             self.data = ",".join(map(str, result_parts))
 
+
 class FocusedStringField(StringField):
     def __call__(self, **kwargs):
         kwargs['autofocus'] = True
         return super().__call__(**kwargs)
 
+
 class LoginForm(FlaskForm):
-    username = FocusedStringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
-    password = PasswordField("Passwort", validators=[InputRequired("Bitte gib dein Passwort ein.")])
+    username = FocusedStringField(
+        "Benutzer",
+        validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
+    password = PasswordField(
+        "Passwort",
+        validators=[InputRequired("Bitte gib dein Passwort ein.")])
     permanent = BooleanField("Eingeloggt bleiben?")
 
+
 class ProtocolTypeForm(FlaskForm):
-    name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")])
-    short_name = StringField("Abkürzung", validators=[InputRequired("Du musst eine Abkürzung angebene.")])
-    organization = StringField("Organisation", validators=[InputRequired("Du musst eine zugehörige Organisation angeben.")])
-    usual_time = DateTimeField("Üblicher Beginn", validators=[InputRequired("Bitte gib die Zeit an, zu der die Sitzung beginnt.")], format="%H:%M")
+    name = StringField(
+        "Name",
+        validators=[InputRequired("Du musst einen Namen angeben.")])
+    short_name = StringField(
+        "Abkürzung",
+        validators=[InputRequired("Du musst eine Abkürzung angebene.")])
+    organization = StringField(
+        "Organisation",
+        validators=[
+            InputRequired("Du musst eine zugehörige Organisation angeben.")
+        ])
+    usual_time = DateTimeField(
+        "Üblicher Beginn",
+        validators=[
+            InputRequired("Bitte gib die Zeit an, zu der die Sitzung beginnt.")
+        ],
+        format="%H:%M")
     is_public = BooleanField("Öffentlich sichtbar")
     publish_group = SelectField("Verwaltungsgruppe", choices=[])
     modify_group = SelectField("Bearbeitungsgruppe", choices=[])
     private_group = SelectField("Interne Gruppe", choices=[])
     public_group = SelectField("Öffentliche Gruppe", choices=[])
-    non_reproducible_pad_links = BooleanField("nicht nachvollziehbare Etherpad-Links")
+    non_reproducible_pad_links = BooleanField(
+        "nicht nachvollziehbare Etherpad-Links")
     private_mail = StringField("Interner Verteiler")
     public_mail = StringField("Öffentlicher Verteiler")
     wiki_category = StringField("Wiki-Kategorie")
@@ -156,44 +190,65 @@ class ProtocolTypeForm(FlaskForm):
         self.private_group.choices = group_choices
         self.public_group.choices = group_choices
 
+
 class DefaultTopForm(FlaskForm):
-    name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")])
-    number = IntegerField("Nummer", validators=[InputRequired("Du musst eine Nummer angeben.")])
+    name = StringField(
+        "Name",
+        validators=[InputRequired("Du musst einen Namen angeben.")])
+    number = IntegerField(
+        "Priorität",
+        validators=[InputRequired("Du musst eine Priorität angeben.")])
     description = TextAreaField("Standardinhalt")
 
+
 class MeetingReminderForm(FlaskForm):
-    days_before = IntegerField("Tage vor Sitzung", validators=[InputRequired("Du musst eine Dauer angeben.")])
+    days_before = IntegerField(
+        "Tage vor Sitzung",
+        validators=[InputRequired("Du musst eine Dauer angeben.")])
     send_public = BooleanField("Öffentlich einladen")
     send_private = BooleanField("Intern einladen")
     additional_text = TextAreaField("Zusätzlicher Mailinhalt")
 
+
 class NewProtocolForm(FlaskForm):
     protocoltype_id = SelectField("Typ", choices=[], coerce=int)
-    date = DateField("Datum", validators=[InputRequired("Du musst ein Datum angeben.")])
-    start_time = DateTimeField("Uhrzeit (HH:MM, optional)", validators=[Optional()], format="%H:%M")
+    date = DateField(
+        "Datum",
+        validators=[InputRequired("Du musst ein Datum angeben.")])
+    start_time = DateTimeField(
+        "Uhrzeit (HH:MM, optional)",
+        validators=[Optional()],
+        format="%H:%M")
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes, add_all=False)
+        self.protocoltype_id.choices = get_protocoltype_choices(
+            protocoltypes, add_all=False)
+
 
 class DocumentEditForm(FlaskForm):
     name = StringField("Dateiname")
     is_private = BooleanField("Intern")
 
+
 class DocumentUploadForm(FlaskForm):
     document = FileField("Datei")
     is_private = BooleanField("Intern")
 
+
 class KnownProtocolSourceUploadForm(FlaskForm):
     source = FileField("Quellcode")
 
+
 class NewProtocolSourceUploadForm(FlaskForm):
     source = FileField("Quellcode")
     protocoltype_id = SelectField("Typ", choices=[], coerce=int)
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes, add_all=False)
+        self.protocoltype_id.choices = get_protocoltype_choices(
+            protocoltypes, add_all=False)
+
 
 class NewProtocolFileUploadForm(FlaskForm):
     file = FileField("Datei")
@@ -202,31 +257,46 @@ class NewProtocolFileUploadForm(FlaskForm):
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes, add_all=False)
+        self.protocoltype_id.choices = get_protocoltype_choices(
+            protocoltypes, add_all=False)
+
 
 def generate_protocol_form(protocol):
     class ProtocolMetasForm(FlaskForm):
         pass
     for meta in protocol.metas:
         setattr(ProtocolMetasForm, meta.name, StringField(meta.name))
+
     class ProtocolForm(FlaskForm):
-        date = DateField("Datum", validators=[InputRequired("Bitte gib das Datum des Protkolls an.")])
-        start_time = DateTimeField("Beginn (%H:%M)", format="%H:%M", validators=[Optional()])
-        end_time = DateTimeField("Ende (%H:%M)", format="%H:%M", validators=[Optional()])
+        date = DateField(
+            "Datum",
+            validators=[
+                InputRequired("Bitte gib das Datum des Protkolls an.")
+            ])
+        start_time = DateTimeField(
+            "Beginn (%H:%M)", format="%H:%M", validators=[Optional()])
+        end_time = DateTimeField(
+            "Ende (%H:%M)", format="%H:%M", validators=[Optional()])
         metas = FormField(ProtocolMetasForm)
         done = BooleanField("Fertig")
         public = BooleanField("Veröffentlicht")
     return ProtocolForm
-    
+
 
 class TopForm(FlaskForm):
-    name = StringField("TOP", validators=[InputRequired("Du musst den Namen des TOPs angeben.")])
-    number = IntegerField("Sortierung", validators=[InputRequired("Du musst eine Sortierung in der Reihenfolge angebene.")])
+    name = StringField(
+        "TOP",
+        validators=[InputRequired("Du musst den Namen des TOPs angeben.")])
+    number = IntegerField(
+        "Priorität",
+        validators=[InputRequired("Du musst eine Priorität angeben.")])
     description = TextAreaField("Beschreibung")
 
+
 class LocalTopForm(FlaskForm):
     description = TextAreaField("Beschreibung")
 
+
 class SearchForm(FlaskForm):
     search = StringField("Suchbegriff")
     protocoltype_id = SelectField("Typ", choices=[], coerce=int)
@@ -235,6 +305,7 @@ class SearchForm(FlaskForm):
         super().__init__(**kwargs)
         self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes)
 
+
 class DecisionSearchForm(SearchForm):
     decisioncategory_id = SelectField("Kategorie", choices=[], coerce=int)
 
@@ -242,52 +313,104 @@ class DecisionSearchForm(SearchForm):
         super().__init__(protocoltypes=protocoltypes, **kwargs)
         self.decisioncategory_id.choices = get_category_choices(categories)
 
+
 class ProtocolSearchForm(SearchForm):
-    state_open = SelectField("Offen", choices=[(-1, "Alle"), (0, "Geplant"), (1, "Fertig")], coerce=int)
+    state_open = SelectField(
+        "Offen",
+        choices=[(-1, "Alle"), (0, "Geplant"), (1, "Fertig")],
+        coerce=int)
+
 
 class TodoSearchForm(SearchForm):
-    state_open = SelectField("Offen", choices=[(-1, "Alle"), (0, "Offen"), (1, "Erledigt")], coerce=int)
+    state_open = SelectField(
+        "Offen",
+        choices=[(-1, "Alle"), (0, "Offen"), (1, "Erledigt")],
+        coerce=int)
+
 
 class NewTodoForm(FlaskForm):
     protocoltype_id = SelectField("Typ", choices=[], coerce=int)
-    who = StringField("Person", validators=[InputRequired("Bitte gib an, wer das Todo erledigen soll.")])
-    description = StringField("Aufgabe", validators=[InputRequired("Bitte gib an, was erledigt werden soll.")])
-    state = SelectField("Status", choices=[], coerce=coerce_todostate, validators=[CheckTodoDateByState()])
-    date = DateField("Datum)", validators=[Optional()])
-    
+    who = StringField(
+        "Person",
+        validators=[
+            InputRequired("Bitte gib an, wer das Todo erledigen soll.")
+        ])
+    description = StringField(
+        "Aufgabe", validators=[
+            InputRequired("Bitte gib an, was erledigt werden soll.")
+        ])
+    state = SelectField(
+        "Status",
+        choices=[],
+        coerce=coerce_todostate,
+        validators=[CheckTodoDateByState()])
+    date = DateField("Datum", validators=[Optional()])
+
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes, add_all=False)
+        self.protocoltype_id.choices = get_protocoltype_choices(
+            protocoltypes, add_all=False)
         self.state.choices = get_todostate_choices()
 
+
 class TodoForm(FlaskForm):
     who = StringField("Person")
-    description = StringField("Aufgabe", validators=[InputRequired("Bitte gib an, was erledigt werden soll.")])
-    state = SelectField("Status", choices=[], coerce=coerce_todostate, validators=[CheckTodoDateByState()])
+    description = StringField(
+        "Aufgabe",
+        validators=[InputRequired("Bitte gib an, was erledigt werden soll.")])
+    state = SelectField(
+        "Status",
+        choices=[],
+        coerce=coerce_todostate,
+        validators=[CheckTodoDateByState()])
     date = DateField("Datum", validators=[Optional()])
 
     def __init__(self, **kwargs):
         super().__init__(**kwargs)
         self.state.choices = get_todostate_choices()
 
+
 class TodoMailForm(FlaskForm):
-    name = StringField("Name", validators=[InputRequired("Du musst den Namen angeben, der zugeordnet werden soll.")])
-    mail = StringField("Mail", validators=[InputRequired("Du musst die Mailadresse angeben, die zugeordnet werden soll.")])
+    name = StringField(
+        "Name",
+        validators=[
+            InputRequired("Du musst den Namen angeben, der zugeordnet werden "
+                          "soll.")])
+    mail = StringField(
+        "Mail",
+        validators=[
+            InputRequired("Du musst die Mailadresse angeben, die zugeordnet "
+                          "werden soll.")])
+
 
 class MetaForm(FlaskForm):
-    name = StringField("Name", validators=[InputRequired("Bitte gib den Namen der Metadaten an.")])
+    name = StringField(
+        "Name",
+        validators=[InputRequired("Bitte gib den Namen der Metadaten an.")])
     value = StringField("Wert")
     internal = BooleanField("Intern")
 
+
 class DefaultMetaForm(FlaskForm):
-    key = StringField("Key", validators=[InputRequired("Bitte gib den Protokoll-Syntax-Schlüssel der Metadaten an.")])
-    name = StringField("Name", validators=[InputRequired("Bitte gib den Namen der Metadaten an.")])
+    key = StringField(
+        "Key",
+        validators=[
+            InputRequired("Bitte gib den Protokoll-Syntax-Schlüssel der "
+                          "Metadaten an.")
+        ])
+    name = StringField(
+        "Name",
+        validators=[InputRequired("Bitte gib den Namen der Metadaten an.")])
     value = StringField("Standardwert")
     internal = BooleanField("Intern")
     prior = BooleanField("Planungsrelevant")
 
+
 class DecisionCategoryForm(FlaskForm):
-    name = StringField("Name", validators=[InputRequired("Bitte gib den Namen der Kategorie an.")])
+    name = StringField(
+        "Name",
+        validators=[InputRequired("Bitte gib den Namen der Kategorie an.")])
+
 
 class MergeTodosForm(FlaskForm):
     todo1 = IntegerField("todo 1", validators=[InputRequired()])
diff --git a/views/tables.py b/views/tables.py
index 8b59ea0fc5de52d255fb5e6d3d5bec63da03771e..e3f60f581c0c7a6e44ef31506be60da537e425bc 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -1,10 +1,10 @@
-# coding: utf-8
-from flask import Markup, url_for, request
-from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decision, Meta, DefaultMeta
-from shared import date_filter, datetime_filter, date_filter_short, current_user, check_login 
+from flask import Markup, url_for
+from shared import date_filter, datetime_filter, time_filter, current_user
+from utils import get_csrf_token
 
 import config
 
+
 class Table:
     def __init__(self, title, values, newlink=None, newtext=None):
         self.title = title
@@ -13,31 +13,53 @@ class Table:
         self.newtext = newtext or "Neu"
 
     def rows(self):
-        return [row for row in [self.row(value) for value in self.values] if row is not None]
+        return [
+            row for row in [self.row(value) for value in self.values]
+            if row is not None]
 
     def classes(self):
         return [None for header in self.headers()]
 
     @staticmethod
-    def link(target, text, confirm=None):
-        confirmation = ""
+    def link(target, text, confirm=None, css_class=None):
+        attributes = [
+            "href=\"{}\"".format(target)
+        ]
         if confirm:
-            confirmation = " onclick=\"return confirm('{}');\"".format(confirm)
-        return Markup("<a href=\"{}\"{}>{}</a>".format(target, confirmation, text))
+            attributes.append(
+                "onclick=\"return confirm('{}');\"".format(confirm))
+        if css_class:
+            attributes.append("class=\"{}\"".format(css_class))
+        return Markup("<a {}>{}</a>".format(" ".join(attributes), text))
 
     @staticmethod
-    def button(target, icon, style, confirm=None):
-        confirmation = ""
-        if confirm:
-            confirmation = " onclick=\"return confirm('{}');\"".format(confirm)
+    def glyphicon(name, text=None):
+        if text is None:
+            text = ""
+        else:
+            text = " {}".format(text)
         return Markup(
-                '''<a href="{target}" class="btn btn-{style}" {confirmation}>
-                        <span class="glyphicon glyphicon-{icon}"></span>
-                   </a>'''.format(target=target, style=style, confirmation=confirmation, icon=icon))
-    
+            "<span class=\"glyphicon glyphicon-{}\"></span>{}".format(
+                name, text))
+
+    @staticmethod
+    def button(target, icon, style, confirm=None):
+        return Table.link(
+            target=target,
+            text=Table.glyphicon(icon),
+            css_class="btn btn-{}".format(style))
+
+    @staticmethod
+    def button_group(buttons, size="xs"):
+        return Markup("".join([
+            Markup("<div class=\"btn-group btn-group-{}\">").format(size),
+            "".join(buttons),
+            Markup("</div>"),
+        ]))
+
     @staticmethod
     def mail(target):
-        return Markup("<a href=\"mailto:{}\">{}</a>".format(target, target))
+        return Table.link("mailto:{}".format(target), target)
 
     @staticmethod
     def bool(value):
@@ -46,11 +68,11 @@ class Table:
     @staticmethod
     def concat(values):
         return Markup(", ".join(values))
-        #if len(values) <= 1:
-        #    return "".join(values)
-        #else:
-        #    return "{} and {}".format(", ".join(values[:-1]), values[-1])
-            
+
+    @staticmethod
+    def concat_lines(values):
+        return Markup("<br>".join(values))
+
 
 class SingleValueTable:
     def __init__(self, title, value, newlink=None, newtext=None):
@@ -62,16 +84,17 @@ class SingleValueTable:
     def rows(self):
         return [self.row()]
 
+
 class ProtocolsTable(Table):
     def __init__(self, protocols, search_results=None):
-        super().__init__("Protokolle", protocols, newlink=url_for("new_protocol"))
+        super().__init__(
+            "Protokolle", protocols, newlink=url_for("new_protocol"))
         self.search_results = search_results
 
     def headers(self):
-        user = current_user()
         result = ["Sitzung", "Sitzung", "Datum"]
-        state_part = ["Status", "Status",""]
-        search_part = ["Suchergebnis",""]
+        state_part = ["Uhrzeit", "Status", "Status", ""]
+        search_part = ["Suchergebnis", ""]
         if self.search_results is None:
             result.extend(state_part)
         else:
@@ -79,50 +102,58 @@ class ProtocolsTable(Table):
         return result
 
     def classes(self):
+        _MOBILE = ["hidden-sm hidden-md hidden-lg"]
+        _STANDARD = ["hidden-xs"]
+        _ALL = [""]
         if self.search_results is None:
-            result = ["hidden-sm hidden-md hidden-lg", "hidden-xs", "hidden-xs", "hidden-sm hidden-md hidden-lg", "hidden-xs", ""]
+            return _MOBILE + 3 * _STANDARD + _MOBILE + _STANDARD + _ALL
         else:
-            result = ["hidden-sm hidden-md hidden-lg", "hidden-xs", "hidden-xs", "", "hidden-xs","hidden-xs"]
-        return result
+            return _MOBILE + 2 * _STANDARD + _ALL + 2 * _STANDARD
 
     def row(self, protocol):
         user = current_user()
         protocol_link = url_for("show_protocol", protocol_id=protocol.id)
         result = [
-            Markup("<br>").join([Table.link(protocol_link, protocol.protocoltype.name), date_filter(protocol.date)]),
+            # Protocol (mobile)
+            Table.concat_lines([
+                Table.link(protocol_link, protocol.protocoltype.name),
+                date_filter(protocol.date)]),
+            # Protocol (standard)
             Table.link(protocol_link, protocol.protocoltype.name),
-            date_filter(protocol.date),
+            date_filter(protocol.date)
         ]
         if self.search_results is None:
-            result.append(Markup('<span class="glyphicon glyphicon-{state}"></span>'.format(state=protocol.get_state_glyph())))
-            result.append(Markup('<span class="glyphicon glyphicon-{glyph}"></span> {state}'.format(state=protocol.get_state_name(),glyph=protocol.get_state_glyph())))
+            result.append(Markup(time_filter(protocol.start_time)))
+            # State (mobile)
+            result.append(Table.glyphicon(protocol.get_state_glyph()))
+            # State (standard)
+            result.append(Table.glyphicon(
+                protocol.get_state_glyph(), protocol.get_state_name()))
         elif protocol in self.search_results:
             result.append(Markup(self.search_results[protocol]))
-            result.append(Markup('<span class="glyphicon glyphicon-{state}"></span>'.format(state=protocol.get_state_glyph())))
-        
-        login_part1=""
-        login_part2=""
+            result.append(Table.glyphicon(protocol.get_state_glyph()))
+
+        buttons = []
         if protocol.has_public_view_right(user):
             user_right = protocol.has_private_view_right(user)
             document = protocol.get_compiled_document(user_right)
             if document is not None:
-                login_part1 = Table.button(
+                buttons.append(Table.button(
                     url_for("download_document", document_id=document.id),
-                    icon="download", style="success")
+                    icon="download", style="success"))
 
         if protocol.protocoltype.has_admin_right(user):
-            login_part2 = Table.button(
+            buttons.append(Table.button(
                 url_for("delete_protocol", protocol_id=protocol.id),
                 icon="trash",
                 style="danger",
-                confirm="Bist du dir sicher, dass du das Protokoll {} löschen möchtest?")
-
-        result.append(Markup(
-            '<div class="btn-group btn-group-xs"> {} </div>'.format(
-                "".join((login_part1, login_part2)))))
+                confirm="Bist du dir sicher, dass du das Protokoll {} "
+                        "löschen möchtest?"))
 
+        result.append(Table.button_group(buttons))
         return result
 
+
 class ProtocolTypesTable(Table):
     def __init__(self, types):
         super().__init__("Protokolltypen", types, newlink=url_for("new_type"))
@@ -144,37 +175,51 @@ class ProtocolTypesTable(Table):
         user = current_user()
         has_private_view_right = protocoltype.has_private_view_right(user)
         has_modify_right = protocoltype.has_modify_right(user)
-        protocoltype_link = url_for("show_type", protocoltype_id=protocoltype.id)
-        protocol_link = (url_for("show_protocol", protocol_id=protocol.id)
+        protocoltype_link = url_for(
+            "show_type", protocoltype_id=protocoltype.id)
+        protocol_link = (
+            url_for("show_protocol", protocol_id=protocol.id)
             if protocol is not None else "")
-        new_protocol_link = url_for("new_protocol", protocoltype_id=protocoltype.id)
-        mobile_name = "{} ({})".format(protocoltype.name, protocoltype.short_name)
+        new_protocol_link = url_for(
+            "new_protocol", protocoltype_id=protocoltype.id)
+        mobile_name = "{} ({})".format(
+            protocoltype.name, protocoltype.short_name)
         mobile_links = []
         if protocol is not None:
-            mobile_links.append(Table.link(protocol_link, protocol.get_short_identifier()))
+            mobile_links.append(Table.link(
+                protocol_link, protocol.get_short_identifier()))
         if has_modify_right:
-            mobile_links.append(Table.link(new_protocol_link, "Neues Protokoll"))
+            mobile_links.append(
+                Table.link(new_protocol_link, "Neues Protokoll"))
         mobile_part = [
-            Table.link(protocoltype_link, mobile_name) if has_private_view_right else mobile_name,
+            Table.link(protocoltype_link, mobile_name)
+            if has_private_view_right else mobile_name,
             Markup("<br>".join(mobile_links))
         ]
         desktop_part = [
-            Table.link(protocoltype_link, protocoltype.short_name) if has_private_view_right else protocoltype.short_name,
+            Table.link(protocoltype_link, protocoltype.short_name)
+            if has_private_view_right else protocoltype.short_name,
             protocoltype.name,
-            Table.link(protocol_link, protocol.get_short_identifier()) if protocol is not None else "Noch kein Protokoll",
-            Table.link(new_protocol_link, "Neues Protokoll") if has_modify_right else ""
-            "" # TODO: add link for modify, delete
+            Table.link(protocol_link, protocol.get_short_identifier())
+            if protocol is not None else "Noch kein Protokoll",
+            Table.link(new_protocol_link, "Neues Protokoll")
+            if has_modify_right else ""
+            ""  # TODO: add link for modify, delete
         ]
         return mobile_part + desktop_part
 
+
 class ProtocolTypeTable(SingleValueTable):
     def __init__(self, protocoltype):
-        super().__init__(protocoltype.name, protocoltype, newlink=url_for("edit_type", protocoltype_id=protocoltype.id))
+        super().__init__(
+            protocoltype.name, protocoltype,
+            newlink=url_for("edit_type", protocoltype_id=protocoltype.id))
 
     def headers(self):
-        general_headers = ["Name", "Abkürzung", "Organisation", "Beginn",
-            "Öffentlich", "Verwaltungsgruppe", "Bearbeitungsgruppe", "Interne Gruppe",
-            "Öffentliche Gruppe"]
+        general_headers = [
+            "Name", "Abkürzung", "Organisation", "Beginn",
+            "Öffentlich", "Verwaltungsgruppe", "Bearbeitungsgruppe",
+            "Interne Gruppe", "Öffentliche Gruppe"]
         etherpad_headers = ["Nicht-reproduzierbare Etherpadlinks"]
         if not config.ETHERPAD_ACTIVE:
             etherpad_headers = []
@@ -193,13 +238,17 @@ class ProtocolTypeTable(SingleValueTable):
         network_headers = ["Netzwerke einschränken", "Erlaubte Netzwerke"]
         action_headers = ["Aktion"]
         feed_headers = []
-        latex_template_headers = ["LaTeX Vorlage"] if getattr(config, "LATEX_TEMPLATES", None) is not None else []
+        latex_template_headers = ["LaTeX Vorlage"] if getattr(
+            config, "LATEX_TEMPLATES", None) is not None else []
         if self.value.has_public_anonymous_view_right():
-            feed_headers = [Markup("<img height=\"18px\" src=\"{}\" /> Feed".format(
-                url_for("static", filename="images/feed-icon.svg")))]
-        return (general_headers + etherpad_headers + mail_headers
+            feed_headers = [
+                Markup("<img height=\"18px\" src=\"{}\" /> Feed".format(
+                    url_for("static", filename="images/feed-icon.svg")))]
+        return (
+            general_headers + etherpad_headers + mail_headers
             + printing_headers + wiki_headers + calendar_headers
-            + network_headers + latex_template_headers + feed_headers + action_headers)
+            + network_headers + latex_template_headers + feed_headers
+            + action_headers)
 
     def row(self):
         user = current_user()
@@ -207,7 +256,8 @@ class ProtocolTypeTable(SingleValueTable):
             self.value.name,
             self.value.short_name,
             self.value.organization,
-            self.value.usual_time.strftime("%H:%M") if self.value.usual_time is not None else "", # todo: remove if, this field is required
+            self.value.usual_time.strftime("%H:%M")
+            if self.value.usual_time is not None else "",
             Table.bool(self.value.is_public),
             self.value.publish_group,
             self.value.modify_group,
@@ -229,49 +279,79 @@ class ProtocolTypeTable(SingleValueTable):
         if not config.PRINTING_ACTIVE:
             printing_part = []
         wiki_part = [
-            (Table.bool(self.value.use_wiki) + ((", " + ("Öffentlich" if self.value.wiki_only_public else "Intern")) if self.value.use_wiki else ""))
+            (Table.bool(self.value.use_wiki)
+                + ((", "
+                    + ("Öffentlich"
+                        if self.value.wiki_only_public
+                        else "Intern")
+                    ) if self.value.use_wiki else ""))
         ]
         if self.value.use_wiki:
             wiki_part.append(self.value.wiki_category)
         if not config.WIKI_ACTIVE:
             wiki_part = []
-        calendar_part = [self.value.calendar if self.value.calendar is not None else ""]
+        calendar_part = [
+            self.value.calendar
+            if self.value.calendar is not None else ""]
         if not config.CALENDAR_ACTIVE:
             calendar_part = []
         network_part = [Table.bool(self.value.restrict_networks)]
         if self.value.allowed_networks is not None:
-            network_part.append(", ".join(map(str.strip, self.value.allowed_networks.split(","))))
+            network_part.append(
+                ", ".join(map(
+                    str.strip, self.value.allowed_networks.split(","))))
         else:
             network_part.append("")
         _latex_templates = getattr(config, "LATEX_TEMPLATES", None)
         if _latex_templates is not None:
-            latex_template_part = [_latex_templates[self.value.latex_template]['name'] if self.value.latex_template is not (None or "") else "Standardvorlage"]
+            latex_template_part = [
+                _latex_templates[self.value.latex_template]['name']
+                if (self.value.latex_template is not None
+                    and self.value.latex_template != "")
+                else "Standardvorlage"]
         else:
             latex_template_part = []
         feed_part = []
         if self.value.has_public_anonymous_view_right():
             feed_part = [Markup(", ".join([
-                Table.link(url_for("feed_protocols_rss",
+                Table.link(url_for(
+                    "feed_protocols_rss",
                     protocoltype_id=self.value.id), "Protokolle (RSS)"),
-                Table.link(url_for("feed_protocols_atom",
+                Table.link(url_for(
+                    "feed_protocols_atom",
                     protocoltype_id=self.value.id), "Protokolle (Atom)"),
-                Table.link(url_for("feed_appointments_rss",
+                Table.link(url_for(
+                    "feed_appointments_rss",
                     protocoltype_id=self.value.id), "Sitzungen (RSS)"),
-                Table.link(url_for("feed_appointments_atom",
+                Table.link(url_for(
+                    "feed_appointments_atom",
                     protocoltype_id=self.value.id), "Sitzungen (Atom)"),
-                Table.link(url_for("feed_appointments_ical",
+                Table.link(url_for(
+                    "feed_appointments_ical",
                     protocoltype_id=self.value.id), "Sitzungen (iCal)"),
             ]))]
-        action_part = [Table.link(url_for("delete_type", protocoltype_id=self.value.id), "Löschen", confirm="Bist du dir sicher, dass du den Protokolltype {} löschen möchtest?".format(self.value.name))]
+        action_part = [
+            Table.link(
+                url_for("delete_type", protocoltype_id=self.value.id,
+                        csrf_token=get_csrf_token()),
+                "Löschen",
+                confirm="Bist du dir sicher, dass du den Protokolltype "
+                        "{} löschen möchtest?".format(self.value.name))
+        ]
         if not self.value.has_admin_right(user):
             action_part = [""]
-        return (general_part + etherpad_part + mail_part + printing_part
-            + wiki_part +  calendar_part + network_part + latex_template_part + feed_part
-            + action_part)
+        return (
+            general_part + etherpad_part + mail_part + printing_part
+            + wiki_part + calendar_part + network_part + latex_template_part
+            + feed_part + action_part)
+
 
 class DefaultTOPsTable(Table):
     def __init__(self, tops, protocoltype=None):
-        super().__init__("Standard-TOPs", tops, newlink=url_for("new_default_top", protocoltype_id=protocoltype.id) if protocoltype is not None else None)
+        super().__init__(
+            "Standard-TOPs", tops,
+            newlink=url_for("new_default_top", protocoltype_id=protocoltype.id)
+            if protocoltype is not None else None)
         self.protocoltype = protocoltype
 
     def headers(self):
@@ -282,31 +362,58 @@ class DefaultTOPsTable(Table):
             top.name,
             top.number,
             Table.concat([
-                Table.link(url_for("move_default_top", defaulttop_id=top.id, diff=1), "Runter"),
-                Table.link(url_for("move_default_top", defaulttop_id=top.id, diff=-1), "Hoch"),
-                Table.link(url_for("edit_default_top", protocoltype_id=self.protocoltype.id, defaulttop_id=top.id), "Ändern"),
-                Table.link(url_for("delete_default_top", defaulttop_id=top.id), "Löschen", confirm="Bist du dir sicher, dass du den Standard-TOP {} löschen willst?".format(top.name))
+                Table.link(
+                    url_for("move_default_top", defaulttop_id=top.id, diff=1,
+                            csrf_token=get_csrf_token()),
+                    "Runter"),
+                Table.link(
+                    url_for("move_default_top", defaulttop_id=top.id, diff=-1,
+                            csrf_token=get_csrf_token()),
+                    "Hoch"),
+                Table.link(
+                    url_for(
+                        "edit_default_top",
+                        protocoltype_id=self.protocoltype.id,
+                        defaulttop_id=top.id),
+                    "Ändern"),
+                Table.link(
+                    url_for("delete_default_top", defaulttop_id=top.id,
+                            csrf_token=get_csrf_token()),
+                    "Löschen",
+                    confirm="Bist du dir sicher, dass du den Standard-TOP "
+                            "{} löschen willst?".format(top.name))
             ])
         ]
 
+
 class MeetingRemindersTable(Table):
     def __init__(self, reminders, protocoltype=None):
-        super().__init__("Einladungsmails", reminders, newlink=url_for("new_reminder", protocoltype_id=protocoltype.id) if protocoltype is not None else None)
+        super().__init__(
+            "Einladungsmails", reminders,
+            newlink=url_for("new_reminder", protocoltype_id=protocoltype.id)
+            if protocoltype is not None else None)
         self.protocoltype = protocoltype
 
     def headers(self):
         return ["Zeit", "Einladen", "Zusätzlicher Mailinhalt", ""]
 
     def row(self, reminder):
-        user = current_user()
         general_part = [
             "{} Tage".format(reminder.days_before),
             self.get_send_summary(reminder),
             reminder.additional_text or ""
         ]
         action_links = [
-            Table.link(url_for("edit_reminder", meetingreminder_id=reminder.id), "Ändern"),
-            Table.link(url_for("delete_reminder", meetingreminder_id=reminder.id), "Löschen", confirm="Bist du dir sicher, dass du die Einladungsmail {} Tage vor der Sitzung löschen willst?".format(reminder.days_before))
+            Table.link(
+                url_for("edit_reminder", meetingreminder_id=reminder.id),
+                "Ändern"),
+            Table.link(
+                url_for("delete_reminder", meetingreminder_id=reminder.id,
+                        csrf_token=get_csrf_token()),
+                "Löschen",
+                confirm="Bist du dir sicher, dass du die Einladungsmail {} "
+                        "Tage vor der Sitzung löschen willst?".format(
+                    reminder.days_before))
         ]
         action_part = [Table.concat(action_links)]
         return general_part + action_part
@@ -319,26 +426,36 @@ class MeetingRemindersTable(Table):
             parts.append("Intern")
         return " und ".join(parts)
 
+
 class ErrorsTable(Table):
     def __init__(self, errors):
         super().__init__("Fehler", errors)
 
     def headers(self):
-        return ["Protokoll", "Aktion", "Fehler", "Zeitpunkt", "Beschreibung", ""]
+        return [
+            "Protokoll", "Aktion", "Fehler", "Zeitpunkt", "Beschreibung", ""]
 
     def classes(self):
         return [None, None, None, None, "hidden-xs", "hidden-xs"]
 
     def row(self, error):
         return [
-            Table.link(url_for("show_protocol", protocol_id=error.protocol.id), error.protocol.get_short_identifier()),
+            Table.link(
+                url_for("show_protocol", protocol_id=error.protocol.id),
+                error.protocol.get_short_identifier()),
             error.action,
             Table.link(url_for("show_error", error_id=error.id), error.name),
             datetime_filter(error.datetime),
             error.get_short_description(),
-            Table.link(url_for("delete_error", error_id=error.id, next=request.path), "Löschen", confirm="Bist du dir sicher, dass du den Fehler löschen möchtest?")
+            Table.link(
+                url_for("delete_error", error_id=error.id,
+                        csrf_token=get_csrf_token()),
+                "Löschen",
+                confirm="Bist du dir sicher, dass du den Fehler löschen "
+                        "möchtest?")
         ]
 
+
 class ErrorTable(SingleValueTable):
     def __init__(self, error):
         super().__init__(error.action, error)
@@ -348,12 +465,15 @@ class ErrorTable(SingleValueTable):
 
     def row(self):
         return [
-            Table.link(url_for("show_protocol", protocol_id=self.value.protocol.id), self.value.protocol.get_short_identifier()),
+            Table.link(
+                url_for("show_protocol", protocol_id=self.value.protocol.id),
+                self.value.protocol.get_short_identifier()),
             self.value.action,
             self.value.name,
             datetime_filter(self.value.datetime)
         ]
 
+
 class TodosTable(Table):
     def __init__(self, todos):
         super().__init__("Todos", todos, newlink=url_for("new_todo"))
@@ -362,34 +482,48 @@ class TodosTable(Table):
         return ["Todo", "ID", "Status", "Sitzung", "Name", "Aufgabe", ""]
 
     def classes(self):
-        return ["hidden-sm hidden-md hidden-lg", "hidden-xs", "hidden-xs", "hidden-xs", "hidden-xs", None, "hidden-xs"]
+        return [
+            "hidden-sm hidden-md hidden-lg",
+            "hidden-xs", "hidden-xs", "hidden-xs", "hidden-xs",
+            None, "hidden-xs"]
 
     def row(self, todo):
         user = current_user()
         protocol = todo.get_first_protocol()
-        mobile_parts = [Table.link(url_for("show_todo", todo_id=todo.id), todo.get_state())]
+        mobile_parts = [Table.link(
+            url_for("show_todo", todo_id=todo.id),
+            todo.get_state())]
         if protocol is not None:
-            mobile_parts.append(Table.link(url_for("show_protocol", protocol_id=protocol.id), todo.protocoltype.short_name))
+            mobile_parts.append(Table.link(
+                url_for("show_protocol", protocol_id=protocol.id),
+                todo.protocoltype.short_name))
         mobile_parts.append(todo.who)
         row = [
             Markup("<br>").join(mobile_parts),
             Table.link(url_for("show_todo", todo_id=todo.id), todo.get_id()),
             todo.get_state(),
-            Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_short_identifier())
-                if protocol is not None
-                else Table.link(url_for("list_protocols", protocoltype_id=todo.protocoltype.id), todo.protocoltype.short_name),
+            Table.link(
+                url_for("show_protocol", protocol_id=protocol.id),
+                protocol.get_short_identifier())
+            if protocol is not None
+            else Table.link(
+                url_for(
+                    "list_protocols", protocoltype_id=todo.protocoltype.id),
+                todo.protocoltype.short_name),
             todo.who,
             todo.description,
         ]
         if todo.protocoltype.has_modify_right(user):
             row.append(Table.concat([
                 Table.link(url_for("edit_todo", todo_id=todo.id), "Ändern"),
-                Table.link(url_for("delete_todo", todo_id=todo.id), "Löschen")
+                Table.link(url_for("delete_todo", todo_id=todo.id,
+                           csrf_token=get_csrf_token()), "Löschen")
             ]))
         else:
             row.append("")
         return row
 
+
 class TodoTable(SingleValueTable):
     def __init__(self, todo):
         super().__init__("Todo", todo)
@@ -399,26 +533,33 @@ class TodoTable(SingleValueTable):
 
     def row(self):
         user = current_user()
-        protocol = self.value.get_first_protocol()
         row = [
             self.value.get_id(),
             self.value.get_state_plain(),
             Table.concat([
-                Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_short_identifier())
-                    for protocol in self.value.protocols
+                Table.link(
+                    url_for("show_protocol", protocol_id=protocol.id),
+                    protocol.get_short_identifier())
+                for protocol in self.value.protocols
             ]),
             self.value.who,
             self.value.description
         ]
         if self.value.protocoltype.has_modify_right(user):
             row.append(Table.concat([
-                Table.link(url_for("edit_todo", todo_id=self.value.id), "Ändern"),
-                Table.link(url_for("delete_todo", todo_id=self.value.id), "Löschen", confirm="Bist du dir sicher, dass du das Todo löschen willst?")
+                Table.link(
+                    url_for("edit_todo", todo_id=self.value.id), "Ändern"),
+                Table.link(
+                    url_for("delete_todo", todo_id=self.value.id,
+                            csrf_token=get_csrf_token()), "Löschen",
+                    confirm="Bist du dir sicher, dass du das Todo löschen "
+                            "willst?")
             ]))
         else:
             row.append("")
         return row
 
+
 class DecisionsTable(Table):
     def __init__(self, decisions):
         super().__init__("Beschlüsse", decisions)
@@ -438,21 +579,29 @@ class DecisionsTable(Table):
     def row(self, decision):
         user = current_user()
         content_part = [
-            Table.link(url_for("show_protocol", protocol_id=decision.protocol.id), decision.protocol.get_short_identifier()),
+            Table.link(
+                url_for("show_protocol", protocol_id=decision.protocol.id),
+                decision.protocol.get_short_identifier()),
             decision.content
         ]
         category_part = [decision.get_categories_str()]
         if not self.category_present:
             category_part = []
         action_part = [
-            Table.link(url_for("print_decision", decisiondocument_id=decision.document.id), "Drucken")
-                if config.PRINTING_ACTIVE
+            Table.link(
+                url_for(
+                    "print_decision",
+                    decisiondocument_id=decision.document.id,
+                    csrf_token=get_csrf_token()),
+                "Drucken")
+            if (config.PRINTING_ACTIVE
                 and decision.protocol.protocoltype.has_modify_right(user)
-                and decision.document is not None 
-                else ""
+                and decision.document is not None)
+            else ""
         ]
         return content_part + category_part + action_part
 
+
 class DocumentsTable(Table):
     def __init__(self, documents, protocol):
         super().__init__("Anhang", documents)
@@ -464,7 +613,7 @@ class DocumentsTable(Table):
         visibility_headers = []
         if self.protocol.has_private_view_right(user):
             visibility_headers = ["Sichtbarkeit"]
-        action_headers=[""]
+        action_headers = [""]
         return general_headers + visibility_headers + action_headers
 
     def classes(self):
@@ -480,24 +629,41 @@ class DocumentsTable(Table):
         user = current_user()
         links = []
         if document.protocol.has_modify_right(user):
-            links.append(Table.link(url_for("edit_document", document_id=document.id), "Bearbeiten"))
+            links.append(Table.link(
+                url_for("edit_document", document_id=document.id),
+                "Bearbeiten"))
         if config.PRINTING_ACTIVE and document.protocol.has_modify_right(user):
-            links.append(Table.link(url_for("print_document", document_id=document.id), "Drucken"))
+            links.append(Table.link(
+                url_for("print_document", document_id=document.id,
+                        csrf_token=get_csrf_token()),
+                "Drucken"))
         if document.protocol.protocoltype.has_admin_right(user):
-            links.append(Table.link(url_for("delete_document", document_id=document.id), "Löschen", confirm="Bist du dir sicher, dass du das Dokument {} löschen willst?".format(document.name)))
+            links.append(Table.link(
+                url_for("delete_document", document_id=document.id,
+                        csrf_token=get_csrf_token()),
+                "Löschen",
+                confirm="Bist du dir sicher, dass du das Dokument {} löschen "
+                        "willst?".format(document.name)))
         general_part = [
             document.id,
-            Table.link(url_for("download_document", document_id=document.id), document.name),
+            Table.link(
+                url_for("download_document", document_id=document.id),
+                document.name),
         ]
         visibility_part = []
         if document.protocol.has_private_view_right(user):
-            visibility_part = ["Intern" if document.is_private else "Öffentlich"]
+            visibility_part = [
+                "Intern"
+                if document.is_private
+                else "Öffentlich"]
         action_part = [Table.concat(links)]
         return general_part + visibility_part + action_part
 
+
 class TodoMailsTable(Table):
     def __init__(self, todomails):
-        super().__init__("Todo-Mail-Zuordnungen", todomails, url_for("new_todomail"))
+        super().__init__(
+            "Todo-Mail-Zuordnungen", todomails, url_for("new_todomail"))
 
     def headers(self):
         return ["Name", "Mail", ""]
@@ -507,11 +673,20 @@ class TodoMailsTable(Table):
             todomail.name,
             todomail.mail,
             Table.concat([
-                Table.link(url_for("edit_todomail", todomail_id=todomail.id), "Ändern"),
-                Table.link(url_for("delete_todomail", todomail_id=todomail.id), "Löschen", confirm="Bist du dir sicher, dass du die Todomailzuordnung {} zu {} löschen willst?".format(todomail.name, todomail.mail))
+                Table.link(
+                    url_for("edit_todomail", todomail_id=todomail.id),
+                    "Ändern"),
+                Table.link(
+                    url_for("delete_todomail", todomail_id=todomail.id,
+                            csrf_token=get_csrf_token()),
+                    "Löschen",
+                    confirm="Bist du dir sicher, dass du die "
+                            "Todomailzuordnung {} zu {} löschen "
+                            "willst?".format(todomail.name, todomail.mail))
             ])
         ]
 
+
 class DefaultMetasTable(Table):
     def __init__(self, metas, protocoltype):
         super().__init__(
@@ -524,7 +699,6 @@ class DefaultMetasTable(Table):
         return ["Name", "Key", "Standardwert", "Intern", "Vorher", ""]
 
     def row(self, meta):
-        user = current_user()
         general_part = [
             meta.name,
             meta.key,
@@ -533,17 +707,24 @@ class DefaultMetasTable(Table):
             Table.bool(meta.prior)
         ]
         links = [
-            Table.link(url_for("edit_defaultmeta", defaultmeta_id=meta.id), "Ändern"),
-            Table.link(url_for("delete_defaultmeta", defaultmeta_id=meta.id), "Löschen", confirm="Bist du dir sicher, dass du das Metadatenfeld {} löschen willst?".format(meta.name))
+            Table.link(
+                url_for("edit_defaultmeta", defaultmeta_id=meta.id), "Ändern"),
+            Table.link(
+                url_for("delete_defaultmeta", defaultmeta_id=meta.id,
+                        csrf_token=get_csrf_token()),
+                "Löschen",
+                confirm="Bist du dir sicher, dass du das Metadatenfeld {} "
+                        "löschen willst?".format(meta.name))
         ]
         link_part = [Table.concat(links)]
         return general_part + link_part
 
+
 class DecisionCategoriesTable(Table):
     def __init__(self, categories, protocoltype):
         super().__init__(
             "Beschlusskategorien",
-            categories, 
+            categories,
             url_for("new_decisioncategory", protocoltype_id=protocoltype.id)
         )
 
@@ -551,13 +732,23 @@ class DecisionCategoriesTable(Table):
         return ["Name", ""]
 
     def row(self, category):
-        user = current_user()
         general_part = [category.name]
         action_part = [
             Table.concat([
-                Table.link(url_for("edit_decisioncategory", decisioncategory_id=category.id), "Ändern"),
-                Table.link(url_for("delete_decisioncategory", decisioncategory_id=category.id), "Löschen", confirm="Bist du dir sicher, dass du die Beschlusskategorie {} löschen willst?".format(category.name))
+                Table.link(
+                    url_for(
+                        "edit_decisioncategory",
+                        decisioncategory_id=category.id),
+                    "Ändern"),
+                Table.link(
+                    url_for(
+                        "delete_decisioncategory",
+                        decisioncategory_id=category.id,
+                        csrf_token=get_csrf_token()),
+                    "Löschen",
+                    confirm="Bist du dir sicher, dass du die "
+                            "Beschlusskategorie {} löschen "
+                            "willst?".format(category.name))
             ])
         ]
         return general_part + action_part
-
diff --git a/wiki.py b/wiki.py
index 4ae80405ea65e2e22cc085720884fc722138b8c1..a393e69326d0d2f37e9a19bd072ad71159508822 100644
--- a/wiki.py
+++ b/wiki.py
@@ -1,14 +1,15 @@
 import requests
-import json
 
 import config
 
 HTTP_STATUS_OK = 200
 HTTP_STATUS_AUTHENTICATE = 401
 
+
 class WikiException(Exception):
     pass
 
+
 def _filter_params(params):
     result = {}
     for key, value in sorted(params.items(), key=lambda t: t[0] == "token"):
@@ -19,14 +20,20 @@ def _filter_params(params):
             result[key] = value
     return result
 
+
 class WikiClient:
-    def __init__(self, active=None, endpoint=None, anonymous=None,  user=None, password=None, domain=None):
-        self.active = active if active is not None else config.WIKI_ACTIVE
-        self.endpoint = endpoint if endpoint is not None else config.WIKI_API_URL
-        self.anonymous = anonymous if anonymous is not None else config.WIKI_ANONYMOUS
-        self.user = user if user is not None else config.WIKI_USER
-        self.password = password if password is not None else config.WIKI_PASSWORD
-        self.domain = domain if domain is not None else config.WIKI_DOMAIN
+    def __init__(self, active=None, endpoint=None, anonymous=None, user=None,
+                 password=None, domain=None):
+        def _or_default(value, default):
+            if value is None:
+                return default
+            return value
+        self.active = _or_default(active, config.WIKI_ACTIVE)
+        self.endpoint = _or_default(endpoint, config.WIKI_API_URL)
+        self.anonymous = _or_default(anonymous, config.WIKI_ANONYMOUS)
+        self.user = _or_default(user, config.WIKI_USER)
+        self.password = _or_default(password, config.WIKI_PASSWORD)
+        self.domain = _or_default(domain, config.WIKI_DOMAIN)
         self.token = None
         self.cookies = requests.cookies.RequestsCookieJar()
 
@@ -45,30 +52,33 @@ class WikiClient:
     def login(self):
         if not self.active:
             return
-        # todo: Change this to the new MediaWiki tokens api once the wiki is updated
+        # todo: Change this to the new MediaWiki tokens api
         token_answer = self.do_action("login", method="post", lgname=self.user)
         if "login" not in token_answer or "token" not in token_answer["login"]:
             raise WikiException("No token in login answer.")
         lgtoken = token_answer["login"]["token"]
-        login_answer = self.do_action("login", method="post", lgname=self.user, lgpassword=self.password, lgdomain=self.domain, lgtoken=lgtoken)
+        login_answer = self.do_action(
+            "login", method="post", lgname=self.user, lgpassword=self.password,
+            lgdomain=self.domain, lgtoken=lgtoken)
         if ("login" not in login_answer
-        or "result" not in login_answer["login"]
-        or login_answer["login"]["result"] != "Success"):
+                or "result" not in login_answer["login"]
+                or login_answer["login"]["result"] != "Success"):
             raise WikiException("Login not successful.")
 
-
     def logout(self):
         if not self.active:
             return
         self.do_action("logout")
 
-    def edit_page(self, title, content, summary, recreate=True, createonly=False):
+    def edit_page(self, title, content, summary, recreate=True,
+                  createonly=False):
         if not self.active:
             return
         # todo: port to new api once the wiki is updated
-        prop_answer = self.do_action("query", method="get", prop="info", intoken="edit", titles=title)
+        prop_answer = self.do_action(
+            "query", method="get", prop="info", intoken="edit", titles=title)
         if ("query" not in prop_answer
-        or "pages" not in prop_answer["query"]):
+                or "pages" not in prop_answer["query"]):
             raise WikiException("Can't get token for page {}".format(title))
         pages = prop_answer["query"]["pages"]
         edit_token = None
@@ -78,7 +88,8 @@ class WikiClient:
                 break
         if edit_token is None:
             raise WikiException("Can't get token for page {}".format(title))
-        edit_answer = self.do_action(action="edit", method="post", data={"text": content},
+        self.do_action(
+            action="edit", method="post", data={"text": content},
             token=edit_token, title=title,
             summary=summary, recreate=recreate,
             createonly=createonly, bot=True)
@@ -89,18 +100,28 @@ class WikiClient:
         kwargs["action"] = action
         kwargs["format"] = "json"
         params = _filter_params(kwargs)
+
         def _do_request():
             if method == "get":
-                return requests.get(self.endpoint, cookies=self.cookies, params=params, auth=requests.auth.HTTPBasicAuth(self.user, self.password))
+                return requests.get(
+                    self.endpoint, cookies=self.cookies, params=params,
+                    auth=requests.auth.HTTPBasicAuth(self.user, self.password))
             elif method == "post":
-                return requests.post(self.endpoint, cookies=self.cookies, data=data, params=params, auth=requests.auth.HTTPBasicAuth(self.user, self.password))
+                return requests.post(
+                    self.endpoint, cookies=self.cookies, data=data,
+                    params=params, auth=requests.auth.HTTPBasicAuth(
+                        self.user, self.password))
         req = _do_request()
         if req.status_code != HTTP_STATUS_OK:
-            raise WikiException("HTTP status code {} on action {}.".format(req.status_code, action))
+            raise WikiException(
+                "HTTP status code {} on action {}.".format(
+                    req.status_code, action))
         self.cookies.update(req.cookies)
         return req.json()
 
+
 def main():
     with WikiClient() as client:
-        client.edit_page(title="Test", content="This is a very long text.", summary="API client test")
-
+        client.edit_page(
+            title="Test", content="This is a very long text.",
+            summary="API client test")