diff --git a/migrations/versions/24bd2198a626_.py b/migrations/versions/24bd2198a626_.py
new file mode 100644
index 0000000000000000000000000000000000000000..78cbf6c47d5ac36c5915f530e72a5d7134e1ba8b
--- /dev/null
+++ b/migrations/versions/24bd2198a626_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: 24bd2198a626
+Revises: 2e2682dfac21
+Create Date: 2017-02-24 17:20:07.135782
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '24bd2198a626'
+down_revision = '2e2682dfac21'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('todos', sa.Column('number', sa.Integer(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('todos', 'number')
+    # ### end Alembic commands ###
diff --git a/migrations/versions/2e2682dfac21_.py b/migrations/versions/2e2682dfac21_.py
new file mode 100644
index 0000000000000000000000000000000000000000..60d7a0f5320d4f82c8f1d21a2349a31138cba552
--- /dev/null
+++ b/migrations/versions/2e2682dfac21_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: 2e2682dfac21
+Revises: aebae2c4523d
+Create Date: 2017-02-24 16:31:01.729972
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '2e2682dfac21'
+down_revision = 'aebae2c4523d'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('todos', sa.Column('is_id_fixed', sa.Boolean(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('todos', 'is_id_fixed')
+    # ### end Alembic commands ###
diff --git a/models/database.py b/models/database.py
index 0402687db9337adb973927b65c10eb92a48af497..582c9ece0f1faad29f2f7f443d4586861988e6d1 100644
--- a/models/database.py
+++ b/models/database.py
@@ -102,17 +102,17 @@ class Protocol(db.Model):
         return Error(self.id, action, name, now, description)
 
     def fill_from_remarks(self, remarks):
-        new_date = datetime.strptime(remarks["Datum"].value, "%d.%m.%Y").date()
+        new_date = datetime.strptime(remarks["Datum"].value.strip(), "%d.%m.%Y").date()
         if self.date is not None:
             if new_date != self.date:
                 raise DateNotMatchingException(original_date=self.date, protocol_date=new_date)
         else:
             self.date = new_date
-        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
+        self.start_time = datetime.strptime(remarks["Beginn"].value.strip(), "%H:%M").time()
+        self.end_time = datetime.strptime(remarks["Ende"].value.strip(), "%H:%M").time()
+        self.author = remarks["Autor"].value.strip()
+        self.participants = remarks["Anwesende"].value.strip()
+        self.location = remarks["Ort"].value.strip()
 
     def is_done(self):
         return self.done
@@ -160,6 +160,9 @@ class Protocol(db.Model):
             return public_candidates[0]
         return None
 
+    def get_template(self):
+        return render_template("protocol-template.txt", protocol=self)
+
     def delete_orphan_todos(self):
         orphan_todos = [
             todo for todo in self.todos
@@ -244,22 +247,28 @@ def on_document_delete(mapper, connection, document):
 class Todo(db.Model):
     __tablename__ = "todos"
     id = db.Column(db.Integer, primary_key=True)
+    number = db.Column(db.Integer)
     who = db.Column(db.String)
     description = db.Column(db.String)
     tags = db.Column(db.String)
     done = db.Column(db.Boolean)
+    is_id_fixed = db.Column(db.Boolean, default=False)
 
     protocols = relationship("Protocol", secondary="todoprotocolassociations", backref="todos")
 
-    def __init__(self, who, description, tags, done):
+    def __init__(self, who, description, tags, done, number=None):
         self.who = who
         self.description = description
         self.tags = tags
         self.done = done
+        self.number = number
 
     def __repr__(self):
-        return "<Todo(id={}, who={}, description={}, tags={}, done={})>".format(
-            self.id, self.who, self.description, self.tags, self.done)
+        return "<Todo(id={}, number={}, who={}, description={}, tags={}, done={})>".format(
+            self.id, self.number, self.who, self.description, self.tags, self.done)
+
+    def get_id(self):
+        return self.number if self.number is not None else self.id
 
     def get_first_protocol(self):
         candidates = sorted(self.protocols, key=lambda p: p.date)
diff --git a/server.py b/server.py
index 05c8d4172793c6a7f7b93e83e986e157462890cc..a9d1962a935e9ff0be8ce1139cb1e8f6af370c94 100755
--- a/server.py
+++ b/server.py
@@ -304,27 +304,12 @@ def new_protocol():
         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 redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
     type_id = request.args.get("type_id")
     if type_id is not None:
         form.protocoltype.data = type_id
     return render_template("protocol-new.html", form=form, upload_form=upload_form, protocoltypes=protocoltypes)
 
-@app.route("/protocol/edit/<int:protocol_id>", methods=["POST"])
-@login_required
-def edit_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 fehlende Zugriffsrechte.", "alert-error")
-        return redirect(request.args.get("next") or url_for("list_protocols"))
-    form = ProtocolForm(obj=protocol)
-    if form.validate_on_submit():
-        form.populate_obj(protocol)
-        db.session.commit()
-        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
-    return redirect(request.args.get("fail") or url_for("update_protocol", protocol_id=protocol.id))
-
 @app.route("/protocol/show/<int:protocol_id>")
 def show_protocol(protocol_id):
     user = current_user()
@@ -439,7 +424,18 @@ def get_protocol_source(protocol_id):
     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>")
+@app.route("/protocol/template/<int:protocol_id>")
+@login_required
+def get_protocol_template(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.get_template().encode("utf-8"))
+    return send_file(file_like, cache_timeout=1, as_attachment=True, attachment_filename="{}-template.txt".format(protocol.get_identifier()))
+
+@app.route("/protocol/update/<int:protocol_id>", methods=["GET", "POST"])
 @login_required
 def update_protocol(protocol_id):
     user = current_user()
@@ -449,6 +445,10 @@ def update_protocol(protocol_id):
         return redirect(request.args.get("next") or url_for("index"))
     upload_form = KnownProtocolSourceUploadForm()
     edit_form = ProtocolForm(obj=protocol)
+    if edit_form.validate_on_submit():
+        edit_form.populate_obj(protocol)
+        db.session.commit()
+        return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
     return render_template("protocol-update.html", upload_form=upload_form, edit_form=edit_form, protocol=protocol)
 
 @app.route("/protocol/tops/new/<int:protocol_id>", methods=["GET", "POST"])
diff --git a/tasks.py b/tasks.py
index 8a97791d59721a05bc21417cb514f070e332569c..7da102cba82a55100761c0cfe3ebf01f2887bb38 100644
--- a/tasks.py
+++ b/tasks.py
@@ -121,9 +121,9 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
                 who = todo_tag.values[0]
                 what = todo_tag.values[1]
                 todo = None
+                field_id = None
                 for other_field in todo_tag.values[2:]:
                     if other_field.startswith(ID_FIELD_BEGINNING):
-                        field_id = 0
                         try:
                             field_id = int(other_field[len(ID_FIELD_BEGINNING):])
                         except ValueError:
@@ -133,10 +133,27 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
                             db.session.add(error)
                             db.session.commit()
                             return
-                        todo = Todo.query.filter_by(id=field_id).first()
+                        todo = Todo.query.filter_by(number=field_id).first()
+                who = who.strip()
+                what = what.strip()
                 if todo is None:
-                    todo = Todo(who=who, description=what, tags="", done=False)
-                    db.session.add(todo)
+                    if field_id is not None:
+                        candidate = Todo.query.filter_by(who=who, description=what, number=None).first()
+                        if candidate is None:
+                            candidate = Todo.query.filter_by(description=what, number=None).first()
+                        if candidate is not None:
+                            candidate.number = field_id
+                            todo = candidate
+                        else:
+                            todo = Todo(who=who, description=what, tags="", done=False)
+                            todo.number = field_id
+                    else:
+                        candidate = Todo.query.filter_by(who=who, description=what).first()
+                        if candidate is not None:
+                            todo = candidate
+                        else:
+                            todo = Todo(who=who, description=what, tags="", done=False)
+                            db.session.add(todo)
                 todo.protocols.append(protocol)
                 todo_tags_internal = todo.tags.split(";")
                 for other_field in todo_tag.values[2:]:
diff --git a/templates/protocol-show.html b/templates/protocol-show.html
index 1bb9db94ff79269fbf8784d4d5a7845109d5b50a..20dd1a6983112fb4e53cac72744bfe652cb9364b 100644
--- a/templates/protocol-show.html
+++ b/templates/protocol-show.html
@@ -12,11 +12,14 @@
 <div class="container">
     <div class="btn-group">
         {% if has_modify_right %}
-            <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>
+            <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>
             {% 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 %} 
             <a class="btn {% if protocol.is_done() %}btn-success{% else %}btn-default{% endif %}" href="{{url_for("update_protocol", protocol_id=protocol.id)}}">Protokoll editieren</a>
+            {% if not protocol.is_done() %}
+                <a class="btn btn-default" href="{{url_for("get_protocol_template", protocol_id=protocol.id)}}">Vorlage herunterladen</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>
             <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_identifier()}} löschen möchtest?');">Löschen</a>
diff --git a/templates/protocol-template.txt b/templates/protocol-template.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5818a73b3a1d4b5b2f770a788858d7dc75127e43
--- /dev/null
+++ b/templates/protocol-template.txt
@@ -0,0 +1,40 @@
+#Datum;{{protocol.date|datify}}
+#Anwesende;
+#Beginn;
+#Ende;
+#Autor;
+#Ort;
+
+{% macro render_top(top) %}
+{TOP {{top.name}}
+            {% if top.name == "Todos" %}
+                {% if protocol.todos|length > 0 %}
+                    {% for todo in protocol.todos %}
+    [todo;{{todo.who}};{{todo.description}};id {{todo.get_number()}}];
+                    {% endfor %}
+                {% else %}
+
+                {% endif %}
+            {% else %}
+
+            {% endif %}
+}
+{% endmacro -%}
+
+{% if not protocol.has_nonplanned_tops() %}
+    {% for default_top in protocol.protocoltype.default_tops %}
+        {% if not default_top.is_at_end() %}
+            {{-render_top(default_top)}}
+        {% endif %}
+    {% endfor %}
+{% endif %}
+{% for top in protocol.tops %}
+    {{-render_top(top)}}
+{% endfor %}
+{% if not protocol.has_nonplanned_tops() %}
+    {% for default_top in protocol.protocoltype.default_tops %}
+        {% if default_top.is_at_end() %}
+            {{-render_top(default_top)}}
+        {% endif %}
+    {% endfor %}
+{% endif %}
diff --git a/templates/protocol-tops-include.html b/templates/protocol-tops-include.html
index ca9a0a63c76463789e1f57e0ee5fb7de27911b04..c7302ed1d7e5b88b39568d8c662014c17e266c32 100644
--- a/templates/protocol-tops-include.html
+++ b/templates/protocol-tops-include.html
@@ -9,10 +9,10 @@
     {% for top in protocol.tops %}
         <li>
             {{top.name}}
-            {% if has_private_view_right %}
+            {% if not protocol.is_done() and has_private_view_right %}
                 ({{top.number}})
             {% endif %}
-            {% if has_modify_right %}
+            {% 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>
diff --git a/templates/protocol-update.html b/templates/protocol-update.html
index 28e8c47a5145b953808427c0c03c861699766e56..749be3081de739043b4f380dbe37fb6fda8572e3 100644
--- a/templates/protocol-update.html
+++ b/templates/protocol-update.html
@@ -6,7 +6,7 @@
 <div class="container">
     <div class="row">
         <div id="left-column" class="col-lg-6">
-            {{render_form(edit_form, action_url=url_for("edit_protocol", protocol_id=protocol.id), action_text="Ändern")}}
+            {{render_form(edit_form, action_url=url_for("update_protocol", protocol_id=protocol.id), action_text="Ändern")}}
         </div>
         <div id="right-column" class="col-lg-6">
             <h3>Protokoll herunterladen</h3>
diff --git a/views/forms.py b/views/forms.py
index a8303bf4448c4ac268cc7ee3df7c9b0a93ec9900..3392ba286c08c62f7d1f1899dbc88c33cdd80bd9 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -1,6 +1,6 @@
 from flask_wtf import FlaskForm
 from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField, DateTimeField
-from wtforms.validators import InputRequired
+from wtforms.validators import InputRequired, Optional
 
 class LoginForm(FlaskForm):
     username = StringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
@@ -50,8 +50,8 @@ class NewProtocolSourceUploadForm(FlaskForm):
 
 class ProtocolForm(FlaskForm):
     date = DateField("Datum", validators=[InputRequired("Bitte gib das Datum des Protkolls an.")], format="%d.%m.%Y")
-    start_time = DateTimeField("Beginn", format="%H:%M")
-    end_time = DateTimeField("Ende", format="%H:%M")
+    start_time = DateTimeField("Beginn", format="%H:%M", validators=[Optional()])
+    end_time = DateTimeField("Ende", format="%H:%M", validators=[Optional()])
     location = StringField("Ort")
     author = StringField("Protokollant")
     participants = StringField("Anwesende")
diff --git a/views/tables.py b/views/tables.py
index 77013423346c7e8a508bdb4b628a235d3fbd5e76..a03970ec8890ebda49d571b3748b1efd5e1d6be7 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -177,11 +177,12 @@ class TodosTable(Table):
         super().__init__("Todos", todos)
 
     def headers(self):
-        return ["Status", "Sitzung", "Name", "Aufgabe"]
+        return ["ID", "Status", "Sitzung", "Name", "Aufgabe"]
 
     def row(self, todo):
         protocol = todo.get_first_protocol()
         return [
+            todo.get_id(),
             todo.get_state(),
             Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier()) if protocol is not None else "",
             todo.who,