diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index 956626ce63f5b703f3c86e580da500478f9d5516..4e27f3e3a5c62d5d3f4f02092a80e8010aad68c6 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 ada65912f6a48801b89e8b4fd29d18141befc395..c6ecb514e934d159865a07ae688e75c14cb71b31 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 ec03f0d41d6f0dad54bc088430567647fcaf5ec1..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,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 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 {