diff --git a/mo/web/org_round.py b/mo/web/org_round.py
index e0f471319a4af848f18ce4077b07ecff9354e5e5..1b7455556abb6444c6538d264dd9f8da5a5b0dbd 100644
--- a/mo/web/org_round.py
+++ b/mo/web/org_round.py
@@ -47,11 +47,19 @@ def org_rounds():
     return render_template('org_rounds.html', rounds=rounds, level_names=mo.db.place_level_names)
 
 
-@app.route('/org/contest/r/<int:id>/')
+class TaskDeleteForm(FlaskForm):
+    delete_task_id = wtforms.IntegerField()
+    delete = wtforms.SubmitField('Smazat úlohu')
+
+
+@app.route('/org/contest/r/<int:id>/', methods=('GET', 'POST'))
 def org_round(id: int):
     sess = db.get_session()
     round, rr = get_round_rr(id, None)
 
+    can_manage_round = rr.have_right(mo.rights.Right.manage_round)
+    can_manage_contestants = rr.have_right(mo.rights.Right.manage_contest)
+
     contests = (sess.query(db.Contest)
                 .filter_by(round=round)
                 .options(joinedload(db.Contest.place))
@@ -59,13 +67,119 @@ def org_round(id: int):
 
     contests.sort(key=lambda c: locale.strxfrm(c.place.name))
 
+    tasks = sess.query(db.Task).filter_by(round=round).all()
+    tasks.sort(key=lambda t: t.code)
+
+    form_delete_task = TaskDeleteForm()
+    if can_manage_contestants and form_delete_task.validate_on_submit():
+        delete_task = sess.query(db.Task).filter_by(
+            round_id=id, task_id=form_delete_task.delete_task_id.data
+        ).first()
+        if not delete_task:
+            flash('Úloha s daným ID v tomto kole neexistuje', 'danger')
+        elif sess.query(db.Solution).filter_by(task_id=delete_task.task_id).first() is not None:
+            flash(f'Úlohu {delete_task.code} nelze smazat, existují řešení vázající se na ní', 'danger')
+        elif sess.query(db.Paper).filter_by(for_task=delete_task.task_id).first() is not None:
+            flash(f'Úlohu {delete_task.code} nelze smazat, existují papíry vázající se na ní', 'danger')
+        elif sess.query(db.PointsHistory).filter_by(task_id=delete_task.task_id).first() is not None:
+            flash(f'Úlohu {delete_task.code} nelze smazat, existují přidělené body vázající se na ní', 'danger')
+        else:
+            sess.delete(delete_task)
+            mo.util.log(
+                type=db.LogType.task,
+                what=delete_task.task_id,
+                details={'action': 'delete', 'task': db.row2dict(delete_task)},
+            )
+            app.logger.info(f"Úloha {delete_task.code} ({delete_task.task_id}) smazána: {db.row2dict(delete_task)}")
+            sess.commit()
+            flash(f'Úloha {delete_task.code} úspěšně smazána', 'success')
+        return redirect(url_for('org_round', id=id))
+
     return render_template(
         'org_round.html',
         round=round,
         contests=contests,
+        tasks=tasks, form_delete_task=form_delete_task,
         level_names=mo.db.place_level_names,
-        can_manage_round=rr.have_right(mo.rights.Right.manage_round),
-        can_manage_contestants=rr.have_right(mo.rights.Right.manage_contest),
+        can_manage_round=can_manage_round,
+        can_manage_contestants=can_manage_contestants,
+    )
+
+
+class TaskEditForm(FlaskForm):
+    code = wtforms.StringField('Kód úlohy')
+    name = wtforms.StringField('Název úlohy')
+    submit = wtforms.SubmitField('Uložit')
+
+
+@app.route('/org/contest/r/<int:id>/task/new', methods=('GET', 'POST'))
+def org_round_task_new(id: int):
+    sess = db.get_session()
+    round, rr = get_round_rr(id, mo.rights.Right.manage_round)
+
+    form = TaskEditForm()
+    if form.validate_on_submit():
+        task = db.Task()
+        task.round = round
+        form.populate_obj(task)
+
+        if sess.query(db.Task).filter_by(round_id=id, code=task.code).first():
+            flash('Úloha se stejným kódem již v tomto kole existuje', 'danger')
+        else:
+            sess.add(task)
+            sess.flush()
+            mo.util.log(
+                type=db.LogType.task,
+                what=task.task_id,
+                details={'action': 'add', 'task': db.row2dict(task)},
+            )
+            sess.commit()
+            app.logger.info(f"Úloha {task.code} ({task.task_id}) přidána: {db.row2dict(task)}")
+            flash('Nová úloha přidána', 'success')
+            return redirect(url_for('org_round', id=id))
+
+    return render_template(
+        'org_round_task_edit.html',
+        round=round, task=None, form=form,
+    )
+
+
+@app.route('/org/contest/r/<int:id>/task/<int:task_id>/edit', methods=('GET', 'POST'))
+def org_round_task_edit(id: int, task_id: int):
+    sess = db.get_session()
+    round, rr = get_round_rr(id, mo.rights.Right.manage_round)
+
+    task = sess.query(db.Task).get(task_id)
+    if not task:
+        raise werkzeug.exceptions.NotFound()
+
+    form = TaskEditForm(obj=task)
+    if form.validate_on_submit():
+        if sess.query(db.Task).filter(
+            db.Task.task_id != task_id, db.Task.round_id == id, db.Task.code == form.code.data
+        ).first():
+            flash('Úloha se stejným kódem již v tomto kole existuje', 'danger')
+        else:
+            form.populate_obj(task)
+            if sess.is_modified(task):
+                changes = db.get_object_changes(task)
+
+                mo.util.log(
+                    type=db.LogType.task,
+                    what=task_id,
+                    details={'action': 'edit', 'changes': changes},
+                )
+                sess.commit()
+                app.logger.info(f"Úloha {task.code} ({task_id}) modifikována, změny: {changes}")
+                flash('Změny úlohy uloženy', 'success')
+            else:
+                flash(u'Žádné změny k uložení', 'info')
+
+            return redirect(url_for('org_round', id=id))
+
+    return render_template(
+        'org_round_task_edit.html',
+        round=round, task=task, form=form,
     )
 
 
diff --git a/mo/web/templates/org_round.html b/mo/web/templates/org_round.html
index 0509d3fce3d8befa1ab61164188fe0270de3d99f..5ab222a8440c21e0018cb9f51a89307de96a8cab 100644
--- a/mo/web/templates/org_round.html
+++ b/mo/web/templates/org_round.html
@@ -38,4 +38,36 @@
 <p>Zatím nebyly založeny v žádné oblasti.
 {% endif %}
 
+<h3>Úlohy</h3>
+{% if can_manage_round %}
+<a class="btn btn-primary right-float" href="{{ url_for('org_round_task_new', id=round.round_id) }}">Nová úloha</a>
+{% endif %}
+{% if tasks %}
+<table class=data>
+	<thead>
+		<tr>
+			<th>Kód</th>
+			<th>Název</th>
+			{% if can_manage_round %}<th>Akce</th>{% endif %}
+		</tr>
+	</thead>
+	{% for task in tasks %}
+		<tr>
+			<td>{{ task.code }}</td>
+			<td>{{ task.name }}</td>
+			{% if can_manage_round %}<td>
+				<form action="" method="POST" onsubmit="return confirm('Opravdu nenávratně smazat?')" class="btn-group">
+					{{ form_delete_task.csrf_token() }}
+					<input type="hidden" name="delete_task_id" value="{{ task.task_id }}">
+					<a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_edit', id=round.round_id, task_id=task.task_id) }}">Editovat</a>
+					<button type="submit" class="btn btn-xs btn-danger">Smazat</button>
+				</form>
+			</td>{% endif %}
+		</tr>
+	{% endfor %}
+</table>
+{% else %}
+<p>Zatím nebyly přidány žádné úlohy.</p>
+{% endif %}
+
 {% endblock %}
diff --git a/mo/web/templates/org_round_task_edit.html b/mo/web/templates/org_round_task_edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..9eba638e966d33705c719291afe62ab46b678652
--- /dev/null
+++ b/mo/web/templates/org_round_task_edit.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+{% import "bootstrap/wtf.html" as wtf %}
+{% block body %}
+<h2>
+	<a href='{{ url_for('org_round', id=round.round_id) }}'>Kolo {{ round.round_code() }}</a>­­
+	» {% if task %} Úloha {{ task.code }}: {{ task.name }}{% else %}Nová úloha{% endif %}
+</h2>
+
+{{ wtf.quick_form(form, form_type='horizontal') }}
+
+{% endblock %}