diff --git a/migrations/versions/97cf1913e60d_.py b/migrations/versions/97cf1913e60d_.py
new file mode 100644
index 0000000000000000000000000000000000000000..18377e9e13ce5039ae4bfb5749a0a8a9cc514935
--- /dev/null
+++ b/migrations/versions/97cf1913e60d_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: 97cf1913e60d
+Revises: bbc1782c0999
+Create Date: 2017-02-22 23:36:29.467493
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '97cf1913e60d'
+down_revision = 'bbc1782c0999'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('tops', sa.Column('number', sa.Integer(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('tops', 'number')
+    # ### end Alembic commands ###
diff --git a/migrations/versions/bbc1782c0999_.py b/migrations/versions/bbc1782c0999_.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1787c079b8dfa99374803fe409d265cb618bf22
--- /dev/null
+++ b/migrations/versions/bbc1782c0999_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: bbc1782c0999
+Revises: 162da8aeeb71
+Create Date: 2017-02-22 23:36:11.613892
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'bbc1782c0999'
+down_revision = '162da8aeeb71'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('tops', 'number')
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('tops', sa.Column('number', sa.VARCHAR(), autoincrement=False, nullable=True))
+    # ### end Alembic commands ###
diff --git a/models/database.py b/models/database.py
index 6dd11e89585e4e76a26235fda989ce1164b37a73..9c511e3df9dad627138e1a06d9d7a450bc4623dd 100644
--- a/models/database.py
+++ b/models/database.py
@@ -1,7 +1,6 @@
 from flask import render_template, send_file, url_for, redirect, flash, request
 
-from datetime import datetime, date, timedelta
-import time
+from datetime import datetime, time, date, timedelta
 import math
 
 from shared import db
@@ -11,6 +10,8 @@ from utils import random_string, url_manager
 
 from sqlalchemy.orm import relationship, backref
 
+import config
+
 class ProtocolType(db.Model):
     __tablename__ = "protocoltypes"
     id = db.Column(db.Integer, primary_key=True)
@@ -79,7 +80,7 @@ class Protocol(db.Model):
     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")
 
-    def __init__(self, protocoltype_id, date, source=None, start_time=None, end_time=None, author=None, participants=None, location=None):
+    def __init__(self, protocoltype_id, date, source=None, start_time=None, end_time=None, author=None, participants=None, location=None, done=False):
         self.protocoltype_id = protocoltype_id
         self.date = date
         self.source = source
@@ -88,6 +89,7 @@ class Protocol(db.Model):
         self.author = author
         self.participants = participants
         self.location = location
+        self.done = done
 
     def __repr__(self):
         return "<Protocol(id={}, protocoltype_id={})>".format(
@@ -99,8 +101,8 @@ class Protocol(db.Model):
 
     def fill_from_remarks(self, remarks):
         self.date = datetime.strptime(remarks["Datum"].value, "%d.%m.%Y")
-        self.start_time = time.strptime(remarks["Beginn"].value, "%H:%M")
-        self.end_time = time.strptime(remarks["Ende"].value, "%H:%M")
+        self.start_time = datetime.strptime(remarks["Beginn"].value, "%H:%M").time()
+        self.end_time = datetime.strptime(remarks["Ende"].value, "%H:%M").time()
         self.author = remarks["Autor"].value
         self.participants = remarks["Anwesende"].value
         self.location = remarks["Ort"].value
@@ -108,6 +110,22 @@ class Protocol(db.Model):
     def is_done(self):
         return self.done
 
+    def get_identifier(self):
+        return "{}-{}".format(
+            self.protocoltype.short_name.lower(),
+            self.date.strftime("%y-%m-%d"))
+
+    def get_etherpad_link(self):
+        return config.ETHERPAD_URL + self.get_identifier()
+
+    def get_etherpad_source_link(self):
+        return self.get_etherpad_link() + "/export/txt"
+
+    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()]
 
 class DefaultTOP(db.Model):
     __tablename__ = "defaulttops"
@@ -133,7 +151,7 @@ class TOP(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
     name = db.Column(db.String)
-    number = db.Column(db.String)
+    number = db.Column(db.Integer)
     planned = db.Column(db.Boolean)
 
     def __init__(self, protocol_id, name, number, planned):
@@ -184,6 +202,24 @@ class Todo(db.Model):
         return "<Todo(id={}, who={}, description={}, tags={}, done={})>".format(
             self.id, self.who, self.description, self.tags, self.done)
 
+    def get_first_protocol(self):
+        candidates = sorted(self.protocols, key=lambda p: p.date)
+        if len(candidates) == 0:
+            return None
+        return candidates[0]
+
+    def get_state(self):
+        return "[Erledigt]" if self.done else "[Offen]"
+
+    def render_html(self):
+        parts = [
+            self.get_state(),
+            "<strong>{}:</strong>".format(self.who),
+            self.description
+        ]
+        return " ".join(parts)
+
+
 class TodoProtocolAssociation(db.Model):
     __tablename__ = "todoprotocolassociations"
     todo_id = db.Column(db.Integer, db.ForeignKey("todos.id"), primary_key=True)
diff --git a/parser.py b/parser.py
index 26e2a13ddb44991662e04566d3bb77ea67860b87..5ded4dad626690dfe4976927f8a355bb5ab899db 100644
--- a/parser.py
+++ b/parser.py
@@ -221,6 +221,9 @@ class Remark(Element):
             level = 0
         print("{}remark: {}: {}".format(" " * level, self.name, self.value))
 
+    def get_tags(self, tags):
+        return tags
+
     @staticmethod
     def parse(match, current, linenumber=None):
         linenumber = Element.parse_inner(match, current, linenumber)
diff --git a/requirements.txt b/requirements.txt
index 928fb5c88ed319661823139d7094bf5c06c041dd..5b99e670aed8f6717aac26d6ed92001613f8988d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -29,6 +29,7 @@ pytz==2016.10
 PyYAML==3.12
 redis==2.10.5
 regex==2017.2.8
+requests==2.13.0
 six==1.10.0
 SQLAlchemy==1.1.5
 vine==1.1.3
diff --git a/server.py b/server.py
index 04c5f4e784d0bc1ee4f3721ecff81dd4fa79c356..030978ceffa89c8453c51731f2d5ef08c6682e52 100755
--- a/server.py
+++ b/server.py
@@ -2,19 +2,21 @@
 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
+from flask import Flask, g, current_app, request, session, flash, redirect, url_for, abort, render_template, Response, send_file
 from flask_script import Manager, prompt
 from flask_migrate import Migrate, MigrateCommand
 #from flask_socketio import SocketIO
 from celery import Celery
 from functools import wraps
+import requests
+from io import StringIO, BytesIO
 
 import config
-from shared import db, date_filter, datetime_filter, ldap_manager, security_manager
+from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager
 from utils import is_past, mail_manager, url_manager
 from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
-from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm
-from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable
+from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm
+from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable
 
 app = Flask(__name__)
 app.config.from_object(config)
@@ -38,6 +40,8 @@ app.jinja_env.trim_blocks = True
 app.jinja_env.lstrip_blocks = True
 app.jinja_env.filters["datify"] = date_filter
 app.jinja_env.filters["datetimify"] = datetime_filter
+app.jinja_env.filters["timify"] = time_filter
+app.jinja_env.filters["datify_long"] = date_filter_long
 app.jinja_env.filters["url_complete"] = url_manager.complete
 app.jinja_env.tests["auth_valid"] = security_manager.check_user
 
@@ -290,9 +294,9 @@ def move_default_top(type_id, top_id, diff):
     default_top.number += int(diff)
     db.session.commit()
     return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id))
-    
 
-@app.route("/protocol/list")
+
+@app.route("/protocols/list")
 def list_protocols():
     is_logged_in = check_login()
     user = current_user()
@@ -302,9 +306,87 @@ def list_protocols():
         or (is_logged_in and (
             protocol.protocoltype.public_group in user.groups
             or protocol.protocoltype.private_group in user.groups))]
+    # TODO: sort by date and paginate
     protocols_table = ProtocolsTable(protocols)
     return render_template("protocols-list.html", protocols=protocols, protocols_table=protocols_table)
-    
+
+@app.route("/protocol/new", methods=["GET", "POST"])
+@login_required
+def new_protocol():
+    user = current_user()
+    protocoltypes = ProtocolType.query.all()
+    form = NewProtocolForm(protocoltypes)
+    if form.validate_on_submit():
+        protocoltype = ProtocolType.query.filter_by(id=form.protocoltype.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(protocoltype.id, form.date.data)
+        db.session.add(protocol)
+        db.session.commit()
+        return redirect(request.args.get("next") or url_for("list_protocols"))
+    return render_template("protocol-new.html", form=form, protocoltypes=protocoltypes)
+
+@app.route("/protocol/show/<int:protocol_id>")
+def show_protocol(protocol_id):
+    user = current_user()
+    protocol = Protocol.query.filter_by(id=protocol_id).first()
+    if protocol is None or not protocol.protocoltype.has_public_view_right(user):
+        flash("Invalides Protokoll.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    errors_table = ErrorsTable(protocol.errors)
+    return render_template("protocol-show.html", protocol=protocol, errors_table=errors_table)
+
+@app.route("/protocol/etherpull/<int:protocol_id>")
+def etherpull_protocol(protocol_id):
+    user = current_user()
+    protocol = Protocol.query.filter_by(id=protocol_id).first()
+    if protocol is None or not protocol.protocoltype.has_modify_right(user):
+        flash("Invalides Protokoll oder keine Berechtigung.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    source_req = requests.get(protocol.get_etherpad_source_link())
+    #source = source_req.content.decode("utf-8")
+    source = source_req.text
+    print(source.split("\r"))
+    #print(source.split("\n"))
+    protocol.source = source
+    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))
+
+@app.route("/protocol/source/<int:protocol_id>")
+@login_required
+def get_protocol_source(protocol_id):
+    user = current_user()
+    protocol = Protocol.query.filter_by(id=protocol_id).first()
+    if protocol is None or not protocol.protocoltype.has_modify_right(user):
+        flash("Invalides Protokoll oder keine Berechtigung.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    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_identifier()))
+
+@app.route("/protocol/update/<int:protocol_id>")
+@login_required
+def update_protocol(protocol_id):
+    user = current_user()
+    protocol = Protocol.query.filter_by(id=protocol_id).first()
+    if protocol is None or not protocol.protocoltype.has_modify_right(user):
+        flash("Invalides Protokoll oder keine Berechtigung.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    # TODO: render form to upload a new version
+
+
+@app.route("/todos/list")
+def list_todos():
+    is_logged_in = check_login()
+    user = current_user()
+    todos = Todos.query.all()
+    # TODO: paginate
+    todos_table = TodosTable(todos)
+    return render_template("todos-list.html", todos=todos, todos_table=todos_table)
+
+
 
 @app.route("/login", methods=["GET", "POST"])
 def login():
diff --git a/static/css/style.css b/static/css/style.css
index 062285afd2a44356446c3f0aae333d05e3d2865e..5d8cdaaa21cf49de678da8d245dff82e89d8c307 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -22,3 +22,8 @@ body {
 h3 > a {
     font-size: 18px;
 }
+
+form {
+    max-width: 350px;
+    margin: 0 auto;
+}
diff --git a/tasks.py b/tasks.py
index 3add3d17b22147169c5d5f5a04532005aca80d2b..47102caae0914d6a86f471210b712a3df8623479 100644
--- a/tasks.py
+++ b/tasks.py
@@ -4,7 +4,7 @@ import os
 import subprocess
 import shutil
 
-from models.database import Document, Protocol, Error, Todo, Decision
+from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP
 from server import celery, app
 from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, time_filter
 from utils import mail_manager, url_manager, encode_kwargs, decode_kwargs
@@ -49,6 +49,9 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
             protocol = Protocol.query.filter_by(id=protocol_id).first()
             if protocol is None:
                 raise Exception("No protocol given. Aborting parsing.")
+            for error in protocol.errors:
+                protocol.errors.remove(error)
+            db.session.commit()
             if protocol.source is None:
                 error = protocol.create_error("Parsing", "Protocol source is None", "")
                 db.session.add(error)
@@ -68,9 +71,9 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
                 db.session.add(error)
                 db.session.commit()
                 return
-            remarks = {element.name: element for element in tree.children is isinstance(element, Remark)}
+            remarks = {element.name: element for element in tree.children if isinstance(element, Remark)}
             required_fields = ["Datum", "Anwesende", "Beginn", "Ende", "Autor", "Ort"]
-            missing_fields = [field for field in required_files 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", "Missing fields", ", ".join(missing_fields))
                 db.session.add(error)
@@ -147,6 +150,16 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
                 decision = Decision(protocol_id=protocol.id, content=decision_tag.values[0])
                 db.session.add(decision)
                 db.session.commit()
+            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, fork.name, index, False)
+                db.session.add(top)
+            db.session.commit()
+            protocol.done = True
+            db.session.commit()
 
 
             
diff --git a/templates/protocol-new.html b/templates/protocol-new.html
new file mode 100644
index 0000000000000000000000000000000000000000..56d303e23064bbbc9ca85cc1a4a240b727a47a63
--- /dev/null
+++ b/templates/protocol-new.html
@@ -0,0 +1,9 @@
+{% extends "layout.html" %}
+{% from "macros.html" import render_form %}
+{% block title %}Protokoll anlegen{% endblock %}
+
+{% block content %}
+<div class="container">
+    {{render_form(form, action_url=url_for("new_protocol"), action_text="Anlegen")}}
+</div>
+{% endblock %}
diff --git a/templates/protocol-show.html b/templates/protocol-show.html
new file mode 100644
index 0000000000000000000000000000000000000000..55f9311ca29c469a806a52c59f70bf9b61eefd63
--- /dev/null
+++ b/templates/protocol-show.html
@@ -0,0 +1,80 @@
+{% extends "layout.html" %}
+{% from "macros.html" import render_table %}
+{% block title %}Protokoll{% endblock %}
+
+{% block content %}
+<div class="container">
+    <div class="btn-group">
+        <a class="btn {% if protocol.source is none %}btn-primary{% else %}btn-default{% endif %}" href="{{url_for("etherpull_protocol", protocol_id=protocol.id)}}">From etherpad</a>
+        {% if protocol.source is not none %}
+            <a class="btn btn-primary" href="{{url_for("get_protocol_source", protocol_id=protocol.id)}}">Download Quelltext</a>
+        {% endif %} 
+        {% if protocol.is_done() %}
+            <a class="btn btn-success" href="{{url_for("update_protocol", protocol_id=protocol.id)}}">Protokoll editieren</a>
+        {% endif %}
+        <a class="btn btn-default" href="{{protocol.get_etherpad_link()}}" target="_blank">Etherpad</a>
+        <a class="btn btn-default" href="{{url_for("show_type", type_id=protocol.protocoltype.id)}}">Typ</a>
+    </div>
+    <div class="row">
+        <div id="left-column" class="col-lg-6">
+            <h2>Protokoll: {{protocol.protocoltype.name}} vom {{protocol.date|datify}}</h2>
+            {% if protocol.is_done() %}
+                <p><strong>Datum:</strong> {{protocol.date|datify_long}}</p>
+                <p><strong>Zeit:</strong> von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}</p>
+                <p><strong>Ort:</strong> {{protocol.location}}</p>
+                <p><strong>Protokollant:</strong> {{protocol.author}}</p>
+                <p><strong>Anwesende:</strong> {{protocol.participants}}</p>
+            {% else %}
+                <p><strong>Geplant:</strong> {{protocol.date|datify_long}}</p>
+            {% endif %}
+
+            <h3>Tagesordnung</h3>
+            <ul>
+            {% if not protocol.has_nonplanned_tops() %}
+                {% for default_top in protocol.protocoltype.default_tops %}
+                    {% if not default_top.is_at_end() %}
+                        <li>{{default_top.name}}</li>
+                    {% endif %}
+                {% endfor %}
+            {% endif %}
+            {% for top in protocol.tops %}
+                <li>{{top.name}}</li>
+            {% endfor %}
+            {% if not protocol.has_nonplanned_tops() %}
+                {% for default_top in protocol.protocoltype.default_tops %}
+                    {% if default_top.is_at_end() %}
+                        <li>{{default_top.name}}</li>
+                    {% endif %}
+                {% endfor %}
+            {% endif %}
+            </ul>
+
+            {% if protocol.is_done() %}
+            <h3>Beschlüsse</h3>
+            <ul>
+                {% for decision in protocol.decisions %}
+                    <li>{{decision.content}}</li>
+                {% endfor %}
+            </ul>
+            {% endif %}
+        </div>
+        <div id="right-column" class="col-lg-6">
+            {% if protocol.is_done() %}
+            <h3>Todos dieser Sitzung <a href="{{url_for("list_todos")}}">Aktuelle Todos</a></h3>
+            <ul>
+                {% for todo in protocol.get_originating_todos() %}
+                    <li>{{todo.render_html()|safe}}</li>
+                {% endfor %}
+            </ul>
+            {% endif %}
+            {% if protocol.errors|length > 0 %}
+                {{render_table(errors_table)}}
+            {% endif %}
+            {% if protocol.documents|length > 0 %}
+                <h3>Anhang</h3>
+                {# TODO: render documents table here #}
+            {% endif %}
+        </div>
+    </div>
+</div>
+{% endblock %}
diff --git a/views/forms.py b/views/forms.py
index 166d39b2d213d1222b6df4252a6d2cfa81c9e7fa..6f6bb5840ed37cc5bf9674808d2a9f95e0f17a3c 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -1,5 +1,5 @@
 from flask_wtf import FlaskForm
-from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField
+from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField
 from wtforms.validators import InputRequired
 
 class LoginForm(FlaskForm):
@@ -24,3 +24,11 @@ class MeetingReminderForm(FlaskForm):
     days_before = IntegerField("Tage vor Sitzung", validators=[InputRequired("Du musst eine Dauer angeben.")])
     send_public = BooleanField("Öffentlich einladen")
     send_private = BooleanField("Intern einladen")
+
+class NewProtocolForm(FlaskForm):
+    protocoltype = SelectField("Typ", choices=[], coerce=int)
+    date = DateField("Datum", validators=[InputRequired("Du musst ein Datum angeben.")], format="%d.%m.%Y")
+
+    def __init__(self, protocoltypes, **kwargs):
+        super().__init__(**kwargs)
+        self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
diff --git a/views/tables.py b/views/tables.py
index 50a2128c8751ded02bf98b7525ed7808f72af91a..1fcc0a991ea226839f45a4a0dce1798d1ff2006d 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -1,7 +1,7 @@
 # coding: utf-8
 from flask import Markup, url_for, request
 from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decision
-from shared import date_filter
+from shared import date_filter, datetime_filter
 
 class Table:
     def __init__(self, title, values, newlink=None, newtext=None):
@@ -49,16 +49,17 @@ class SingleValueTable:
 
 class ProtocolsTable(Table):
     def __init__(self, protocols):
-        super().__init__("Protokolle", protocols, newlink=None)
+        super().__init__("Protokolle", protocols, newlink=url_for("new_protocol"))
 
     def headers(self):
-        return ["ID", "Sitzung", "Datum"]
+        return ["ID", "Sitzung", "Status", "Datum"]
 
     def row(self, protocol):
         return [
-            Table.link(url_for("protocol_view", protocol_id=protocol.id), str(protocol.id)),
-            protocol.protocoltype.name,
-            date_filter(protocol.data)
+            Table.link(url_for("show_protocol", protocol_id=protocol.id), str(protocol.id)),
+            Table.link(url_for("show_type", type_id=protocol.protocoltype.id), protocol.protocoltype.name),
+            Table.link(url_for("show_protocol", protocol_id=protocol.id), "Fertig" if protocol.is_done() else "Geplant"),
+            date_filter(protocol.date)
         ]
 
 class ProtocolTypesTable(Table):
@@ -142,3 +143,34 @@ class MeetingRemindersTable(Table):
         if reminder.send_private:
             parts.append("Intern")
         return " und ".join(parts)
+
+class ErrorsTable(Table):
+    def __init__(self, errors):
+        super().__init__("Fehler", errors)
+
+    def headers(self):
+        return ["Protokoll", "Fehler", "Zeitpunkt", "Beschreibung"]
+
+    def row(self, error):
+        return [
+            Table.link(url_for("show_protocol", protocol_id=error.protocol.id), error.protocol.get_identifier()),
+            error.name,
+            datetime_filter(error.datetime),
+            error.description
+        ]
+
+class TodosTable(Table):
+    def __init__(self, todos):
+        super().__init__("Todos", todos)
+
+    def headers(self):
+        return ["Status", "Sitzung", "Name", "Aufgabe"]
+
+    def row(self, todo):
+        protocol = todo.get_first_protocol()
+        return [
+            todo.get_state(),
+            Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier()),
+            todo.who,
+            todo.description
+        ]