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 import and_
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from typing import List, Tuple, Optional, Dict from typing import List, Tuple, Optional, Dict
...@@ -7,6 +8,62 @@ import werkzeug.exceptions ...@@ -7,6 +8,62 @@ import werkzeug.exceptions
import mo.db as db import mo.db as db
from mo.rights import Right from mo.rights import Right
from mo.web import app 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') @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): ...@@ -16,6 +73,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
raise werkzeug.exceptions.BadRequest() raise werkzeug.exceptions.BadRequest()
if round_id is not None and contest_id is not None: if round_id is not None and contest_id is not None:
raise werkzeug.exceptions.BadRequest() raise werkzeug.exceptions.BadRequest()
format = request.args.get('format', "")
sess = db.get_session() sess = db.get_session()
user_id_subq = sess.query(db.Participation.user_id).join( 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): ...@@ -35,7 +93,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
if not contest: if not contest:
raise werkzeug.exceptions.NotFound() raise werkzeug.exceptions.NotFound()
round = contest.round round = contest.round
rr.get_for_contest(contest) rr = g.gatekeeper.rights_for_contest(contest)
contest_subq = [contest_id] contest_subq = [contest_id]
user_id_subq = user_id_subq.filter(db.Participation.contest == contest) 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): ...@@ -69,65 +127,100 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
).all() ).all()
) )
class ResultOrder: # Construct columns
span: int columns = [
place: int Column(key='order', name='poradi', title='Pořadí'),
Column(key='participant', name='ucastnik', title='Účastník'),
def format(self): ]
if self.span == 1: if not contest_id:
return f"{self.place}." columns.append(Column(key='contest', name='oblast', title='Soutěžní oblast'))
else: columns.append(Column(key='school', name='skola', title='Škola'))
return f"{self.place}.–{self.place + self.span - 1}." columns.append(Column(key='grade', name='rocnik', title='Ročník'))
for task in tasks:
class Result: title = task.code
def __init__(self, user: db.User, contest: db.Contest, school: db.School): if contest_id:
self.user = user title = '<a href="{}">{}</a>'.format(
self.contest = contest url_for('org_contest_task_submits', contest_id=contest_id, task_id=task.task_id),
self.school = school task.code
self.order = ResultOrder() )
self.points: Dict[int, int] = {} if rr.have_right(Right.edit_points) and round.state == db.RoundState.grading:
self.total_points = 0 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: 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: 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: 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 # 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 # 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ě?) # různé olympiády i různé ročníky to mohou mít různě?)
def get_result_cmp(result): def get_result_cmp(result):
return -result.total_points return -result.keys['total_points']
def get_result_full_cmp(result): 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) rows.sort(key=get_result_full_cmp)
last = None
for result in results: # 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: if last is None:
result.order.place = 1 row.keys['order'] = OrderCell(1)
result.order.span = 1 last = row
last = result elif get_result_cmp(last) == get_result_cmp(row):
elif get_result_cmp(last) == get_result_cmp(result): row.keys['order'] = last.keys['order']
result.order.place = last.order.place last.keys['order'].span += 1
result.order.span = 0 else:
last.order.span += 1 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: else:
result.order.place = last.order.place + last.order.span lastOrder = row.keys['order']
result.order.span = 1
last = result 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( return render_template(
'org_score.html', 'org_score.html',
contest=contest, round=round, tasks=tasks, contest=contest, round=round, tasks=tasks,
results=results, table=table,
can_edit_points=rr.have_right(Right.edit_points) and round.state == db.RoundState.grading,
db=db, # kvůli hodnotám enumů db=db, # kvůli hodnotám enumů
) )
else:
return table.send_as(format)
...@@ -17,42 +17,6 @@ ...@@ -17,42 +17,6 @@
<p>Pořadí je neoficiální (seřazené podle součtu bodů s dělenými místy), testovací <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> uživatelé jsou skryti. Rozkliknutím bodů se lze dostat na detail daného řešení.</p>
<table class="data full center"> {{ table.to_html() }}
<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>
{% endblock %} {% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment