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

UI pro postup z předchozího kola

parent 18ddcd33
No related branches found
No related tags found
1 merge request!29Postup z předchozího kola
This commit is part of merge request !29. Comments created here will be created in the context of that merge request.
......@@ -2,10 +2,12 @@ from dataclasses import dataclass
from flask import render_template, g, redirect, url_for, flash, request
from flask_wtf import FlaskForm
import flask_wtf.file
import locale
from markupsafe import Markup
from sqlalchemy import func, and_
from sqlalchemy import func, and_, select
from sqlalchemy.orm import joinedload, aliased
from sqlalchemy.orm.query import Query
from sqlalchemy.dialects.postgresql import insert as pgsql_insert
from typing import Any, List, Tuple, Optional, Sequence, Dict
import werkzeug.exceptions
import wtforms
......@@ -1187,3 +1189,99 @@ def org_contest_user(contest_id: int, user_id: int):
paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, None, u, p),
paper_counts=paper_counts,
)
class AdvanceForm(FlaskForm):
boundary = IntegerField('Bodová hranice', description="Postupí všichni účastníci, kteří v minulém kole získali aspoň tolik bodů.", validators=[validators.InputRequired()])
status = wtforms.HiddenField()
preview = wtforms.SubmitField('Zobrazit návrh')
execute = wtforms.SubmitField('Provést')
@app.route('/org/contest/c/<int:contest_id>/advance', methods=('GET', 'POST'))
def org_contest_advance(contest_id: int):
sess = db.get_session()
conn = sess.connection()
contest, rr = get_contest_rr(contest_id, Right.manage_contest)
round = contest.round
if round.state != db.RoundState.preparing:
flash('Aktuální kolo není ve stavu přípravy', 'danger')
return redirect(url_for('org_contest', id=contest_id))
prev_round = sess.query(db.Round).filter_by(year=round.year, category=round.category, seq=round.seq - 1).one_or_none()
if prev_round is None:
flash('Předchozí kolo nenalezeno', 'danger')
return redirect(url_for('org_contest', id=contest_id))
elif prev_round.state != db.RoundState.closed:
flash('Předchozí kolo dosud nebylo ukončeno', 'danger')
return redirect(url_for('org_contest', id=contest_id))
elif prev_round.level < round.level:
flash('Předchozí kolo se koná ve vyšší oblasti než toto kolo', 'danger')
return redirect(url_for('org_contest', id=contest_id))
prev_contests: List[db.Contest] = []
accept_by_place_id: Dict[int, int] = {}
reject_by_place_id: Dict[int, int] = {}
form = AdvanceForm()
if form.validate_on_submit():
desc_cte = db.place_descendant_cte(contest.place, max_level=prev_round.level)
prev_contests = (sess.query(db.Contest)
.filter(db.Contest.round == prev_round)
.filter(db.Contest.place_id.in_(select([desc_cte])))
.options(joinedload(db.Contest.place))
.all())
prev_contests.sort(key=lambda c: locale.strxfrm(c.place.name or ""))
accept_by_place_id = {c.place_id: 0 for c in prev_contests}
reject_by_place_id = {c.place_id: 0 for c in prev_contests}
prev_pion_query = (sess.query(db.Participation)
.filter(db.Participation.contest_id.in_([c.contest_id for c in prev_contests]))
.filter(db.Participation.state.in_((db.PartState.registered, db.PartState.invited, db.PartState.present))))
prev_pions = prev_pion_query.all()
if form.boundary.data > 0:
accept_uids = (sess.query(db.Solution.user_id)
.select_from(db.Solution)
.join(db.Task, and_(db.Task.task_id == db.Solution.task_id, db.Task.round == prev_round))
.filter(db.Solution.user_id.in_(prev_pion_query.with_entities(db.Participation.user_id).subquery()))
.group_by(db.Solution.user_id)
.having(func.sum(db.Solution.points) >= form.boundary.data)
.all())
accept_uids = [a[0] for a in accept_uids]
else:
accept_uids = None
want_execute = form.execute.data
for pp in prev_pions:
if accept_uids and pp.user_id not in accept_uids:
reject_by_place_id[pp.place_id] += 1
continue
accept_by_place_id[pp.place_id] += 1
if want_execute:
# ORM neumí ON CONFLICT DO NOTHING, takže musíme o vrstvu níže
ins = pgsql_insert(db.Participation.__table__).values(
user_id=pp.user_id,
contest_id=contest.contest_id,
place_id=contest.place.place_id,
state=db.PartState.invited,
).on_conflict_do_nothing()
conn.execute(ins)
if want_execute:
sess.commit()
flash('Provedeno.', 'success')
return render_template(
'org_contest_advance.html',
contest=contest,
round=contest.round,
prev_round=prev_round,
prev_contests=prev_contests,
accept_by_place_id=accept_by_place_id,
reject_by_place_id=reject_by_place_id,
form=form,
)
......@@ -32,11 +32,16 @@
<div class="btn-group">
<a class="btn btn-primary" href='{{ url_for('org_contest_list', id=contest.contest_id, site_id=site_id) }}'>Seznam účastníků</a>
{% if round.state != RoundState.preparing %}
<a class="btn btn-primary" href='{{ url_for('org_contest_solutions', id=contest.contest_id, site_id=site_id) }}'>Odevzdaná řešení</a>
{% endif %}
{% if not site %}
{% if round.state in [RoundState.grading, RoundState.closed] %}
<a class="btn btn-primary" href='{{ url_for('org_score', contest_id=contest.contest_id) }}'>Výsledky</a>
{% endif %}
{% if round.state == RoundState.preparing and round.seq > 1 %}
<a class="btn btn-primary" href='{{ url_for('org_contest_advance', contest_id=contest.contest_id) }}'>Postup z minulého kola</a>
{% endif %}
{% if can_manage %}
<a class="btn btn-default" href='{{ url_for('org_contest_import', id=contest.contest_id) }}'>Importovat data</a>
{% endif %}
......
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Postup z {{ prev_round.round_code() }} ({{ prev_round.name }}) do {{ round.round_code() }} ({{ round.name }}){% endblock %}
{% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, action="Postup") }}
{% endblock %}
{% block body %}
<form method="POST" class="form form-horizontal" action="">
{{ form.csrf_token }}
{{ wtf.form_field(form.boundary, form_type='horizontal') }}
<div class="btn-group col-lg-offset-2">
{{ wtf.form_field(form.preview) }}
{{ wtf.form_field(form.execute, class="btn btn-primary") }}
</div>
</form>
{% if form.preview.data or form.execute.data %}
<h3>Postup z oblastí</h3>
<table class='data'>
<thead>
<tr><th>Oblast<th>Postoupilo<th>Nepostoupilo
<tbody>
{% for c in prev_contests %}
<tr>
<td>{{ c.place.name }}
<td>{{ accept_by_place_id[c.place.place_id] }}
<td>{{ reject_by_place_id[c.place.place_id] }}
{% endfor %}
<tfoot>
<tr>
<th>Celkem
<th>{{ accept_by_place_id.values()|sum }}
<th>{{ reject_by_place_id.values()|sum }}
</tfoot>
</table>
{% endif %}
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment