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

Přidána možnost uzavření všech soutěží v podstromu oblastí.

Closes #401.
parent 5a74c622
Branches
No related tags found
No related merge requests found
...@@ -68,8 +68,8 @@ def create_subcontests(master_round: db.Round, master_contest: db.Contest, reaso ...@@ -68,8 +68,8 @@ def create_subcontests(master_round: db.Round, master_contest: db.Contest, reaso
mo.util.logger.info(f"{reason.title()}: Podsoutěž #{subcontest.contest_id} založena: {db.row2dict(subcontest)}") mo.util.logger.info(f"{reason.title()}: Podsoutěž #{subcontest.contest_id} založena: {db.row2dict(subcontest)}")
def check_contest_state(round: db.Round, contest: Optional[db.Contest], state: db.RoundState) -> List[str]: def check_contest_state(round: db.Round, state: db.RoundState, contest: Optional[db.Contest] = None, hier_place: Optional[db.Place] = None) -> List[str]:
"""Kontrola stavu soutěží (buď zadané nebo všech v kole) při přepínání stavu.""" """Kontrola stavu soutěží (buď jedné určené nebo všech v kole/podstromu) při přepínání stavu."""
sess = db.get_session() sess = db.get_session()
errors = [] errors = []
...@@ -78,6 +78,8 @@ def check_contest_state(round: db.Round, contest: Optional[db.Contest], state: d ...@@ -78,6 +78,8 @@ def check_contest_state(round: db.Round, contest: Optional[db.Contest], state: d
.options(joinedload(db.Contest.place))) .options(joinedload(db.Contest.place)))
if contest is not None: if contest is not None:
contests_query = contests_query.filter_by(contest_id=contest.contest_id) contests_query = contests_query.filter_by(contest_id=contest.contest_id)
elif hier_place is not None:
contests_query = db.filter_place_nth_parent(contests_query, db.Contest.place_id, round.level - hier_place.level, hier_place.place_id)
def add_ct_errors(cts: List[db.Contest], msg: str) -> None: def add_ct_errors(cts: List[db.Contest], msg: str) -> None:
if len(cts) > 100: if len(cts) > 100:
...@@ -116,7 +118,7 @@ def check_contest_state(round: db.Round, contest: Optional[db.Contest], state: d ...@@ -116,7 +118,7 @@ def check_contest_state(round: db.Round, contest: Optional[db.Contest], state: d
if not round.is_subround() and state == db.RoundState.closed: if not round.is_subround() and state == db.RoundState.closed:
ct_no_score = (contests_query ct_no_score = (contests_query
.filter_by(scoretable_id=None) .filter(db.Contest.scoretable_id == None)
.filter((sess.query(db.Participation) .filter((sess.query(db.Participation)
.filter(db.Participation.contest_id == db.Contest.contest_id) .filter(db.Participation.contest_id == db.Contest.contest_id)
.filter_by(state=db.PartState.active)) .filter_by(state=db.PartState.active))
......
...@@ -1763,12 +1763,12 @@ def org_contest_edit(ct_id: int): ...@@ -1763,12 +1763,12 @@ def org_contest_edit(ct_id: int):
if form.submit.data or form.force_submit.data: if form.submit.data or form.force_submit.data:
do_submit = form.validate_on_submit() do_submit = form.validate_on_submit()
if do_submit: if do_submit:
errors = mo.contests.check_contest_state(round, contest, form.state.data) errors = mo.contests.check_contest_state(round=round, contest=contest, state=form.state.data)
if errors and not form.force_submit.data: if errors and not form.force_submit.data:
do_submit = False do_submit = False
offer_force_submit = True offer_force_submit = True
else: else:
errors = mo.contests.check_contest_state(round, contest, form.state.data) errors = mo.contests.check_contest_state(round=round, contest=contest, state=form.state.data)
if do_submit: if do_submit:
form.populate_obj(contest) form.populate_obj(contest)
......
# Web: Správa kol # Web: Správa kol
import bleach
from bleach.sanitizer import ALLOWED_TAGS
from dataclasses import dataclass, field from dataclasses import dataclass, field
import decimal import decimal
from flask import render_template, g, redirect, flash, request, url_for from flask import render_template, g, redirect, flash, request, url_for
import locale import locale
import flask_wtf.file import flask_wtf.file
from flask_wtf.form import FlaskForm from flask_wtf.form import FlaskForm
import bleach from functools import reduce
from bleach.sanitizer import ALLOWED_TAGS
import markdown import markdown
from markupsafe import Markup from markupsafe import Markup
import operator
import os import os
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from typing import Optional, List, Dict, Tuple, Set from typing import Optional, List, Dict, Tuple, Set
...@@ -142,7 +144,7 @@ def region_stats(round: db.Round, region: db.Place) -> List[ContestStat]: ...@@ -142,7 +144,7 @@ def region_stats(round: db.Round, region: db.Place) -> List[ContestStat]:
q = db.filter_place_nth_parent(q, db.Contest.place_id, round.level - region.level, region.place_id) 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), joinedload(db.Contest.master)) q = q.options(joinedload(db.Contest.place), joinedload(db.Contest.master))
for c in q.all(): for c in q.all():
s = ContestStat(region=c.place, contest=c, num_contests=1) s = ContestStat(region=c.place, contest=c, num_contests=1, contest_states={c.state})
stats[c.place.place_id] = s stats[c.place.place_id] = s
have_contests = True have_contests = True
else: else:
...@@ -185,6 +187,7 @@ def region_totals(region: db.Place, stats: List[ContestStat]) -> ContestStat: ...@@ -185,6 +187,7 @@ def region_totals(region: db.Place, stats: List[ContestStat]) -> ContestStat:
num_contests=sum(s.num_contests for s in stats), num_contests=sum(s.num_contests for s in stats),
num_active_pants=sum(s.num_active_pants 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), num_unconfirmed_pants=sum(s.num_unconfirmed_pants for s in stats),
contest_states=reduce(operator.or_, (s.contest_states for s in stats), set()),
) )
...@@ -234,6 +237,13 @@ def org_round(round_id: int, hier_id: Optional[int] = None): ...@@ -234,6 +237,13 @@ def org_round(round_id: int, hier_id: Optional[int] = None):
else: else:
round_jobs = None round_jobs = None
show_close_contests = (
round.state == db.RoundState.delegate and
rights.have_right(Right.manage_contest) and
reg_total.num_contests > 0 and
bool(reg_total.contest_states - {db.RoundState.closed})
)
return render_template( return render_template(
'org_round.html', 'org_round.html',
ctx=ctx, rights=rights, ctx=ctx, rights=rights,
...@@ -245,6 +255,7 @@ def org_round(round_id: int, hier_id: Optional[int] = None): ...@@ -245,6 +255,7 @@ def org_round(round_id: int, hier_id: Optional[int] = None):
form_add_contest=form_add_contest, form_add_contest=form_add_contest,
statement_exists=mo.web.util.task_statement_exists(round), statement_exists=mo.web.util.task_statement_exists(round),
round_jobs=round_jobs, round_jobs=round_jobs,
show_close_contests=show_close_contests,
) )
...@@ -505,7 +516,7 @@ def check_round_settings(round: db.Round, form: RoundEditForm) -> List[str]: ...@@ -505,7 +516,7 @@ def check_round_settings(round: db.Round, form: RoundEditForm) -> List[str]:
if form.enroll_deadline is not None and form.enroll_deadline.data and form.enroll_mode.data == db.RoundEnrollMode.manual: if form.enroll_deadline is not None and form.enroll_deadline.data and form.enroll_mode.data == db.RoundEnrollMode.manual:
errors.append('Nemá smysl definovat termín přihlašování, když není přihlašování zapnuto.') errors.append('Nemá smysl definovat termín přihlašování, když není přihlašování zapnuto.')
errors.extend(mo.contests.check_contest_state(round, None, state)) errors.extend(mo.contests.check_contest_state(round=round, state=state))
return errors return errors
...@@ -740,3 +751,56 @@ def org_contestant_stats(round_id: int, hier_id: Optional[int] = None): ...@@ -740,3 +751,56 @@ def org_contestant_stats(round_id: int, hier_id: Optional[int] = None):
state_counts=state_counts, state_counts=state_counts,
total=sum(state_counts.values()), total=sum(state_counts.values()),
) )
class CloseContestsForm(FlaskForm):
submit = wtforms.SubmitField('Ukončit')
force_submit = wtforms.SubmitField('Ukončit s chybami')
back = wtforms.SubmitField('Zpět')
@app.route('/org/contest/r/<int:round_id>/close-contests', methods=('GET', 'POST'))
@app.route('/org/contest/r/<int:round_id>/h/<int:hier_id>/close-contests', methods=('GET', 'POST'))
def org_close_contests(round_id: int, hier_id: Optional[int] = None):
ctx = get_context(round_id=round_id, hier_id=hier_id, right_needed=Right.manage_contest)
sess = db.get_session()
if ctx.round.state != db.RoundState.delegate:
flash('Stav kola neumožňuje ukončovat jednotlivé soutěže', 'danger')
return redirect(ctx.url_for('org_round'))
form = CloseContestsForm()
errors = mo.contests.check_contest_state(round=ctx.round, hier_place=ctx.hier_place, state=db.RoundState.closed)
if form.validate_on_submit():
if form.back.data:
return redirect(ctx.url_for('org_round'))
if form.submit.data and not errors or form.force_submit.data:
contests_q = sess.query(db.Contest).filter_by(round=ctx.round)
if ctx.hier_place is not None:
contests_q = db.filter_place_nth_parent(contests_q, db.Contest.place_id, ctx.round.level - ctx.hier_place.level, ctx.hier_place.place_id)
contests = contests_q.filter(db.Contest.state != db.RoundState.closed).all()
for c in contests:
c.state = db.RoundState.closed
changes = db.get_object_changes(c)
app.logger.info(f"Contest #{c.contest_id} modified, changes: {changes}")
mo.util.log(
type=db.LogType.contest,
what=c.contest_id,
details={'action': 'edit', 'changes': changes, 'reason': 'web'},
)
sess.commit()
flash(inflect_with_number(len(contests), 'Ukončena %s soutěž.', 'Ukončeny %s soutěže.', 'Ukončeno %s soutěží.'), 'success')
return redirect(ctx.url_for('org_round'))
if not errors:
del form.force_submit
return render_template(
'org_close_contests.html',
ctx=ctx,
form=form,
errors=errors,
)
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
{% endif %} {% endif %}
{% if can_add_contest %} {% if can_add_contest %}
<form action="" method="POST" class="form-inline"> <form action="" method="POST" class="form-inline" style='margin-bottom: 15px'>
{{ form_add_contest.csrf_token() }} {{ form_add_contest.csrf_token() }}
{{ wtf.form_field(form_add_contest.place) }} {{ wtf.form_field(form_add_contest.place) }}
{{ wtf.form_field(form_add_contest.create_contest) }} {{ wtf.form_field(form_add_contest.create_contest) }}
...@@ -198,6 +198,12 @@ ...@@ -198,6 +198,12 @@
</form> </form>
{% endif %} {% endif %}
{% if show_close_contests %}
<div class="btn-group">
<a class="btn btn-default" href='{{ ctx.url_for('org_close_contests') }}'>Ukončit soutěže</a>
</div>
{% endif %}
{% if can_manage_round %} {% if can_manage_round %}
<h3>Dávky pro správu kola</h3> <h3>Dávky pro správu kola</h3>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment