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

Score: Přepracováno na vypisování pomocí mo.web.table (umožňuje i export)

Bylo potřeba implementovat několik vylepšení mo.web.table a pořídit si dvě
vlastní buňkové třídy:
* pro pořadí, které v HTML umí dělat rowspan
* pro zobrazení bodů s odkazem na řešení, (může být prázdné, ukazovat otazníčky nebo body)

Řeší 3. subtask issue #94
parent 24a65da5
No related branches found
No related tags found
1 merge request!17Výsledkovka pomocí mo.web.table
This commit is part of merge request !17. Comments created here will be created in the context of that merge request.
from flask import render_template, g
from flask import render_template, request, g
from flask.helpers import url_for
from sqlalchemy import and_
from sqlalchemy.orm import joinedload
from typing import List, Tuple, Optional, Dict
......@@ -7,6 +8,62 @@ import werkzeug.exceptions
import mo.db as db
from mo.rights import Right
from mo.web import app
from mo.web.table import Cell, CellLink, Column, Row, Table, cell_user_link
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 SolPointsCell(Cell):
url: str
exists: bool
points: Optional[int]
def __init__(self, url: str):
self.url = url
self.exists = False
self.points = None
def __str__(self) -> str:
if not self.exists:
return ''
elif self.points is None:
return '?'
return str(self.points)
def set_points(self, points: Optional[int]):
self.exists = True
self.points = points
def to_html(self) -> str:
if not self.exists:
return '<td>–'
a = f'<td><a href="{self.url}" title="Detail řešení">'
if self.points is None:
return a + '<span class="unknown">?</span></a>'
return a + str(self.points) + '</a>'
@app.route('/org/contest/r/<int:round_id>/score')
......@@ -16,6 +73,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
raise werkzeug.exceptions.BadRequest()
if round_id is not None and contest_id is not None:
raise werkzeug.exceptions.BadRequest()
format = request.args.get('format', "")
sess = db.get_session()
user_id_subq = sess.query(db.Participation.user_id).join(
......@@ -35,7 +93,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
if not contest:
raise werkzeug.exceptions.NotFound()
round = contest.round
rr.get_for_contest(contest)
rr = g.gatekeeper.rights_for_contest(contest)
contest_subq = [contest_id]
user_id_subq = user_id_subq.filter(db.Participation.contest == contest)
......@@ -69,65 +127,100 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
).all()
)
class ResultOrder:
span: int
place: int
def format(self):
if self.span == 1:
return f"{self.place}."
else:
return f"{self.place}.–{self.place + self.span - 1}."
class Result:
def __init__(self, user: db.User, contest: db.Contest, school: db.School):
self.user = user
self.contest = contest
self.school = school
self.order = ResultOrder()
self.points: Dict[int, int] = {}
self.total_points = 0
# Construct columns
columns = [
Column(key='order', name='poradi', title='Pořadí'),
Column(key='participant', name='ucastnik', title='Účastník'),
]
if not contest_id:
columns.append(Column(key='contest', name='oblast', title='Soutěžní oblast'))
columns.append(Column(key='school', name='skola', title='Škola'))
columns.append(Column(key='grade', name='rocnik', title='Ročník'))
for task in tasks:
title = task.code
if contest_id:
title = '<a href="{}">{}</a>'.format(
url_for('org_contest_task_submits', contest_id=contest_id, task_id=task.task_id),
task.code
)
if rr.have_right(Right.edit_points) and round.state == db.RoundState.grading:
title += ' (<a href="{}" title="Editovat body">✎</a>)'.format(
url_for('org_contest_task_points', contest_id=contest_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'))
results_map: Dict[int, Result] = {}
# Construct rows
rows_map: Dict[int, Row] = {}
for user, pion, pant in data:
results_map[user.user_id] = Result(user, pion.contest, pant.school_place)
school = pant.school_place
row = Row(keys={
'user': user,
'participant': cell_user_link(user, user.full_name()),
'contest': CellLink(pion.contest.place.name, url_for('org_contest', id=pion.contest_id)),
'school': CellLink(school.name, url_for('org_place', id=school.place_id)),
'grade': pant.grade,
'total_points': 0,
})
for task in tasks:
row.keys[f'task_{task.task_id}'] = SolPointsCell(
url_for('org_submit_list', contest_id=pion.contest_id, user_id=user.user_id, task_id=task.task_id)
)
rows_map[user.user_id] = row
for sol in sols:
results_map[sol.user_id].points[sol.task_id] = sol.points
rows_map[sol.user_id].keys[f'task_{sol.task_id}'].set_points(sol.points)
if sol.points:
results_map[sol.user_id].total_points += sol.points
rows_map[sol.user_id].keys['total_points'] += sol.points
results = list(results_map.values())
rows = list(rows_map.values())
# FIXME: Pokud to chceme přetavit do finální výsledkovky, tak musíme
# předefinovat následující funkci (a udělat to konfigurovatelné, protože
# různé olympiády i různé ročníky to mohou mít různě?)
def get_result_cmp(result):
return -result.total_points
return -result.keys['total_points']
def get_result_full_cmp(result):
return (get_result_cmp(result), result.user.last_name, result.user.first_name)
return (get_result_cmp(result), result.keys['user'].last_name, result.keys['user'].first_name)
results.sort(key=get_result_full_cmp)
last = None
for result in results:
rows.sort(key=get_result_full_cmp)
# Spočítáme pořadí - v prvním kroku prolinkujeme opakující se OrderCell na první,
# ve druhém kroku je pak správně rozkopírujeme s nastaveným continuation na True
last: Row = None
for row in rows:
if last is None:
result.order.place = 1
result.order.span = 1
last = result
elif get_result_cmp(last) == get_result_cmp(result):
result.order.place = last.order.place
result.order.span = 0
last.order.span += 1
row.keys['order'] = OrderCell(1)
last = row
elif get_result_cmp(last) == get_result_cmp(row):
row.keys['order'] = last.keys['order']
last.keys['order'].span += 1
else:
row.keys['order'] = OrderCell(last.keys['order'].place + last.keys['order'].span)
last = row
lastOrder: OrderCell = None
for row in rows:
if row.keys['order'] == lastOrder:
row.keys['order'] = OrderCell(lastOrder.place, lastOrder.span, True)
else:
result.order.place = last.order.place + last.order.span
result.order.span = 1
last = result
lastOrder = row.keys['order']
filename = f"vysledky_{round.year}-{round.category}-{round.level}"
if contest_id:
filename += f"_oblast_{contest.place.code or contest.place.place_id}"
table = Table(
table_class="data full center",
columns=columns,
rows=rows,
filename=filename,
)
if format == "":
return render_template(
'org_score.html',
contest=contest, round=round, tasks=tasks,
results=results,
can_edit_points=rr.have_right(Right.edit_points) and round.state == db.RoundState.grading,
table=table,
db=db, # kvůli hodnotám enumů
)
else:
return table.send_as(format)
......@@ -17,42 +17,6 @@
<p>Pořadí je neoficiální (seřazené podle součtu bodů s dělenými místy), testovací
uživatelé jsou skryti. Rozkliknutím bodů se lze dostat na detail daného řešení.</p>
<table class="data full center">
<thead>
<tr>
<th>Pořadí
<th>Účastník
{% if not contest %}<th>Soutěžní oblast{% endif %}
<th>Škola
{% for task in tasks %}<th>
{% if contest %}
<a href="{{ url_for('org_contest_task_submits', contest_id=contest.contest_id, task_id=task.task_id) }}">{{ task.code }}</a>
{% if can_edit_points %}
(<a title="Editovat body" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}"></a>)
{% endif %}
{% else %}{{ task.code }}{% endif %}
{% endfor %}
<th>Celkové body
</tr>
</thead>
{% for result in results %}
<tr>
{% if result.order.span > 0 %}<td{% if result.order.span > 1 %} rowspan={{ result.order.span }}{% endif %}>
{{ result.order.format() }}
{% endif %}
<th>{{ result.user|user_link }}
{% if not contest %}<td><a href="{{ url_for('org_contest', id=result.contest.contest_id) }}">{{ result.contest.place.name }}</a>{% endif %}
<td><a href="{{ url_for('org_place', id=result.school.place_id) }}">{{ result.school.name }}</a>
{% for task in tasks %}
<td>{% if task.task_id in result.points %}
<a title="Detail řešení" href="{{ url_for('org_submit_list', contest_id=result.contest.contest_id, user_id=result.user.user_id, task_id=task.task_id) }}">
{% if result.points[task.task_id] is not none %}{{ result.points[task.task_id] }}{% else %}<span class="unknown">?</span>{% endif %}
</a>
{% else %}–{% endif %}
{% endfor %}
<th>{{ result.total_points }}</th>
</tr>
{% endfor %}
</table>
{{ table.to_html() }}
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment