Skip to content
Snippets Groups Projects

Postup z předchozího kola

Merged Martin Mareš requested to merge mj/postup into devel
+ 138
2
@@ -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
@@ -17,7 +19,7 @@ from mo.imports import ImportType, create_import
import mo.jobs.submit
from mo.rights import Right, Rights
import mo.util
from mo.util_format import inflect_number
from mo.util_format import inflect_number, inflect_by_number
from mo.web import app
import mo.web.util
from mo.web.util import PagerForm
@@ -1187,3 +1189,137 @@ 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="Postoupí 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)
def redirect_back():
return redirect(url_for('org_contest', id=contest_id))
round = contest.round
if round.state != db.RoundState.preparing:
flash('Aktuální kolo není ve stavu přípravy', 'danger')
return redirect_back()
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_back()
elif prev_round.state != db.RoundState.closed:
flash('Předchozí kolo dosud nebylo ukončeno', 'danger')
return redirect_back()
elif prev_round.level < round.level:
flash('Předchozí kolo se koná ve vyšší oblasti než toto kolo', 'danger')
return redirect_back()
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
if want_execute:
app.logger.info(f'Postup: Z kola #{prev_round.round_id} do #{round.round_id}, soutěž #{contest_id}')
mo.util.log(
type=db.LogType.contest,
what=contest_id,
details={'action': 'advance'},
)
really_inserted = 0
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
res = conn.execute(
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()
.returning(db.Participation.contest_id)
)
inserted = res.fetchall()
if inserted:
# Opravdu došlo ke vložení
really_inserted += 1
app.logger.info(f'Postup: Založena účast user=#{pp.user_id} contest=#{contest_id} place=#{contest.place_id}')
mo.util.log(
type=db.LogType.participant,
what=pp.user_id,
details={
'action': 'add-to-contest',
# Tady nemůžeme použít obvyklé row2dict, neboť nemáme v ruce ORMový objekt
'new': {
'contest_id': contest.contest_id,
'place_id': contest.place_id,
},
},
)
if want_execute:
sess.commit()
msg = (inflect_by_number(really_inserted, 'Pozván', 'Pozváni', 'Pozváno')
+ ' '
+ inflect_number(really_inserted, 'nový soutěžící', 'noví soutěžící', 'nových soutěžících')
+ '.')
flash(msg, 'success')
return redirect_back()
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,
)
Loading