diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..8b84e389adfc97cfeba38ca6ad6c68f7e986438d
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+ignore = E402,W503
+
diff --git a/back.py b/back.py
index 57c91f332a845c619caf29d9d3fb41dcc305201c..8afa00be5a19e22d2688e6bc0c908512dac217a8 100644
--- a/back.py
+++ b/back.py
@@ -19,9 +19,17 @@ def anchor(func, cookie=cookie):
     return result
 
 
+def default_url(default, **url_args):
+    return url_for(default, **url_args)
+
+
 def url(default=default_view, cookie=cookie, **url_args):
-    return session.get(cookie, url_for(default, **url_args))
+    return session.get(cookie, default_url(default, **url_args))
 
 
 def redirect(default=default_view, cookie=cookie, **url_args):
-    return flask_redirect(url(default, cookie, **url_args))
+    print(request.url, request.url_rule, default, session.get(cookie))
+    target = url(default, cookie, **url_args)
+    if target == request.url:
+        target = default_url(default, **url_args)
+    return flask_redirect(target)
diff --git a/config.py.example b/config.py.example
index 581ca2258cc1f74ba69b02a9fdcafc4f7e33565d..5c4cd1502fe4f3799bffb79448207999fd7f031d 100644
--- a/config.py.example
+++ b/config.py.example
@@ -25,6 +25,9 @@ CELERY_BROKER_URL = "redis://localhost:6379/0" # change this if you do not use r
 CELERY_TASK_SERIALIZER = "pickle" # do not change
 CELERY_ACCEPT_CONTENT = ["pickle"] # do not change
 
+# Send exceptions to sentry (optional)
+# SENTRY_DSN = "https://********:********@sentry.example.com//1"
+
 # CUPS printserver (optional)
 PRINTING_ACTIVE = True
 PRINTING_SERVER = "printsrv.example.com:631"
diff --git a/decorators.py b/decorators.py
index 17b73569653f0b514b910aa6ef152884252d2fb8..7057c004aeaa05894f4f5f40568b221daa082473 100644
--- a/decorators.py
+++ b/decorators.py
@@ -1,6 +1,7 @@
 from flask import request, flash, abort
 
 from functools import wraps
+from hmac import compare_digest
 
 from models.database import ALL_MODELS
 from shared import current_user
@@ -97,8 +98,8 @@ 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())
+        true_token = get_csrf_token()
+        if token is None or not compare_digest(token, true_token):
             abort(400)
         return function(*args, **kwargs)
     return _decorated_function
diff --git a/models/database.py b/models/database.py
index 69f2bde49162f95a58e579655af750dee82158e3..b9f64aa9f18016f9d3644b83a88f9b26f0964b26 100644
--- a/models/database.py
+++ b/models/database.py
@@ -376,7 +376,6 @@ class Protocol(DatabaseModel):
         candidates = [
             document for document in self.documents
             if document.is_compiled
-            and (private is None or document.is_private == private)
         ]
         private_candidates = [
             document for document in candidates
@@ -386,10 +385,14 @@ class Protocol(DatabaseModel):
             document for document in candidates
             if not document.is_private
         ]
-        if len(private_candidates) > 0:
-            return private_candidates[0]
-        elif len(public_candidates) > 0:
-            return public_candidates[0]
+
+        def _get_candidates():
+            if private is None or private:
+                return private_candidates + public_candidates
+            return public_candidates
+        candidates = _get_candidates()
+        if candidates:
+            return candidates[0]
         return None
 
     def get_template(self):
diff --git a/requirements.txt b/requirements.txt
index a5143e52aaaae6b419859f9a008800d03e3121a7..899198691c333e40b7c20c5c28d2de26ddaa798c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,6 +17,7 @@ curtsies==0.3.0
 enum-compat==0.0.2
 eventlet==0.22.1
 feedgen==0.6.1
+flake8==3.5.0
 Flask==0.12.2
 Flask-Migrate==2.1.1
 Flask-Script==2.0.6
@@ -34,15 +35,18 @@ ldap3==2.4.1
 lxml==4.1.1
 Mako==1.0.7
 MarkupSafe==1.0
+mccabe==0.6.1
 nose==1.3.7
 packaging==16.8
 pathtools==0.1.2
 psycopg2==2.7.4
 pyasn1==0.4.2
+pycodestyle==2.3.1
+pyflakes==1.6.0
 Pygments==2.2.0
 pyldap==2.4.45
 pyparsing==2.2.0
-python-dateutil==2.6.1
+python-dateutil==2.7.0
 python-editor==1.0.3
 python-engineio==2.0.2
 python-Levenshtein==0.12.0
@@ -50,6 +54,7 @@ python-pam==1.8.2
 python-socketio==1.8.4
 pytz==2018.3
 PyYAML==3.12
+raven==6.6.0
 redis==2.10.6
 regex==2018.2.8
 requests==2.18.4
diff --git a/server.py b/server.py
index 11c1b460aabce265d9ef8fd49a65ed0db90b7354..639e8f80169a1b2861a146995fa13df82c14447d 100755
--- a/server.py
+++ b/server.py
@@ -31,7 +31,7 @@ from shared import (
 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)
+    get_internal_filename, get_csrf_token, get_current_ip)
 from decorators import (
     db_lookup, protect_csrf,
     require_private_view_right, require_modify_right, require_publish_right,
@@ -64,10 +64,37 @@ migrate = Migrate(app, db)
 manager = Manager(app)
 manager.add_command("db", MigrateCommand)
 
+try:
+    from raven.contrib.flask import Sentry
+    sentry = Sentry(app, dsn=config.SENTRY_DSN)
+
+    def get_user_info(request):
+        return {
+            "is_authenticated": check_login(),
+            "ip_address": get_current_ip(),
+            "release": get_git_revision(),
+        }
+    sentry.get_user_info = get_user_info
+except ModuleNotFoundError:
+    print("Raven not installed. Not sending issues to Sentry.")
+except AttributeError:
+    print("DSN not configured. Not sending issues to Sentry.")
+
 
 def make_celery(app, config):
     celery = Celery(app.import_name, broker=config.CELERY_BROKER_URL)
     celery.conf.update(app.config)
+    try:
+        from raven import Client as RavenClient
+        from raven.contrib.celery import (
+            register_signal, register_logger_signal)
+        raven_client = RavenClient(config.SENTRY_DSN)
+        register_logger_signal(raven_client)
+        register_signal(raven_client)
+    except ModuleNotFoundError:
+        print("Raven not installed. Not sending celery issues to Sentry.")
+    except AttributeError:
+        print("DSN not configured. Not sending celery issues to Sentry.")
     return celery
 
 
diff --git a/templates/decisions-list.html b/templates/decisions-list.html
index 667eef2d66d228a07ce673c80ab372b4fbaf42af..dc0aed8edb58bc5801b029d2364809e272782753 100644
--- a/templates/decisions-list.html
+++ b/templates/decisions-list.html
@@ -2,16 +2,6 @@
 {% from "macros.html" import render_table, render_form %}
 {% block title %}Beschlüsse{% endblock %}
 
-{% macro page_link(text, _page=None, _page_length=None) %}
-	{% if _page is none %}
-		{% set _page = page %}
-	{% endif %}
-	{% if _page_length is none %}
-		{% set _page_length = page_length %}
-	{% endif %}
-    <a href="{{url_for(request.endpoint, page=_page, protocoltype_id=protocoltype_id, search=search_term, decisioncategory_id=decisioncategory_id, page_length=_page_length)}}">{{text}}</a>
-{% endmacro %}
-
 {% block content %}
 <div class="container">
     {{render_form(search_form, class_="form-inline", action_url=url_for("list_decisions"), action_text="Suchen", labels_visible=False, method="GET")}}
diff --git a/templates/macros.html b/templates/macros.html
index e6d0edd7c26a03c329daaccd5abd0e6f33815956..230a10073978ed63fe17a60cf77735d312e874ab 100644
--- a/templates/macros.html
+++ b/templates/macros.html
@@ -165,6 +165,18 @@ to not render a label for the CRSFTokenField -->
     </table>
 {%- endmacro %}
 
+{% macro page_link(text, _page=None, _page_length=None) -%}
+    {% set args = request.view_args.copy() %}
+    {% set _ = args.update(request.args) %}
+    {% if _page is not none %}
+        {% set _ = args.update({"page": _page}) %}
+    {% endif %}
+    {% if _page_length is not none %}
+        {% set _ = args.update({"page_length": _page_length}) %}
+    {% endif %}
+    <a href="{{url_for(request.endpoint, **args)}}">{{text}}</a>
+{%- endmacro %}
+
 {% macro render_likes(likes) -%}
     {% set timestamp = now() %}
     {% if timestamp.month == 4 and timestamp.day == 1 %}
diff --git a/templates/pagination-footer.html b/templates/pagination-footer.html
index 1f34556743d148f291e56d4c2fcbb1a313dce21b..19ae96bd5ce505313397b7d85e8a847dc7335cf1 100644
--- a/templates/pagination-footer.html
+++ b/templates/pagination-footer.html
@@ -1,3 +1,4 @@
+{% from "macros.html" import page_link %}
 <div class="centered">
     {% if page > page_diff %}
         {{page_link("<<", _page=0)}}
diff --git a/templates/protocol-show.html b/templates/protocol-show.html
index ba615a8260f9bb0158c176c04bbffb21a03a09c6..b5546fe387a9786922c32abfee4a616d452431e1 100644
--- a/templates/protocol-show.html
+++ b/templates/protocol-show.html
@@ -51,7 +51,7 @@
                 {% endif %}
                 {% if has_admin_right %}
             <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>
+            <a class="btn btn-danger" href="{{url_for("delete_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}" onclick="return confirm('Bist du dir sicher, dass du das Protokoll {{protocol.get_short_identifier()}} löschen möchtest?');">Löschen</a>
                 {% endif %}
             {% endif %}
         </div>
diff --git a/templates/protocols-list.html b/templates/protocols-list.html
index 02d3c81ee505e06950b72655f09d17243aa5a886..e019a255ae23589f55fb9df9d220c223d21d8ea9 100644
--- a/templates/protocols-list.html
+++ b/templates/protocols-list.html
@@ -2,18 +2,6 @@
 {% from "macros.html" import render_table, render_form %}
 {% block title %}Protokolle{% endblock %}
 
-{% macro page_link(text, _page=None, _page_length=None) %}
-	{% if _page is none %}
-		{% set _page = page %}
-	{% endif %}
-	{% if _page_length is none %}
-		{% set _page_length = page_length %}	
-	{% endif %}
-    <a href="{{url_for(request.endpoint, page=_page, protocoltype_id=protocoltype_id, search=search_term, state_open=state_open, page_length=_page_length)}}">{{text}}</a>
-{% endmacro %}
-
-
-
 {% block content %}
 <div class="container">
     {{render_form(search_form, class_="form-inline", action_url=url_for("list_protocols"), action_text="Suchen", labels_visible=False, method="GET")}}
diff --git a/templates/todos-list.html b/templates/todos-list.html
index 0734992cdf861046927284fef46743b8d8338a3b..0c9923ee51cd34f9147f7c506a84abd73690b877 100644
--- a/templates/todos-list.html
+++ b/templates/todos-list.html
@@ -2,16 +2,6 @@
 {% from "macros.html" import render_table, render_form %}
 {% block title %}Todos{% endblock %}
 
-{% macro page_link(text, _page=None, _page_length=None) %}
-	{% if _page is none %}
-		{% set _page = page %}
-	{% endif %}
-	{% if _page_length is none %}
-		{% set _page_length = page_length %}
-	{% endif %}
-    <a href="{{url_for(request.endpoint, page=_page, protocoltype_id=protocoltype_id, search=search_term, state_open=state_open, page_length=_page_length)}}">{{text}}</a>
-{% endmacro %}
-
 {% block content %}
 <div class="container">
     {{render_form(search_form, class_="form-inline", action_url=url_for("list_todos"), action_text="Suchen", labels_visible=False, method="GET")}}
diff --git a/utils.py b/utils.py
index 5e80b9cbebc40ae8cacf6cff9201fa12c1577e17..116a2f157c623617bc501c5fb089c35bdf54fde2 100644
--- a/utils.py
+++ b/utils.py
@@ -193,11 +193,16 @@ def add_line_numbers(text):
     return "\n".join(lines)
 
 
-def check_ip_in_networks(networks_string):
+def get_current_ip():
     address = ipaddress.ip_address(request.remote_addr)
     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"])
+    return address
+
+
+def check_ip_in_networks(networks_string):
+    address = get_current_ip()
     try:
         for network_string in networks_string.split(","):
             network = ipaddress.ip_network(network_string.strip())
diff --git a/views/tables.py b/views/tables.py
index e3f60f581c0c7a6e44ef31506be60da537e425bc..7c75dd158a481b14c39730764b852cbc03494fc6 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -144,7 +144,9 @@ class ProtocolsTable(Table):
 
         if protocol.protocoltype.has_admin_right(user):
             buttons.append(Table.button(
-                url_for("delete_protocol", protocol_id=protocol.id),
+                url_for(
+                    "delete_protocol", protocol_id=protocol.id,
+                    csrf_token=get_csrf_token()),
                 icon="trash",
                 style="danger",
                 confirm="Bist du dir sicher, dass du das Protokoll {} "