diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 8b0d5edd4a3e56b8ac1aed91cf37c9816a0d3cbb..98546219b4da0e50be3c43a289e41cec62b32001 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -353,6 +353,7 @@ def org_contest(id: int, site_id: Optional[int] = None):
can_manage=rr.have_right(Right.manage_contest),
can_upload=rr.can_upload_feedback(round),
can_edit_points=rr.can_edit_points(round),
+ can_create_solutions=rr.can_upload_feedback(round) or rr.can_upload_solutions(round),
can_view_statement=rr.can_view_statement(round),
tasks=tasks, places_counts=places_counts,
)
@@ -556,6 +557,7 @@ class SolutionContext:
allow_view: bool
allow_upload_solutions: bool
allow_upload_feedback: bool
+ allow_create_solutions: bool
allow_edit_points: bool
@@ -608,6 +610,8 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio
if not allow_view:
raise werkzeug.exceptions.Forbidden()
+ allow_upload_solutions = rr.can_upload_solutions(round)
+ allow_upload_feedback = rr.can_upload_feedback(round)
return SolutionContext(
contest=contest,
round=round,
@@ -617,8 +621,9 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio
site=site,
# XXX: Potřebujeme tohle všechno? Nechceme spíš vracet rr a nechat každého, ať na něm volá metody?
allow_view=allow_view,
- allow_upload_solutions=rr.can_upload_solutions(round),
- allow_upload_feedback=rr.can_upload_feedback(round),
+ allow_upload_solutions=allow_upload_solutions,
+ allow_upload_feedback=allow_upload_feedback,
+ allow_create_solutions=allow_upload_solutions or allow_upload_feedback,
allow_edit_points=rr.can_edit_points(round),
)
@@ -633,6 +638,7 @@ class SubmitForm(FlaskForm):
file_note = wtforms.TextAreaField("Poznámka k souboru")
submit_sol = wtforms.SubmitField('Uložit a nahrát soubor jako řešení')
submit_fb = wtforms.SubmitField('Uložit a nahrát soubor jako opravu')
+ delete = wtforms.SubmitField('Smazat řešení')
class SetFinalForm(FlaskForm):
@@ -704,6 +710,24 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
form = SubmitForm(obj=sol)
if form.validate_on_submit():
+ if sol and form.delete.data:
+ if sol.final_submit or sol.final_feedback:
+ flash('Nelze smazat řešení, ke kterému již byl odevzdán soubor', 'danger')
+ else:
+ flash('Řešení smazáno', 'success')
+ sess.delete(sol)
+ mo.util.log(
+ type=db.LogType.participant,
+ what=sc.user.user_id,
+ details={
+ 'action': 'solution-removed',
+ 'task': task_id,
+ },
+ )
+ sess.commit()
+ app.logger.info(f"Řešení úlohy {sc.task.code} od účastníka {sc.user.user_id} smazáno")
+ return redirect(self_url)
+
points = form.points.data
# Checks
if sol and sc.allow_edit_points and points and points < 0:
@@ -713,6 +737,21 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
flash('Schází soubor k nahrání, žádné změny nebyly uloženy', 'danger')
return redirect(self_url)
+ if not sol and (sc.allow_edit_points or sc.allow_upload_solutions or sc.allow_upload_feedback):
+ flash('Řešení založeno', 'success')
+ sol = db.Solution(task=sc.task, user=sc.user)
+ sess.add(sol)
+ mo.util.log(
+ type=db.LogType.participant,
+ what=sc.user.user_id,
+ details={
+ 'action': 'solution-created',
+ 'task': task_id,
+ },
+ )
+ sess.commit()
+ app.logger.info(f"Řešení úlohy {sc.task.code} od účastníka {sc.user.user_id} založeno")
+
# Edit sol and points
if sol and sc.allow_edit_points:
# Sol edit
@@ -768,16 +807,6 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
return redirect(self_url)
sess.add(paper)
-
- # FIXME: Bylo by hezké použít INSERT ... ON CONFLICT UPDATE
- # (SQLAlchemy to umí, ale ne přes ORM, jen core rozhraním)
- sol = (sess.query(db.Solution)
- .filter_by(user_id=user_id, task_id=task_id)
- .with_for_update()
- .one_or_none())
- if sol is None:
- sol = db.Solution(task=sc.task, user=sc.user)
- sess.add(sol)
if type == db.PaperType.solution:
sol.final_submit_obj = paper
else:
@@ -874,20 +903,24 @@ class TaskPointsForm(FlaskForm):
submit = wtforms.SubmitField("Uložit body")
+class TaskCreateForm(FlaskForm):
+ submit = wtforms.SubmitField("Založit označená řešení")
+
+
@app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/')
@app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/task/<int:task_id>/')
@app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/points', methods=('GET', 'POST'), endpoint="org_contest_task_points")
+@app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/create', methods=('GET', 'POST'), endpoint="org_contest_task_create")
+@app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/task/<int:task_id>/create', methods=('GET', 'POST'), endpoint="org_contest_task_create")
def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = None):
sc = get_solution_context(contest_id, None, task_id, site_id)
- edit_points = request.endpoint == "org_contest_task_points"
- if edit_points:
- assert site_id is None
- if not sc.allow_edit_points:
- raise werkzeug.exceptions.Forbidden()
- if sc.round.state != db.RoundState.grading:
- flash("Kolo není ve stavu opravování, nelze zadávat body", "warning")
- return redirect(url_for('org_contest_task', contest_id=contest_id, task_id=task_id))
+ action_create = request.endpoint == "org_contest_task_create"
+ action_points = request.endpoint == "org_contest_task_points"
+ if action_create and not sc.allow_create_solutions:
+ raise werkzeug.exceptions.Forbidden()
+ if action_points and not sc.allow_edit_points:
+ raise werkzeug.exceptions.Forbidden()
sess = db.get_session()
@@ -896,8 +929,41 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non
rows.sort(key=lambda r: r[0].user.sort_key())
points_form: Optional[TaskPointsForm] = None
+ create_form: Optional[TaskCreateForm] = None
+
+ if action_create:
+ create_form = TaskCreateForm()
+ if create_form.validate_on_submit():
+ new_sol_count = 0
+ for pion, sol in rows:
+ if sol:
+ continue # již existuje
+ if not request.form.get(f"create_sol_{pion.user_id}"):
+ continue # nikdo nežádá o vytvoření
+
+ sol = db.Solution(task=sc.task, user=pion.user)
+ sess.add(sol)
+ mo.util.log(
+ type=db.LogType.participant,
+ what=pion.user_id,
+ details={
+ 'action': 'solution-created',
+ 'task': task_id,
+ },
+ )
+ app.logger.info(f"Řešení úlohy {sc.task.code} od účastníka {pion.user_id} založeno")
+ new_sol_count += 1
+
+ if new_sol_count > 0:
+ sess.commit()
+ flash(inflect_by_number(new_sol_count, "Založeno", "Založena", "Založeno") + ' '
+ + inflect_number(new_sol_count, "nové řešení", "nová řešení", "nových řešení"),
+ "success")
+ else:
+ flash("Žádné změny k uložení", "info")
+ return redirect(url_for('org_contest_task', contest_id=contest_id, task_id=task_id, site_id=site_id))
- if edit_points:
+ if action_points:
points_form = TaskPointsForm()
if points_form.validate_on_submit():
count = 0
@@ -906,10 +972,13 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non
if sol is None:
continue
points = request.form.get(f"points_{sol.user_id}", type=int)
- if points and points < 0:
+ if points is None:
+ continue
+ if points < 0:
flash('Nelze zadat záporné body', 'danger')
ok = False
break
+
if points != sol.points:
# Save points
sol.points = points
@@ -924,7 +993,7 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non
if ok:
if count > 0:
sess.commit()
- flash("Změněny body u " + inflect_number(count, "řešitele", "řešitelů", "řešitelů"), "success")
+ flash("Změněny body u " + inflect_number(count, "řešení", "řešení", "řešení"), "success")
else:
flash("Žádné změny k uložení", "info")
return redirect(url_for('org_contest_task', contest_id=contest_id, task_id=task_id))
@@ -943,17 +1012,26 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non
"org_contest_task.html",
sc=sc, rows=rows, paper_counts=paper_counts,
paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, sc.site, u, p),
- can_upload=sc.allow_upload_feedback,
- points_form=points_form, request_form=request.form,
+ points_form=points_form, create_form=create_form, request_form=request.form,
)
+class ContestSolutionsEditForm(FlaskForm):
+ submit = wtforms.SubmitField("Založit označená řešení")
+
+
@app.route('/org/contest/c/<int:id>/solutions', methods=('GET', 'POST'))
@app.route('/org/contest/c/<int:id>/site/<int:site_id>/solutions', methods=('GET', 'POST'))
+@app.route('/org/contest/c/<int:id>/solutions/edit', methods=('GET', 'POST'), endpoint="org_contest_solutions_edit")
+@app.route('/org/contest/c/<int:id>/site/<int:site_id>/solutions/edit', methods=('GET', 'POST'), endpoint="org_contest_solutions_edit")
def org_contest_solutions(id: int, site_id: Optional[int] = None):
sc = get_solution_context(id, None, None, site_id)
sess = db.get_session()
+ edit_action = request.endpoint == "org_contest_solutions_edit"
+ if edit_action and not sc.allow_create_solutions:
+ raise werkzeug.exceptions.Forbidden()
+
pions_subq = sess.query(db.Participation.user_id).filter_by(contest=sc.contest)
if sc.site:
pions_subq = pions_subq.filter_by(place=sc.site)
@@ -996,13 +1074,46 @@ def org_contest_solutions(id: int, site_id: Optional[int] = None):
for s in sols:
task_sols[s.task_id][s.user_id] = s
+ edit_form: Optional[ContestSolutionsEditForm] = None
+ if edit_action:
+ edit_form = ContestSolutionsEditForm()
+ if edit_form.validate_on_submit():
+ new_sol_count = 0
+ for task in tasks:
+ for pion in pions:
+ if pion.user_id in task_sols[task.task_id]:
+ continue # již existuje
+ if not request.form.get(f"create_sol_{task.task_id}_{pion.user_id}"):
+ continue # nikdo nežádá o vytvoření
+
+ sol = db.Solution(task=task, user=pion.user)
+ sess.add(sol)
+ mo.util.log(
+ type=db.LogType.participant,
+ what=pion.user_id,
+ details={
+ 'action': 'solution-created',
+ 'task': task.task_id,
+ },
+ )
+ app.logger.info(f"Řešení úlohy {task.code} od účastníka {pion.user_id} založeno")
+ new_sol_count += 1
+
+ if new_sol_count > 0:
+ sess.commit()
+ flash(inflect_by_number(new_sol_count, "Založeno", "Založena", "Založeno") + ' '
+ + inflect_number(new_sol_count, "nové řešení", "nová řešení", "nových řešení"),
+ "success")
+ else:
+ flash("Žádné změny k uložení", "info")
+ return redirect(url_for('org_contest_solutions', id=id, site_id=site_id))
+
return render_template(
'org_contest_solutions.html',
contest=sc.contest, site=sc.site, sc=sc,
pions=pions, tasks=tasks, tasks_sols=task_sols, paper_counts=paper_counts,
- can_upload=sc.allow_upload_feedback,
- can_edit_points=sc.allow_edit_points,
paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, sc.site, u, p),
+ edit_form=edit_form,
)
diff --git a/mo/web/templates/org_contest.html b/mo/web/templates/org_contest.html
index ada65912f6a48801b89e8b4fd29d18141befc395..510d74c2ed9320cc8f008fdef6d69a0a8bfb27ee 100644
--- a/mo/web/templates/org_contest.html
+++ b/mo/web/templates/org_contest.html
@@ -84,7 +84,8 @@
<th>Kód
<th>Název
<th>Odevzdaná řešení
- <th>Akce
+ <th>Jednotlivé akce
+ <th>Dávkové operace
</tr>
</thead>
{% for task in tasks %}
@@ -94,12 +95,19 @@
<td>{{ task.sol_count }}
<td><div class="btn-group">
<a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Odevzdaná řešení</a>
- <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Stáhnout</a>
+ {% if not site and can_edit_points %}
+ <a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Zadat body</a>
+ {% endif %}
+ {% if can_create_solutions %}
+ <a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_create', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Založit řešení</a>
+ {% endif %}
+ </div>
+ <td><div class="btn-group">
+ <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Stáhnout ZIP</a>
{% if can_upload %}
- <a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Nahrát</a>
+ <a class='btn btn-xs btn-default' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Nahrát ZIP</a>
{% endif %}
{% if not site and can_edit_points %}
- <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Zadat body</a>
<a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_batch_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Nahrát body</a>
{% endif %}
</div>
diff --git a/mo/web/templates/org_contest_solutions.html b/mo/web/templates/org_contest_solutions.html
index 670a5472bd17e63d0190d69496123423fbff6614..dcb73a6f713c7b2386521b024525a0d7ee0ba0d6 100644
--- a/mo/web/templates/org_contest_solutions.html
+++ b/mo/web/templates/org_contest_solutions.html
@@ -4,10 +4,10 @@
{% set site_id = site.place_id if site else None %}
{% block title %}
-Tabulka řešení {% if site %}soutěžního místa {{ site.name }}{% else %}oblasti {{ contest.place.name }}{% endif %}
+{{ "Založení řešení" if edit_form else "Tabulka řešení" }} {% if site %}soutěžního místa {{ site.name }}{% else %}oblasti {{ contest.place.name }}{% endif %}
{% endblock %}
{% block breadcrumbs %}
-{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Tabulka řešení") }}
+{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Založení řešení" if edit_form else "Tabulka řešení") }}
{% endblock %}
{% block body %}
@@ -20,10 +20,22 @@ Tabulka řešení {% if site %}soutěžního místa {{ site.name }}{% else %}obl
{% include "parts/org_submit_warning.html" %}
-<p>Všechna odevzdání od účastníka k úloze můžete vidět po kliknutí na ikonku <span class="icon">🔍</span>.
+<p><i>
+{% if edit_form %}
+Zaškrtnutím políček u řešení, která dosud neexistují, a odesláním tlačítkem pod tabulkou tato řešení založíte.
+To se hodí, pokud se nechystáte do systému nahrávat soubory řešení, ale jen chcete řešení vytvořit, aby jim
+bylo možné vyplnit body. Pokud nějaké řešení založíte omylem, lze toto prázdné řešení smazat v jeho detailu.
+{% else %}
+Všechna odevzdání od účastníka k úloze můžete vidět po kliknutí na ikonku <span class="icon">🔍</span>.
Odkazem v záhlaví se lze dostat na detailní výpis odevzdání všech uživatelů pro
-konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje více verzí dostupných v detailu.</p>
+konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje více verzí dostupných v detailu.
+{% endif %}
+</i></p>
+{% if edit_form %}
+<form class="form" method="POST">
+{{ edit_form.csrf_token }}
+{% endif %}
<table class="data full center">
<colgroup><col span="2"></colgroup>
{% for task in tasks %}
@@ -36,7 +48,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
<th rowspan=2>Stav účasti</th>
{% for task in tasks %}<th colspan=4>
<a href="{{ url_for('org_contest_task', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">{{ task.code }}</a>
- {% if can_edit_points %}
+ {% if sc.allow_edit_points %}
<a title="Editovat body" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}" class="icon pull-right">✎</a>
{% endif %}
{% endfor %}
@@ -87,7 +99,13 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
{% endif %}
<td class="sol">
{% else %}
- <td colspan=3>–
+ <td colspan=3>
+ {% if edit_form %}
+ <label>
+ <input type="checkbox" name="create_sol_{{task.task_id}}_{{u.user_id}}">
+ Založit
+ </label>
+ {% else %}–{% endif %}
<td>
{% endif %}
<a class="btn btn-xs btn-link icon" title="Detail řešení" href="{{ url_for('org_submit_list', contest_id=contest.contest_id, user_id=u.user_id, task_id=task.task_id, site_id=site_id) }}">🔍</a>
@@ -100,7 +118,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
{% for task in tasks %}
<td colspan=4><div class='btn-group'>
<a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">Stáhnout</a>
- {% if can_upload %}
+ {% if sc.allow_upload_feedback %}
<a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">Nahrát</a>
{% endif %}
</div>
@@ -108,5 +126,18 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
<td>
</tfoot>
</table>
+{% if edit_form %}
+ <div class='btn-group'>
+ {{ wtf.form_field(edit_form.submit, class="btn btn-primary") }}
+ <a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=contest.contest_id, site_id=site_id) }}">Zrušit</a>
+ </div>
+</form>
+{% else %}
+<div class='btn-group'>
+ {% if sc.allow_create_solutions %}
+ <a class="btn btn-primary" href="{{ url_for('org_contest_solutions_edit', id=contest.contest_id, site_id=site_id) }}">Založit řešení</a>
+ {% endif %}
+</div>
+{% endif %}
{% endblock %}
diff --git a/mo/web/templates/org_contest_task.html b/mo/web/templates/org_contest_task.html
index 224fd89eb4395db03ab1635dbcb9e16c11ec0ced..0e4719576a7c2b0a3c455ed23543592576a42099 100644
--- a/mo/web/templates/org_contest_task.html
+++ b/mo/web/templates/org_contest_task.html
@@ -7,9 +7,9 @@
{% set site_id = site.place_id if site else None %}
{% set task = sc.task %}
-{% block title %}{% if points_form %}Editace bodů{% else %}Odevzdaná řešení{% endif %} úlohy {{ task.code }} {{ task.name }}{% endblock %}
+{% block title %}{{ "Zadávání bodů" if points_form else "Založení řešení" if create_form else "Odevzdaná řešení" }} úlohy {{ task.code }} {{ task.name }}{% endblock %}
{% block breadcrumbs %}
-{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Editace bodů" if points_form else None) }}
+{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Zadávání bodů" if points_form else "Založení řešení" if create_form else None) }}
{% endblock %}
{% block body %}
@@ -23,25 +23,29 @@
{% include "parts/org_submit_warning.html" %}
-{% if points_form %}
+{% set form = points_form or create_form %}
+{% if form %}
<form class="form" method="POST">
-{{ points_form.csrf_token }}
+{{ form.csrf_token }}
{% endif %}
{% with for_user=None, for_task=task, rows=rows %}
{% include "parts/org_solution_table.html" %}
{% endwith %}
-{% if points_form %}
+{% if form %}
<div class='btn-group'>
- {{ wtf.form_field(points_form.submit, class="btn btn-primary") }}
- <a class="btn btn-default" href="{{ url_for('org_contest_task', contest_id=ct_id, task_id=task.task_id) }}">Zrušit editaci bodů</a>
+ {{ wtf.form_field(form.submit, class="btn btn-primary" ) }}
+ <a class="btn btn-default" href="{{ url_for('org_contest_task', contest_id=ct_id, task_id=task.task_id, site_id=site_id) }}">Zrušit</a>
</div>
</form>
{% else %}
<div class='btn-group'>
<a class='btn btn-primary' href="{{ url_for('org_contest_task_download', contest_id=ct_id, site_id=site_id, task_id=task.task_id) }}">Stáhnout řešení</a>
- {% if can_upload %}
+ {% if sc.allow_upload_feedback %}
<a class='btn btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=ct_id, site_id=site_id, task_id=task.task_id) }}">Nahrát opravená řešení</a>
{% endif %}
+ {% if sc.allow_create_solutions %}
+ <a class="btn btn-primary" href="{{ url_for('org_contest_task_create', contest_id=ct_id, task_id=task.task_id, site_id=site_id) }}">Založit řešení</a>
+ {% endif %}
{% if not site and sc.allow_edit_points %}
<a class="btn btn-primary" href="{{ url_for('org_contest_task_points', contest_id=ct_id, task_id=task.task_id) }}">Zadat body</a>
{% endif %}
diff --git a/mo/web/templates/org_round.html b/mo/web/templates/org_round.html
index 3296d22beeeb1486e96d26f526c4c4666c6e619b..9f6d685c9e13769ad89564da968fed8236a2dced 100644
--- a/mo/web/templates/org_round.html
+++ b/mo/web/templates/org_round.html
@@ -89,7 +89,8 @@
<th>Kód
<th>Název
<th>Odevzdaná řešení
- {% if can_manage_round or can_handle_submits %}<th>Akce{% endif %}
+ {% if can_manage_round %}<th>Akce{% endif %}
+ {% if can_handle_submits or can_upload %}<th>Dávkové operace{% endif %}
</tr>
</thead>
{% for task in tasks %}
@@ -97,28 +98,32 @@
<td>{{ task.code }}
<td>{{ task.name }}
<td>{{ task.sol_count }}
+ {% if can_manage_round %}
<td><div class="btn-group">
- {% if can_manage_round %}
<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>
<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 }}">
<button type="submit" class="btn btn-xs btn-danger">Smazat</button>
</form>
+ {% if g.user.is_admin %}
+ <a class="btn btn-xs btn-default" href="{{ log_url('task', task.task_id) }}">Historie</a>
{% endif %}
+ </div>
+ {% endif %}
+ {% if can_handle_submits or can_upload %}
+ <td><dic class="btn-group">
{% if can_handle_submits %}
- <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_download', round_id=round.round_id, task_id=task.task_id) }}">Stáhnout</a>
+ <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_download', round_id=round.round_id, task_id=task.task_id) }}">Stáhnout ZIP</a>
{% endif %}
{% if can_upload %}
- <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_upload', round_id=round.round_id, task_id=task.task_id) }}">Nahrát</a>
+ <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_upload', round_id=round.round_id, task_id=task.task_id) }}">Nahrát ZIP</a>
{% endif %}
{% if can_upload %}
<a class="btn btn-xs btn-default" href="{{ url_for('org_round_task_batch_points', round_id=round.round_id, task_id=task.task_id) }}">Nahrát body</a>
{% endif %}
- {% if g.user.is_admin %}
- <a class="btn btn-xs btn-default" href="{{ log_url('task', task.task_id) }}">Historie</a>
- {% endif %}
</div>
+ {% endif %}
</tr>
{% endfor %}
</table>
diff --git a/mo/web/templates/org_submit_list.html b/mo/web/templates/org_submit_list.html
index 37ad2fd414a30dde82d47066507f15307b7f525e..ffb9443ae94574a9eb049d4b57b5b3a75a6e9009 100644
--- a/mo/web/templates/org_submit_list.html
+++ b/mo/web/templates/org_submit_list.html
@@ -161,44 +161,64 @@ Existuje více než jedna verze oprav, finální je podbarvená.
</div>
{% else %}
-<p>Žádné odevzdané řešení. {% if form %}Můžete ho přidat pomocí formuláře níže.{% endif %}
+<p>Žádné odevzdané řešení. {% if form %}Můžete ho založit pomocí formuláře níže.{% endif %}
{% endif %}
{% if form %}
-
-<div class="form-frame">
<form method="post" class="form-horizontal" enctype="multipart/form-data">
+<div class="form-frame">
{{ form.csrf_token }}
+ {% set action = 'Uložit' if solution else 'Založit řešení' %}
{% if sc.allow_edit_points %}
- <h3 style="margin-top: 10px;">Hodnocení řešení</h3>
+ {% if solution %}
+ <h3 style="margin-top: 10px;">Hodnocení řešení</h3>
+ {% else %}
+ <h3>Založit řešení</h3>
+ <p><i>Můžete rovnou vyplnit i poznámky a přidělené body
+ {%- if sc.allow_upload_feedback or sc.allow_upload_solutions %}, případně rovnou nahrát i soubor řešení nebo opravy{% endif %}.
+ </i></p>
+ {% endif %}
{{ wtf.form_field(form.note, form_type='horizontal', horizontal_columns=('sm', 2, 10), rows=4)}}
{{ wtf.form_field(form.org_note, form_type='horizontal', horizontal_columns=('sm', 2, 10), rows=4 )}}
{{ wtf.form_field(form.points, form_type='horizontal', horizontal_columns=('sm', 2, 10) )}}
{{ wtf.form_field(
form.submit, form_type='horizontal', class='btn btn-primary', horizontal_columns=('sm', 2, 10),
- value='Uložit bez nahrání souboru' if sc.allow_upload_feedback or sc.allow_upload_solutions else 'Uložit'
+ value=action + (' bez nahrání souboru' if sc.allow_upload_feedback or sc.allow_upload_solutions else '')
)}}
{% endif %}
{% if sc.allow_upload_feedback or sc.allow_upload_solutions %}
- <h3>Nahrání souboru</h3>
- {% if sc.allow_edit_points %}
- <p><i>Lze najednou editovat řešení (například zadat body) i nahrát soubor, použijte tlačítka na spodku formuláře.</i></p>
+ {% if solution %}
+ <h3>Nahrání souboru</h3>
+ {% if sc.allow_edit_points %}
+ <p><i>Lze najednou editovat řešení (například zadat body) i nahrát soubor, použijte tlačítka na spodku formuláře.</i></p>
+ {% endif %}
+ {% else %}
+ <h3>Založit řešení a nahrát soubor</h3>
{% endif %}
{{ wtf.form_field(form.file, form_type='horizontal', horizontal_columns=('sm', 2, 10)) }}
{{ wtf.form_field(form.file_note, form_type='horizontal', horizontal_columns=('sm', 2, 10)) }}
<div class="form-group">
<div class="btn btn-group col-sm-offset-2">
{% if sc.allow_upload_solutions %}
- {{ wtf.form_field(form.submit_sol, class='btn btn-primary' )}}
+ {{ wtf.form_field(form.submit_sol, class='btn btn-primary', value=action + ' a nahrát soubor jako řešení' )}}
{% endif %}
{% if sc.allow_upload_feedback %}
- {{ wtf.form_field(form.submit_fb, class='btn btn-success' )}}
+ {{ wtf.form_field(form.submit_fb, class='btn btn-success', value=action + ' a nahrát soubor jako opravu' )}}
{% endif %}
</div>
</div>
{% endif %}
-</form>
</div>
+
+{% if solution and not solution.final_submit and not solution.final_feedback and sc.allow_create_solutions %}
+<div class="form-frame">
+ <h3 style="margin-top: 10px;">Smazání řešení</h3>
+ <p>Toto řešení zatím neobsahuje žádný soubor. Pokud bylo přidáno omylem, můžete ho smazat.</p>
+ {{ wtf.form_field(form.delete, class='btn btn-danger') }}
+</div>
+{% endif %}
+</form>
+
{% endif %}
{% endblock %}
diff --git a/mo/web/templates/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html
index 9eb4f7de3697d62e18835065a153beb71d86d69a..150d77ecb0f50d4af978c31b7e65e4557fa9c949 100644
--- a/mo/web/templates/parts/org_solution_table.html
+++ b/mo/web/templates/parts/org_solution_table.html
@@ -1,11 +1,18 @@
-<p><i>{% if for_user %}U každé úlohy je zobrazeno účastníkovo {% else %}U každého účastníka je zobrazeno jeho {% endif %}
-finální řešení, finální oprava a přidělené body. Historii všech odevzdání, oprav a bodů naleznete v detailu řešení.
+<p><i>
+{% if create_form %}
+Zaškrtnutím políček u řešení, která dosud neexistují, a odesláním tlačítkem pod tabulkou tato řešení založíte.
+To se hodí, pokud se nechystáte do systému nahrávat soubory řešení, ale jen chcete řešení vytvořit, aby jim
+bylo možné vyplnit body. Pokud nějaké řešení založíte omylem, lze toto prázdné řešení smazat v jeho detailu.
+{% else %}
+Historii všech odevzdání, oprav a bodů pro každé řešení naleznete v jeho detailu.
{% if sc.allow_upload_feedback or sc.allow_edit_points %}Tamtéž můžete odevzdávat nové verze a změnit, které řešení/oprava je
-finální (ve výchozím stavu poslední nahrané).{% endif %}
+finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solutions %}Tamtéž můžete odevzdat nové řešení.{% endif %}
+{% if for_task and sc.allow_create_solutions %} Hromadně založit řešení pro více řešitelů můžete pomocí tlačítek pod tabulkou.{% endif %}
+{% endif %}
</i></p>
-<p><i>Legenda k symbolům: <span class='sol-warn icon'>⚠</span> odevzdané po termínu,
-<span class="icon">🛈</span> nahráno někým jiným, než řešitelem, <span class="icon">🗐</span> existuje více verzí. Symboly po najetí myší zobrazí bližší informace.
+<p><i>Legenda: <span class='sol-warn icon'>⚠</span> odevzdané po termínu,
+ <span class="icon">🛈</span> nahráno někým jiným, než řešitelem, <span class="icon">🗐</span> existuje více verzí. Symboly po najetí myší zobrazí bližší informace.
</i></p>
<table class="data full">
@@ -15,11 +22,11 @@ finální (ve výchozím stavu poslední nahrané).{% endif %}
{% if for_task %}<th>Stav účasti{% endif %}
<th>Finální řešení
<th>Finální oprava
+ <th>Poznámky
<th>Přidělené body
{% if not for_user and not site and sc.allow_edit_points and not points_form %}
<a title="Editovat body" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}" class="icon pull-right">✎</a>
{% endif %}
- <th>Poznámky
<th>Akce
</tr>
</thead>
@@ -73,19 +80,25 @@ finální (ve výchozím stavu poslední nahrané).{% endif %}
<span title="Celkem {{ paper_counts[key]|inflected('verze', 'verze', 'verzí') }}" class="icon">🗐</span>
{% endif %}
{% else %}–{% endif %}
- <td>{% if points_form %}
- <input type="number" class="form-control" name="points_{{sol.user_id}}" value="{{ request_form.get("points_{}".format(sol.user_id)) or sol.points }}" size="4">
- {% else %}
- {% if sol.points is not none %}{{ sol.points}}{% else %}<span class="unknown">?</span>{% endif %}
- {% endif %}
- {% else %}
- <td>–<td>–<td>–
- {% endif %}
<td style="text-align: center;">
- {% if sol and sol.note %}<span class="icon" title="Poznámka pro řešitele: {{ sol.note }}">🗩</span>{% endif %}
- {% if sol and sol.org_note %} <span class="icon" title="Interní poznámka: {{ sol.org_note }}">🗩</span>{% endif %}
+ {% if sol.note %}<span class="icon" title="Poznámka pro řešitele: {{ sol.note }}">🗩</span>{% endif %}
+ {% if sol.org_note %} <span class="icon" title="Interní poznámka: {{ sol.org_note }}">🗩</span>{% endif %}
<td>
+ {% if points_form %}
+ <input type="number" class="form-control" name="points_{{u.user_id}}" value="{{ request_form.get("points_{}".format(u.user_id)) or sol.points }}" size="4">
+ {% else %}
+ {% if sol.points is not none %}{{ sol.points}}{% else %}<span class="unknown">?</span>{% endif %}
+ {% endif %}
+ {% else %}
+ <td colspan="4" class="text-center">
+ {% if create_form %}
+ <input type="checkbox" name="create_sol_{{u.user_id}}" id="create_sol_{{u.user_id}}"{% if request_form.get("create_sol_{}".format(u.user_id)) %}checked{% endif %}>
+ <label for="create_sol_{{u.user_id}}">Založit řešení</label>
+ {% else %}–{% endif %}
+ {% endif %}
+ <td><div class="btn-group">
<a class="btn btn-xs btn-primary" href="{{ url_for('org_submit_list', contest_id=ct_id, user_id=u.user_id, task_id=task.task_id, site_id=site_id) }}">Detail</a>
+ </div>
</tr>
{% endfor %}
</table>
diff --git a/static/mo.css b/static/mo.css
index 47c87eee9867cfcb525f85a7ed16a77acc1c4b83..8978f2cd51d677e19ab53875e9175b7793006476 100644
--- a/static/mo.css
+++ b/static/mo.css
@@ -172,6 +172,13 @@ nav#main-menu a.active {
border-radius: 4px 4px;
}
+.checked_toggle input.toggle:checked ~ .checked_hide {
+ display: none;
+}
+.checked_toggle input.toggle:not(:checked) ~ .checked_show {
+ display: none;
+}
+
/* Round states */
.rstate-preparing {