From 9cf17b66d253914b3df4429d6ba86eefbac4304f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ji=C5=99=C3=AD=20Setni=C4=8Dka?= <setnicka@seznam.cz>
Date: Fri, 19 Feb 2021 19:59:42 +0100
Subject: [PATCH] =?UTF-8?q?Vytv=C3=A1=C5=99en=C3=AD=20=C5=99e=C5=A1en?=
 =?UTF-8?q?=C3=AD=20ze=20str=C3=A1nky=20=C3=BAlohy?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Vytvořen nový endpoint org_contest_task_create. Vše v pozadí obsluhuje
stejná univerzální tabulka, jen na správných místech vypisuje správné
formuláře ve správném módu (má teď mód zobrazení, zakládání řešení a
bodování).

Přidány odkazy z různých míst.
---
 mo/web/org_contest.py                         | 65 +++++++++++++++----
 mo/web/templates/org_contest.html             |  3 +
 mo/web/templates/org_contest_task.html        | 18 +++--
 .../templates/parts/org_solution_table.html   | 45 ++++++++-----
 static/mo.css                                 |  7 ++
 5 files changed, 102 insertions(+), 36 deletions(-)

diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 956626ce..4e27f3e3 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -903,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()
 
@@ -925,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 edit_points:
+            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 action_points:
         points_form = TaskPointsForm()
         if points_form.validate_on_submit():
             count = 0
@@ -935,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
@@ -953,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))
@@ -972,8 +1012,7 @@ 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,
     )
 
 
diff --git a/mo/web/templates/org_contest.html b/mo/web/templates/org_contest.html
index ada65912..c6ecb514 100644
--- a/mo/web/templates/org_contest.html
+++ b/mo/web/templates/org_contest.html
@@ -98,6 +98,9 @@
 			{% 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>
 			{% endif %}
+			{% if can_create_solutions %}
+				<a class="btn btn-xs btn-primary" 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 %}
 			{% 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>
diff --git a/mo/web/templates/org_contest_task.html b/mo/web/templates/org_contest_task.html
index ec03f0d4..0e471957 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,17 +23,18 @@
 
 {% 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 %}
@@ -42,6 +43,9 @@
 	{% 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/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html
index 9eb4f7de..150d77ec 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 47c87eee..8978f2cd 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 {
-- 
GitLab