diff --git a/server.py b/server.py
index 029eedacf61ae3623f7aab88d81022db52aa7569..6584081df16bd055715b4f1c4eaf334b75a5cba1 100755
--- a/server.py
+++ b/server.py
@@ -20,10 +20,10 @@ import math
 
 import config
 from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager, current_user, check_login, login_required, group_required, class_filter
-from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms
+from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg
 from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
-from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm
-from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable
+from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm
+from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable
 
 app = Flask(__name__)
 app.config.from_object(config)
@@ -718,6 +718,11 @@ def list_todos():
             todo for todo in todos
             if search_term.lower() in todo.description.lower()
         ]
+    def _sort_key(todo):
+        first_protocol = todo.get_first_protocol()
+        result = (not todo.done, first_protocol.date if first_protocol is not None else datetime.now().date())
+        return result
+    todos = sorted(todos, key=_sort_key, reverse=True)
     page = _get_page()
     page_count = int(math.ceil(len(todos) / config.PAGE_LENGTH))
     if page >= page_count:
@@ -728,6 +733,88 @@ def list_todos():
     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)
 
+@app.route("/todo/new", methods=["GET", "POST"])
+@login_required
+def new_todo():
+    user = current_user()
+    protocoltype_id = optional_int_arg("type_id")
+    protocol_id = optional_int_arg("protocol_id")
+    protocoltype = ProtocolType.query.filter_by(id=protocoltype_id).first()
+    protocol = Protocol.query.filter_by(id=protocol_id).first()
+    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"))
+    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):
+            flash("Invalider Protokolltyp.")
+            return redirect(request.args.get("next") or url_for("index"))
+        todo = Todo(type_id=form.protocoltype_id.data, who=form.who.data,
+            description=form.description.data, tags=form.tags.data,
+            done=form.done.data)
+        if protocol is not None:
+            todo.protocols.append(protocol)
+        db.session.add(todo)
+        db.session.commit()
+        todo.number = todo.id
+        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))
+        else:
+            return redirect(request.args.get("next") or url_for("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)
+
+@app.route("/todo/edit/<int:todo_id>", methods=["GET", "POST"])
+@login_required
+def edit_todo(todo_id):
+    user = current_user()
+    todo = Todo.query.filter_by(id=todo_id).first()
+    if todo is None or not todo.protocoltype.has_modify_right(user):
+        flash("Invalides Todo oder unzureichende Berechtigung.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    form = TodoForm(obj=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=todo.protocoltype.id))
+    return render_template("todo-edit.html", form=form, todo=todo)
+
+@app.route("/todo/show/<int:todo_id>")
+@login_required
+def show_todo(todo_id):
+    user = current_user()
+    todo = Todo.query.filter_by(id=todo_id).first()
+    if todo is None or not todo.protocoltype.has_private_view_right(user):
+        flash("Invalides Todo oder unzureichende Berechtigung.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    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
+def delete_todo(todo_id):
+    user = current_user()
+    todo = Todo.query.filter_by(id=todo_id).first()
+    if todo is None or not todo.protocoltype.has_private_view_right(user):
+        flash("Invalides Todo oder unzureichende Berechtigung.", "alert-error")
+        return redirect(request.args.get("next") or url_for("index"))
+    type_id = todo.protocoltype.id
+    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=type_id))
+    
+
+
 @app.route("/decisions/list")
 def list_decisions():
     is_logged_In = check_login()
diff --git a/templates/protocol-show.html b/templates/protocol-show.html
index d9f75865ece24925ae32624a490f8c8e1cc9bc96..f2aef05326560bdc24bb56ca7c21d1873cc11d54 100644
--- a/templates/protocol-show.html
+++ b/templates/protocol-show.html
@@ -2,7 +2,7 @@
 {% from "macros.html" import render_table, render_form %}
 {% block title %}Protokoll{% endblock %}
 
-{% set loggedin = check_login() %}
+{% set logged_in = check_login() %}
 {% set user = current_user() %}
 {% set has_public_view_right = protocol.protocoltype.has_public_view_right(user) %}
 {% set has_private_view_right = protocol.protocoltype.has_private_view_right(user) %}
@@ -69,7 +69,7 @@
         </div>
         <div id="right-column" class="col-lg-6">
             {% if protocol.is_done() and has_public_view_right and logged_in %}
-                <h3>Todos dieser Sitzung <a href="{{url_for("list_todos")}}">Aktuelle Todos</a></h3>
+                <h3>Todos dieser Sitzung <a href="{{url_for("list_todos")}}">Aktuelle Todos</a> <a href="{{url_for("new_todo", protocol_id=protocol.id)}}">Neu</a></h3>
                 <ul>
                     {% if protocol.get_originating_todos()|length > 0 %}
                         {% for todo in protocol.get_originating_todos() %}
diff --git a/templates/protocol-template.txt b/templates/protocol-template.txt
index 5818a73b3a1d4b5b2f770a788858d7dc75127e43..4f1516f532d83cb3dab051e8933c94fd51888d26 100644
--- a/templates/protocol-template.txt
+++ b/templates/protocol-template.txt
@@ -10,7 +10,7 @@
             {% if top.name == "Todos" %}
                 {% if protocol.todos|length > 0 %}
                     {% for todo in protocol.todos %}
-    [todo;{{todo.who}};{{todo.description}};id {{todo.get_number()}}];
+    [todo;{{todo.who}};{{todo.description}};id {{todo.get_id()}}];
                     {% endfor %}
                 {% else %}
 
diff --git a/templates/todo-edit.html b/templates/todo-edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..13a2ba231cd3f9a6c1bb9ddf2965b068a6454c31
--- /dev/null
+++ b/templates/todo-edit.html
@@ -0,0 +1,9 @@
+{% extends "layout.html" %}
+{% from "macros.html" import render_form %}
+{% block title %}Todo ändern{% endblock %}
+
+{% block content %}
+<div class="container">
+    {{render_form(form, action_url=url_for("edit_todo", todo_id=todo.id), action_text="Ändern")}}
+</div>
+{% endblock %}
diff --git a/templates/todo-new.html b/templates/todo-new.html
new file mode 100644
index 0000000000000000000000000000000000000000..348f4a495568fc8237ac416956d2e6802b4e9d85
--- /dev/null
+++ b/templates/todo-new.html
@@ -0,0 +1,9 @@
+{% extends "layout.html" %}
+{% from "macros.html" import render_form %}
+{% block title %}Todo anlegen{% endblock %}
+
+{% block content %}
+<div class="container">
+    {{render_form(form, action_url=url_for("new_todo", protocol_id=protocol.id, protocoltype_id=protocoltype.id), action_text="Anlegen")}}
+</div>
+{% endblock %}
diff --git a/templates/todo-show.html b/templates/todo-show.html
new file mode 100644
index 0000000000000000000000000000000000000000..40ea947ccd79b41eacfde82179b2016447f0f579
--- /dev/null
+++ b/templates/todo-show.html
@@ -0,0 +1,9 @@
+{% extends "layout.html" %}
+{% from "macros.html" import render_single_table %}
+{% block title %}Todo{% endblock %}
+
+{% block content %}
+<div class="container">
+    {{render_single_table(todo_table)}}
+</div>
+{% endblock %}
diff --git a/utils.py b/utils.py
index 7cd92045111a0662fbe34e0ee18afcc73f686732..d98df8186029fcac15db567db3e92862d0498762 100644
--- a/utils.py
+++ b/utils.py
@@ -1,4 +1,4 @@
-from flask import render_template
+from flask import render_template, request
 
 import random
 import string
@@ -152,3 +152,9 @@ def split_terms(text, quote_chars="\"'", separators=" \t\n"):
     if len(current_term) > 0:
         terms.append(current_term)
     return terms
+
+def optional_int_arg(name):
+    try:
+        return int(request.args.get(name))
+    except (ValueError, TypeError):
+        return None
diff --git a/views/forms.py b/views/forms.py
index 1c579e8edb375ae8e668f874a89b0717980b43f4..9a5bb77b311c4f4d22043aebf9721e9e0a837f09 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -4,6 +4,12 @@ from wtforms.validators import InputRequired, Optional
 
 import config
 
+def get_protocoltype_choices(protocoltypes, add_all=True):
+    choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
+    if add_all:
+        choices.insert(0, (-1, "Alle"))
+    return choices
+
 class LoginForm(FlaskForm):
     username = StringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
     password = PasswordField("Passwort", validators=[InputRequired("Bitte gib dein Passwort ein.")])
@@ -39,7 +45,7 @@ class NewProtocolForm(FlaskForm):
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
+        self.protocoltype.choices = get_protocoltype_choices(protocoltypes, add_all=False)
 
 class DocumentUploadForm(FlaskForm):
     document = FileField("Datei")
@@ -54,7 +60,7 @@ class NewProtocolSourceUploadForm(FlaskForm):
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
+        self.protocoltype.choices = get_protocoltype_choices(protocoltypes, add_all=False)
 
 class NewProtocolFileUploadForm(FlaskForm):
     file = FileField("Datei")
@@ -63,7 +69,7 @@ class NewProtocolFileUploadForm(FlaskForm):
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
+        self.protocoltype.choices = get_protocoltype_choices(protocoltypes, add_all=False)
 
 class ProtocolForm(FlaskForm):
     date = DateField("Datum", validators=[InputRequired("Bitte gib das Datum des Protkolls an.")], format="%d.%m.%Y")
@@ -84,6 +90,21 @@ class SearchForm(FlaskForm):
 
     def __init__(self, protocoltypes, **kwargs):
         super().__init__(**kwargs)
-        choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
-        choices.insert(0, (-1, "Alle"))
-        self.protocoltype.choices = choices
+        self.protocoltype.choices = get_protocoltype_choices(protocoltypes)
+
+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.")])
+    tags = StringField("Weitere Tags")
+    done = BooleanField("Erledigt")
+    
+    def __init__(self, protocoltypes, **kwargs):
+        super().__init__(**kwargs)
+        self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes, add_all=False)
+
+class TodoForm(FlaskForm):
+    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.")])
+    tags = StringField("Weitere Tags")
+    done = BooleanField("Erledigt")
diff --git a/views/tables.py b/views/tables.py
index ce3389f5e2375b570527bfd0ba533f385c5ff33d..0cc7c3069ceeb9ff504ba664740e751d599eeb07 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -195,19 +195,57 @@ class ErrorsTable(Table):
 
 class TodosTable(Table):
     def __init__(self, todos):
-        super().__init__("Todos", todos)
+        super().__init__("Todos", todos, newlink=url_for("new_todo"))
 
     def headers(self):
-        return ["Status", "Sitzung", "Name", "Aufgabe"]
+        return ["ID", "Status", "Sitzung", "Name", "Aufgabe", ""]
 
     def row(self, todo):
+        user = current_user()
         protocol = todo.get_first_protocol()
-        return [
+        row = [
+            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_identifier()) if protocol is not None else "",
+            Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier())
+                if protocol is not None
+                else Table.link(url_for("list_protocols", protocoltype=todo.protocoltype.id), todo.protocoltype.short_name),
             todo.who,
-            todo.description
+            todo.description,
         ]
+        if todo.protocoltype.has_modify_right(user):
+            row.append(Table.link(url_for("edit_todo", todo_id=todo.id), "Ändern"))
+        else:
+            row.append("")
+        return row
+
+class TodoTable(SingleValueTable):
+    def __init__(self, todo):
+        super().__init__("Todo", todo)
+
+    def headers(self):
+        return ["ID", "Status", "Sitzung", "Name", "Aufgabe", "Tags", ""]
+
+    def row(self):
+        user = current_user()
+        protocol = self.value.get_first_protocol()
+        row = [
+            self.value.get_id(),
+            self.value.get_state_plain(),
+            Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier())
+                if protocol is not None
+                else Table.link(url_for("list_protocols", protocolttype=self.value.protocoltype.id), self.value.protocoltype.short_name),
+            self.value.who,
+            self.value.description,
+            self.value.tags
+        ]
+        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?")
+            ]))
+        else:
+            row.append("")
+        return row
 
 class DecisionsTable(Table):
     def __init__(self, decisions):