Select Git revision
org_score.py
-
Jan Prachař authoredJan Prachař authored
org_score.py 11.29 KiB
from decimal import Decimal
from flask import render_template, request
from flask.helpers import flash, url_for
from typing import List, Optional, Union
from flask_wtf.form import FlaskForm
import werkzeug.exceptions
from werkzeug.utils import redirect
import wtforms
import mo
import mo.db as db
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, CellInput, CellLink, Column, Row, Table, cell_pion_link
from mo.util_format import format_decimal, inflect_number
class OrderCell(Cell):
place: int
span: int
continuation: bool
def __init__(self, place: int, span: int = 1, continuation: bool = False):
self.place = place
self.span = span
self.continuation = continuation
def __str__(self) -> str:
if self.span == 1:
return f"{self.place}."
else:
return f"{self.place}.–{self.place + self.span - 1}."
def to_html(self) -> str:
if self.continuation:
return "" # covered by rowspan cell above this one
elif self.span == 1:
return f"<td>{self.__str__()}"
else:
return f"<td rowspan='{self.span}'>{self.__str__()}"
class SolTotalPointsCell(Cell):
points: Decimal
def __init__(self, points: Decimal):
self.points = points
def __str__(self) -> str:
return format_decimal(self.points)
def to_html(self) -> str:
return '<td style="text-align: right"><b>' + str(self) + '</b>'
class SolPointsCell(Cell):
contest_id: int
user: db.User
sol: Optional[db.Solution]
link_to_paper: bool
def __init__(self, contest_id: int, user: db.User, sol: Optional[db.Solution], link_to_paper: bool):
self.contest_id = contest_id
self.user = user
self.sol = sol
self.link_to_paper = link_to_paper
def __str__(self) -> str:
if not self.sol:
return '–'
elif self.sol.points is None:
return '?'
return format_decimal(self.sol.points)
def to_html(self) -> str:
td = "<td style='text-align: right'>"
if not self.sol:
return td+'–'
elif self.sol.points is None:
points = '<span class="unknown">?</span>'
else:
points = format_decimal(self.sol.points)
if not self.link_to_paper:
return f'<td>{points}'
else:
url = url_for('org_submit_list', ct_id=self.contest_id, user_id=self.user.user_id, task_id=self.sol.task_id)
return td+f'<a href="{url}" title="Zobrazit detail řešení">{points}</a>'
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/r/<int:round_id>/h/<int:hier_id>/score')
@app.route('/org/contest/r/<int:round_id>/h/<int:hier_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")
@app.route('/public/kolo/<int:round_id>/vysledky', endpoint="public_score")
@app.route('/public/kolo/<int:round_id>/h/<int:hier_id>/vysledky', endpoint="public_score")
@app.route('/public/soutez/<int:ct_id>/vysledky', endpoint="public_score")
def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_id: Optional[int] = None):
public = request.endpoint == "public_score"
ctx = get_context(round_id=round_id, hier_id=hier_id, ct_id=ct_id, public=public)
contest = ctx.contest
round = ctx.round
format = request.args.get('format', "")
sess = db.get_session()
state = round.state
if contest:
state = contest.state
if state != db.RoundState.closed and not ctx.rights.have_right(Right.view_contestants):
raise werkzeug.exceptions.Forbidden()
is_edit = request.endpoint == 'org_score_edit'
if is_edit and not ctx.rights.have_right(Right.manage_contest):
raise werkzeug.exceptions.Forbidden()
score = Score(round.master, contest, ctx.hier_place)
tasks = score.get_tasks()
tasks.sort(key=mo.util.sortby_task_code)
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_(
sess.query(db.Round.round_id).filter_by(master_round_id=round.master_round_id)
)
).all()
subcontest_id_map = {}
for subcontest in all_subcontests:
subcontest_id_map[(subcontest.round_id, subcontest.master_contest_id)] = subcontest.contest_id
# Construct columns
is_export = (format != "")
columns = []
columns.append(Column(key='order', name='poradi', title='Pořadí'))
if is_export:
columns.append(Column(key='status', name='stav'))
columns.append(Column(key='participant', name='ucastnik', title='Účastník'))
if is_export and not public:
columns.append(Column(key='email', name='email'))
if not ct_id and round.level > 0:
columns.append(Column(key='contest', name='oblast', title=round.get_level().name.title()))
if is_export:
columns.append(Column(key='pion_place', name='soutezni_misto'))
columns.append(Column(key='school', name='skola', title='Škola'))
columns.append(Column(key='grade', name='rocnik', title='Ročník'))
if is_export and not public:
columns.append(Column(key='birth_year', name='rok_narozeni'))
for task in tasks:
title = task.code
if contest and contest.state != db.RoundState.closed:
local_ct_id = subcontest_id_map[(task.round_id, contest.master_contest_id)]
title = task.code
if ctx.rights.can_view_submits():
title = '<a href="{}">{}</a>'.format(
url_for('org_contest_task', ct_id=local_ct_id, task_id=task.task_id),
task.code
)
if ctx.rights.can_edit_points():
title += ' <a href="{}" title="Editovat body" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span></a>'.format(
url_for('org_contest_task_edit', ct_id=local_ct_id, task_id=task.task_id),
)
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='Pořadí na sdíleném místě'))
# columns.append(Column(key='order_key', name='order_key', title='Třídící klíč'))
# Construct rows
table_rows = []
for result in results:
user, pant, pion = result.user, result.pant, result.pion
school = pant.school_place
local_pion_ct_id = subcontest_id_map[(round.round_id, pion.contest_id)]
order_cell = OrderCell(result.order.place, result.order.span, result.order.continuation)
if result.winner:
status = 'vítěz'
elif result.successful:
status = 'úspěšný'
else:
status = ""
row = Row(keys={
'order': order_cell,
'status': status,
'user': user,
'email': user.email,
'participant': user.full_name() if state == db.RoundState.closed else cell_pion_link(user, local_pion_ct_id, user.full_name()),
'contest': CellLink(pion.contest.place.name or "–", url_for('public_score' if public else 'org_score', ct_id=pion.contest_id)),
'pion_place': pion.place.name,
'school': school.name or "–",
'grade': pant.grade,
'total_points': SolTotalPointsCell(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()
for task in tasks:
local_sol_ct_id = subcontest_id_map[(task.round_id, pion.contest_id)]
row.keys[f'task_{task.task_id}'] = SolPointsCell(
contest_id=local_sol_ct_id, user=user, sol=sols.get(task.task_id),
link_to_paper=ctx.rights.can_view_submits() and state != db.RoundState.closed,
)
if result.winner:
row.html_attr = {"class": "warning", "title": "Vítěz"}
elif result.successful:
row.html_attr = {"class": "success", "title": "Úspěšný řešitel"}
table_rows.append(row)
filename = f"vysledky_{round.year}-{round.category}-{round.level}"
if contest:
filename += f"_oblast_{contest.place.code or contest.place.place_id}"
table = Table(
columns=columns,
rows=table_rows,
filename=filename,
show_downlink=not is_edit,
)
group_rounds = round.get_group_rounds(True)
group_rounds.sort(key=lambda r: r.round_code())
if format == "":
return render_template(
'org_score.html',
ctx=ctx,
contest=contest, round=round, tasks=tasks,
table=table, messages=messages,
need_exp = any(task.experiment for task in tasks),
group_rounds=group_rounds,
can_view_submits=ctx.rights.can_view_submits(),
public=public,
edit_form=edit_form,
RoundScoreMode=db.RoundScoreMode,
)
else:
return table.send_as(format)