Skip to content
Snippets Groups Projects
Commit 93703d9f authored by Martin Mareš's avatar Martin Mareš
Browse files

Hierarchie: Kola a statistiky

parent 7acb33d7
No related branches found
No related tags found
No related merge requests found
from dataclasses import dataclass
import decimal
from flask import render_template, g, redirect, flash, request
import locale
......@@ -8,9 +9,9 @@ from bleach.sanitizer import ALLOWED_TAGS
import markdown
import os
from sqlalchemy import func
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import joinedload, aliased
from sqlalchemy.sql.functions import coalesce
from typing import Optional
from typing import Optional, List, Dict, Tuple
import werkzeug.exceptions
import wtforms
from wtforms import validators, ValidationError
......@@ -157,44 +158,93 @@ def create_subcontests(master_round: db.Round, master_contest: db.Contest):
app.logger.info(f"Podsoutěž #{subcontest.contest_id} založena: {db.row2dict(subcontest)}")
@app.route('/org/contest/r/<int:round_id>/', methods=('GET', 'POST'))
def org_round(round_id: int):
@dataclass
class ContestStat:
region: db.Place
contest: Optional[db.Contest] = None
num_contests: int = 0
num_active_pants: int = 0
num_unconfirmed_pants: int = 0
def region_stats(round: db.Round, region: db.Place) -> List[ContestStat]:
stats: Dict[int, ContestStat] = {}
sess = db.get_session()
ctx = get_context(round_id=round_id)
round = ctx.round
rights = ctx.rights
participants_count = sess.query(
db.Participation.contest_id,
func.count(db.Participation.user_id).label('count')
).group_by(db.Participation.contest_id).subquery()
# Účastníci jsou jen pod master contesty
contests_counts = (sess.query(
db.Contest,
coalesce(participants_count.c.count, 0)
).outerjoin(participants_count, db.Contest.master_contest_id == participants_count.c.contest_id)
.filter(db.Contest.round == round)
.options(joinedload(db.Contest.place))
if region.level > round.level:
return []
if (region.level >= round.level - 1
or region.level == 2 and round.level == 4):
# List individual contests
q = sess.query(db.Contest).filter_by(round=round)
q = db.filter_place_nth_parent(q, db.Contest.place_id, round.level - region.level, region.place_id)
q = q.options(joinedload(db.Contest.place))
for c in q.all():
s = ContestStat(region=c.place, contest=c, num_contests=1)
stats[c.place.place_id] = s
have_contests = True
else:
# List sub-regions
regs = sess.query(db.Place).filter(db.Place.parent_place == region).all()
for r in regs:
s = ContestStat(region=r)
stats[r.place_id] = s
have_contests = False
region_ids = [s.region.place_id for s in stats.values()]
if not have_contests:
rcs = (sess.query(db.RegionContestStat)
.filter_by(round=round)
.filter(db.RegionContestStat.region.in_(region_ids))
.all())
for r in rcs:
stats[r.region].num_contests = r.count
contests_counts.sort(key=lambda c: locale.strxfrm(c[0].place.name))
sol_counts_q = (
sess.query(db.Solution.task_id, func.count(db.Solution.task_id))
.filter(db.Solution.task_id.in_(
sess.query(db.Task.task_id).filter_by(round=round)
))
rs = (sess.query(db.RegionParticipantStat)
.filter_by(round_id=round.master_round_id)
.filter(db.RegionParticipantStat.region.in_(region_ids))
.all())
for r in rs:
if r.state == db.PartState.active:
stats[r.region].num_active_pants = r.count
elif r.state == db.PartState.registered:
stats[r.region].num_unconfirmed_pants = r.count
out = list(stats.values())
out.sort(key=lambda s: locale.strxfrm(s.region.name or ""))
return out
def region_totals(region: db.Place, stats: List[ContestStat]) -> ContestStat:
return ContestStat(
region=region,
num_contests=sum(s.num_contests for s in stats),
num_active_pants=sum(s.num_active_pants for s in stats),
num_unconfirmed_pants=sum(s.num_unconfirmed_pants for s in stats),
)
sol_counts = {}
for task_id, count in sol_counts_q.group_by(db.Solution.task_id).all():
sol_counts[task_id] = count
def task_stats(round: db.Round, region: db.Place) -> List[Tuple[db.Task, int]]:
sess = db.get_session()
tasks = sess.query(db.Task).filter_by(round=round).all()
tasks.sort(key=lambda t: t.code)
for task in tasks:
task.sol_count = sol_counts[task.task_id] if task.task_id in sol_counts else 0
ts = (sess.query(db.RegionTaskStat)
.filter_by(round=round, region=region.place_id)
.all())
count_by_id = {s.task_id: s.count for s in ts}
return [(t, count_by_id.get(t.task_id, 0)) for t in tasks]
@app.route('/org/contest/r/<int:round_id>/', methods=('GET', 'POST'))
@app.route('/org/contest/r/<int:round_id>/h/<int:hier_id>', methods=('GET', 'POST'))
def org_round(round_id: int, hier_id: Optional[int] = None):
ctx = get_context(round_id=round_id, hier_id=hier_id)
round = ctx.round
rights = ctx.rights
form_delete_task = TaskDeleteForm()
if rights.have_right(Right.manage_round) and delete_task(round_id, form_delete_task):
......@@ -208,13 +258,19 @@ def org_round(round_id: int):
group_rounds = round.get_group_rounds(True)
group_rounds.sort(key=lambda r: r.round_code())
region = ctx.hier_place or db.get_root_place()
reg_stats = region_stats(round, region)
reg_total = region_totals(region, reg_stats)
task_info = task_stats(round, region)
return render_template(
'org_round.html',
ctx=ctx, rights=rights,
round=round, group_rounds=group_rounds,
roles=[r.friendly_name() for r in rights.get_roles()],
contests_counts=contests_counts,
tasks=tasks, form_delete_task=form_delete_task,
reg_stats=reg_stats, reg_total=reg_total,
task_info=task_info,
form_delete_task=form_delete_task,
form_add_contest=form_add_contest,
statement_exists=mo.web.util.task_statement_exists(round),
)
......
......
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% set can_manage_round = rights.have_right(Right.manage_round) %}
{% set in_hier = ctx.hier_id != None %}
{% set can_manage_round = rights.have_right(Right.manage_round) and not in_hier %}
{% set can_manage_contest = rights.have_right(Right.manage_contest) %}
{% set can_view_contestants = rights.have_right(Right.view_contestants) %}
{% set can_handle_submits = rights.have_right(Right.view_submits) %}
......@@ -8,7 +9,13 @@
{% set can_view_statement = rights.can_view_statement() %}
{% set can_add_contest = g.gatekeeper.rights_generic().have_right(Right.add_contest) %}
{% block title %}{{ round.name }} {{ round.round_code() }}{% endblock %}
{% block title %}
{% if in_hier %}
{{ round.round_code() }}: {{ ctx.hier_place.name }}
{% else %}
{{ round.name }} {{ round.round_code() }}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
{{ ctx.breadcrumbs() }}
{% endblock %}
......@@ -81,7 +88,7 @@
{% if can_view_contestants %}
<a class="btn btn-primary" href='{{ ctx.url_for('org_generic_list') }}'>Seznam účastníků</a>
{% endif %}
{% if can_view_contestants and round.state in [RoundState.grading, RoundState.closed, RoundState.delegate] %}
{% if can_view_contestants and round.state in [RoundState.grading, RoundState.closed, RoundState.delegate] and not in_hier %}
<a class="btn btn-primary" href='{{ ctx.url_for('org_score') }}'>Výsledky</a>
{% endif %}
{% if can_manage_contest %}
......@@ -94,35 +101,53 @@
{% if round.has_messages %}
<a class="btn btn-default" href='{{ ctx.url_for('org_round_messages') }}'>Zprávičky</a>
{% endif %}
{% if g.user.is_admin %}
{% if g.user.is_admin and not in_hier %}
<a class="btn btn-default" href='{{ log_url('round', round.round_id) }}'>Historie</a>
{% endif %}
</div>
{% endif %}
<h3>Soutěže</h3>
{% if contests_counts %}
{% if reg_total.num_contests %}
{% set show_contests = reg_stats[0].contest != None %}
<table class=data>
<thead>
<tr>
{% if show_contests %}
<th>{{ round.get_level().name|capitalize }}
<th>Stav
{% else %}
<th>{{ reg_stats[0].region.type_name()|capitalize }}
<th>Počet soutěží
{% endif %}
<th>Počet účastníků
<th>Počet nepotvrzených
</tr>
</thead>
{% for (c, count) in contests_counts %}
{% for rs in reg_stats %}
<tr>
<td><a href='{{ url_for('org_contest', ct_id=c.contest_id) }}'>{{ c.place.name }}</a>
{% with state=c.state %}
{% if show_contests %}
<td><a href='{{ url_for('org_contest', ct_id=rs.contest.contest_id) }}'>{{ rs.region.name }}</a>
{% with state=rs.contest.state %}
<td class='rstate-{{state.name}}'>{{ state.friendly_name() }}
{% endwith %}
<td>{{ count }}
{% else %}
<td><a href='{{ ctx.url_for('org_round', hier_id=rs.region.place_id) }}'>{{ rs.region.name }}</a>
<td>{{ rs.num_contests }}
{% endif %}
<td>{{ rs.num_active_pants }}
<td>{{ rs.num_unconfirmed_pants }}
{% endfor %}
<tfoot>
<tr>
<th>Celkem
{% if show_contests %}
<th>
<th>{{ contests_counts|sum(attribute=1) }}
{% else %}
<th>{{ reg_total.num_contests }}
{% endif %}
<th>{{ reg_total.num_active_pants }}
<th>{{ reg_total.num_unconfirmed_pants }}
</tr>
</tfoot>
</table>
......@@ -139,7 +164,7 @@
{% endif %}
<h3>Úlohy</h3>
{% if tasks %}
{% if task_info %}
<table class=data>
<thead>
<tr>
......@@ -151,11 +176,11 @@
{% if can_handle_submits or can_upload %}<th>Dávkové operace{% endif %}
</tr>
</thead>
{% for task in tasks %}
{% for task, sol_count in task_info %}
<tr>
<td>{{ task.code }}
<td>{{ task.name }}
<td>{{ task.sol_count }}
<td>{{ sol_count }}
<td>{{ task.max_points|decimal|none_value('–') }}
{% if can_manage_round %}
<td><div class="btn-group">
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment