diff --git a/mo/web/org_score.py b/mo/web/org_score.py index a8e4ca012dacfd6bce5664c010e08b4aa438c67d..93f1d8762926d05e5a813619c0135a4a1040d5e6 100644 --- a/mo/web/org_score.py +++ b/mo/web/org_score.py @@ -1,8 +1,10 @@ -from flask import render_template, request, g -from flask.helpers import url_for +from flask import render_template, request +from flask.helpers import flash, url_for from typing import List, Optional, Union -from sqlalchemy.orm import joinedload +from flask_wtf.form import FlaskForm import werkzeug.exceptions +from werkzeug.utils import redirect +import wtforms import mo import mo.db as db @@ -10,8 +12,8 @@ from mo.rights import Right from mo.score import Score from mo.web import app from mo.web.org_contest import get_context -from mo.web.table import Cell, CellLink, Column, Row, Table, cell_pion_link -from mo.util_format import format_decimal +from mo.web.table import Cell, CellInput, CellLink, Column, Row, Table, cell_pion_link +from mo.util_format import format_decimal, inflect_number class OrderCell(Cell): @@ -78,8 +80,14 @@ class SolPointsCell(Cell): return f'<td>{points}' # no paper no link +class ScoreEditForm(FlaskForm): + submit = wtforms.SubmitField("Uložit zjednoznačnění") + + @app.route('/org/contest/r/<int:round_id>/score') +@app.route('/org/contest/r/<int:round_id>/score/edit', methods=('GET', 'POST'), endpoint="org_score_edit") @app.route('/org/contest/c/<int:ct_id>/score') +@app.route('/org/contest/c/<int:ct_id>/score/edit', methods=('GET', 'POST'), endpoint="org_score_edit") def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ctx = get_context(round_id=round_id, ct_id=ct_id) contest = ctx.contest @@ -92,11 +100,52 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): raise werkzeug.exceptions.Forbidden() can_view_submits = ctx.rights.have_right(Right.view_submits) + is_edit = request.endpoint == 'org_score_edit' + can_manage = (round_id is not None and ctx.rights.have_right(Right.manage_round)) or (ct_id is not None and ctx.rights.have_right(Right.manage_contest)) + + if is_edit and not can_manage: + raise werkzeug.exceptions.Forbidden() + score = Score(round.master, contest) tasks = score.get_tasks() results = score.get_sorted_results() messages = score.get_messages() + edit_form: Optional[ScoreEditForm] = None + + if is_edit: + edit_form = ScoreEditForm() + if edit_form.validate_on_submit(): + count = 0 + for result in results: + try: + score_suborder = int(request.form.get(f"suborder_{result.user.user_id}")) + except ValueError: + score_suborder = None + + if score_suborder != result.pion.score_suborder: + app.logger.info(f"Změněno zjednoznačnění u soutěžícího #{result.user.user_id} v soutěži #{result.pion.contest_id}: {result.pion.score_suborder}->{score_suborder}") + mo.util.log( + type=db.LogType.participant, + what=result.user.user_id, + details={ + 'action': 'pion-changed-suborder', + 'contest_id': result.pion.contest_id, + 'old_score_suborder': result.pion.score_suborder, + 'new_score_suborder': score_suborder + }, + ) + result.pion.score_suborder = score_suborder + count += 1 + + if count > 0: + sess.commit() + flash('Změněno zjednoznačnění u ' + inflect_number(count, 'soutěžícího', 'soutěžících', 'soutěžících'), 'success') + else: + flash('Žádné změny k uložení', 'info') + return redirect(ctx.url_for('org_score')) + + # Pro tvorbu odkazů na správné contesty ve výsledkovkách dělených kol all_subcontests: List[db.Contest] = sess.query(db.Contest).filter( db.Contest.round_id.in_( @@ -138,6 +187,8 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ) columns.append(Column(key=f'task_{task.task_id}', name=task.code, title=title)) columns.append(Column(key='total_points', name='celkove_body', title='Celkové body')) + if is_edit: + columns.append(Column(key='suborder', name='zjednoznacneni_poradi', title='Zjednoznačnění')) # columns.append(Column(key='order_key', name='order_key', title='Třídící klíč')) # Construct rows @@ -173,6 +224,7 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): 'total_points': format_decimal(result.get_total_points()), 'birth_year': pant.birth_year, 'order_key': result._order_key, + 'suborder': CellInput(f"suborder_{user.user_id}", pion.score_suborder, "number", attrs={"size": 6}), }) sols = result.get_sols_map() @@ -196,6 +248,7 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): columns=columns, rows=table_rows, filename=filename, + show_downlink=not is_edit, ) group_rounds = round.get_group_rounds(True) @@ -208,6 +261,8 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): contest=contest, round=round, tasks=tasks, table=table, messages=messages, group_rounds=group_rounds, + round_id=round_id, ct_id=ct_id, can_manage=can_manage, + edit_form=edit_form, ) else: return table.send_as(format) diff --git a/mo/web/templates/org_score.html b/mo/web/templates/org_score.html index 0513cf4d27055f070c286c67d7a94396ca72a7ff..564949f2521938c2c5dcefb8f2e6d5624c09aa1b 100644 --- a/mo/web/templates/org_score.html +++ b/mo/web/templates/org_score.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% import "bootstrap/wtf.html" as wtf %} {% block title %} {{ round.round_code() }}: Výsledky pro {{ round.name|lower }} kategorie {{ round.category }}{% if contest %} {{ contest.place.name_locative() }}{% endif %} @@ -42,7 +43,7 @@ {% if group_rounds|length > 1 %} <p>Toto je <b>sdílená výsledková listina</b> pro několik kol: -{% for r in group_rounds %}{% if loop.index > 1 %}, {% endif %}<a href="{{ url_for('org_round', round_id=r.round_id) }}">{{ r.round_code() }} {{ r.name }}</a>{% endfor %}. +{% for r in group_rounds %}{% if loop.index > 1 %}, {% endif %}<a href="{{ ctx.url_for('org_round') }}">{{ r.round_code() }} {{ r.name }}</a>{% endfor %}. Jsou v ní započítány body ze všech úloh těchto kol.</p> {% endif %} @@ -62,6 +63,22 @@ Rozkliknutím bodů se lze dostat na detail daného řešení.</p> {% endif %} {% endif %} +{% if edit_form %} +<p><strong>Zjednoznačnění pořadí:</strong> U soutěžících na sdílených pozicích vyplňte číslo do políčka na konci řádku. Třídí se vzestupně od nejmenšího, prázdné políčko se považuje za nulu.</p> +<form method="POST" class="form form-horizontal" action=""> + {{ edit_form.csrf_token }} + {{ wtf.form_field(edit_form.submit, class="btn btn-primary pull-right") }}<br> +{% elif can_manage %} + <a class="btn btn-default pull-right" href="{{ ctx.url_for('org_score_edit') }}">Zjednoznačnit pořadí</a><br> +{% endif %} + {{ table.to_html() }} +{% if edit_form %} + {{ wtf.form_field(edit_form.submit, class="btn btn-primary pull-right") }} +</form> +{% elif can_manage %} + <a class="btn btn-default pull-right" href="{{ ctx.url_for('org_score_edit') }}">Zjednoznačnit pořadí</a><br> +{% endif %} + {% endblock %}