Skip to content
Snippets Groups Projects
Commit 96fefcf8 authored by Jiří Setnička's avatar Jiří Setnička
Browse files

Nastavení ručního zjednoznačnění pořadí u výsledkovek

Požaduje právo manage_round/manage_contest.

Issue #210
parent c37af8e8
Branches
No related tags found
No related merge requests found
This commit is part of merge request !105. Comments created here will be created in the context of that merge request.
from flask import render_template, request, g from flask import render_template, request
from flask.helpers import url_for from flask.helpers import flash, url_for
from typing import List, Optional, Union from typing import List, Optional, Union
from sqlalchemy.orm import joinedload from flask_wtf.form import FlaskForm
import werkzeug.exceptions import werkzeug.exceptions
from werkzeug.utils import redirect
import wtforms
import mo import mo
import mo.db as db import mo.db as db
...@@ -10,8 +12,8 @@ from mo.rights import Right ...@@ -10,8 +12,8 @@ from mo.rights import Right
from mo.score import Score from mo.score import Score
from mo.web import app from mo.web import app
from mo.web.org_contest import get_context from mo.web.org_contest import get_context
from mo.web.table import Cell, CellLink, Column, Row, Table, cell_pion_link from mo.web.table import Cell, CellInput, CellLink, Column, Row, Table, cell_pion_link
from mo.util_format import format_decimal from mo.util_format import format_decimal, inflect_number
class OrderCell(Cell): class OrderCell(Cell):
...@@ -78,8 +80,14 @@ class SolPointsCell(Cell): ...@@ -78,8 +80,14 @@ class SolPointsCell(Cell):
return f'<td>{points}' # no paper no link 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')
@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')
@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): def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None):
ctx = get_context(round_id=round_id, ct_id=ct_id) ctx = get_context(round_id=round_id, ct_id=ct_id)
contest = ctx.contest contest = ctx.contest
...@@ -92,11 +100,52 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ...@@ -92,11 +100,52 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None):
raise werkzeug.exceptions.Forbidden() raise werkzeug.exceptions.Forbidden()
can_view_submits = ctx.rights.have_right(Right.view_submits) 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) score = Score(round.master, contest)
tasks = score.get_tasks() tasks = score.get_tasks()
results = score.get_sorted_results() results = score.get_sorted_results()
messages = score.get_messages() 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 # 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( all_subcontests: List[db.Contest] = sess.query(db.Contest).filter(
db.Contest.round_id.in_( db.Contest.round_id.in_(
...@@ -138,6 +187,8 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ...@@ -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=f'task_{task.task_id}', name=task.code, title=title))
columns.append(Column(key='total_points', name='celkove_body', title='Celkové body')) 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íč')) # columns.append(Column(key='order_key', name='order_key', title='Třídící klíč'))
# Construct rows # Construct rows
...@@ -173,6 +224,7 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ...@@ -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()), 'total_points': format_decimal(result.get_total_points()),
'birth_year': pant.birth_year, 'birth_year': pant.birth_year,
'order_key': result._order_key, 'order_key': result._order_key,
'suborder': CellInput(f"suborder_{user.user_id}", pion.score_suborder, "number", attrs={"size": 6}),
}) })
sols = result.get_sols_map() sols = result.get_sols_map()
...@@ -196,6 +248,7 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ...@@ -196,6 +248,7 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None):
columns=columns, columns=columns,
rows=table_rows, rows=table_rows,
filename=filename, filename=filename,
show_downlink=not is_edit,
) )
group_rounds = round.get_group_rounds(True) group_rounds = round.get_group_rounds(True)
...@@ -208,6 +261,8 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None): ...@@ -208,6 +261,8 @@ def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None):
contest=contest, round=round, tasks=tasks, contest=contest, round=round, tasks=tasks,
table=table, messages=messages, table=table, messages=messages,
group_rounds=group_rounds, group_rounds=group_rounds,
round_id=round_id, ct_id=ct_id, can_manage=can_manage,
edit_form=edit_form,
) )
else: else:
return table.send_as(format) return table.send_as(format)
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %} {% block title %}
{{ round.round_code() }}: Výsledky pro {{ round.name|lower }} kategorie {{ round.category }}{% if contest %} {{ contest.place.name_locative() }}{% endif %} {{ round.round_code() }}: Výsledky pro {{ round.name|lower }} kategorie {{ round.category }}{% if contest %} {{ contest.place.name_locative() }}{% endif %}
...@@ -42,7 +43,7 @@ ...@@ -42,7 +43,7 @@
{% if group_rounds|length > 1 %} {% if group_rounds|length > 1 %}
<p>Toto je <b>sdílená výsledková listina</b> pro několik kol: <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> Jsou v ní započítány body ze všech úloh těchto kol.</p>
{% endif %} {% endif %}
...@@ -62,6 +63,22 @@ Rozkliknutím bodů se lze dostat na detail daného řešení.</p> ...@@ -62,6 +63,22 @@ Rozkliknutím bodů se lze dostat na detail daného řešení.</p>
{% endif %} {% endif %}
{% 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() }} {{ 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 %} {% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment