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

Reforma orgovského rozhraní ke kolům a soutěžím

Místo spousty izolovaných funkcí na resolvování parametrů z URL
zaveden společný kontext. To umožňuje daleko jednodušší implementaci
generických stránek, které pracují jak s koly, tak se soutěžemi
a soutěžními místy.
parent 15f71507
No related branches found
No related tags found
1 merge request!95Reforma orgovského rozhraní ke kolům a soutěžím
Showing
with 788 additions and 804 deletions
...@@ -198,31 +198,6 @@ class Rights: ...@@ -198,31 +198,6 @@ class Rights:
return self.have_right(Right.edit_orgs) return self.have_right(Right.edit_orgs)
return self.have_right(Right.edit_users) return self.have_right(Right.edit_users)
# Interní rozhodovaní o dostupnosti zadání
def _check_view_statement(self, round: db.Round):
if round.tasks_file is None:
return False
if self.have_right(Right.manage_round):
# Správce kola může vždy všechno
return True
# Pokud už soutěž skončila, přístup k zadání má každý org.
# XXX: Rozhodujeme podle stavu kola, nikoliv soutěže!
if round.state in [db.RoundState.grading, db.RoundState.closed]:
return True
# Od stanoveného času vidí zadání orgové s právem view_statement.
if (self.have_right(Right.view_statement)
and round.state != db.RoundState.preparing
and round.pr_tasks_start is not None
and mo.now >= round.pr_tasks_start):
return True
# Ve zbylých případech jsme konzervativní a zadání neukazujeme
return False
class RoundRights(Rights): class RoundRights(Rights):
"""Práva ke kolu.""" """Práva ke kolu."""
...@@ -237,8 +212,11 @@ class RoundRights(Rights): ...@@ -237,8 +212,11 @@ class RoundRights(Rights):
# Metody offer_* testují, zda se má v UI nabízet příslušná funkce. # Metody offer_* testují, zda se má v UI nabízet příslušná funkce.
# Skutečnou kontrolu práv dělá až implementace funkce podle stavu soutěže. # Skutečnou kontrolu práv dělá až implementace funkce podle stavu soutěže.
def _get_state(self) -> db.RoundState:
return self.round.state
def _is_active(self) -> bool: def _is_active(self) -> bool:
return self.round.state not in [db.RoundState.preparing, db.RoundState.closed] return self._get_state() not in [db.RoundState.preparing, db.RoundState.closed]
def offer_upload_solutions(self) -> bool: def offer_upload_solutions(self) -> bool:
return (self.have_right(Right.upload_submits) return (self.have_right(Right.upload_submits)
...@@ -252,11 +230,47 @@ class RoundRights(Rights): ...@@ -252,11 +230,47 @@ class RoundRights(Rights):
return (self.have_right(Right.manage_contest) return (self.have_right(Right.manage_contest)
or (self.have_right(Right.edit_points) and self._is_active())) or (self.have_right(Right.edit_points) and self._is_active()))
def can_view_statement(self) -> bool: def can_upload_solutions(self) -> bool:
return self._check_view_statement(self.round) return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_solutions) and self._get_state() == db.RoundState.running)
def can_upload_feedback(self) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_feedback) and self._get_state() == db.RoundState.grading)
def can_edit_points(self) -> bool:
return (self.have_right(Right.edit_points) and self._get_state() == db.RoundState.grading
or self.have_right(Right.manage_contest))
def can_create_solutions(self) -> bool:
return self.can_upload_solutions() or self.can_upload_feedback()
def can_view_statement(self):
round = self.round
if round.tasks_file is None:
return False
if self.have_right(Right.manage_round):
# Správce kola může vždy všechno
return True
# Pokud už soutěž skončila, přístup k zadání má každý org.
# XXX: Rozhodujeme podle stavu kola, nikoliv soutěže!
if round.state in [db.RoundState.grading, db.RoundState.closed]:
return True
# Od stanoveného času vidí zadání orgové s právem view_statement.
if (self.have_right(Right.view_statement)
and round.state != db.RoundState.preparing
and round.pr_tasks_start is not None
and mo.now >= round.pr_tasks_start):
return True
# Ve zbylých případech jsme konzervativní a zadání neukazujeme
return False
class ContestRights(Rights): class ContestRights(RoundRights):
"""Práva k soutěži.""" """Práva k soutěži."""
contest: db.Contest contest: db.Contest
...@@ -264,22 +278,10 @@ class ContestRights(Rights): ...@@ -264,22 +278,10 @@ class ContestRights(Rights):
def __repr__(self): def __repr__(self):
ros = " ".join([r.role.name for r in self.user_roles]) ros = " ".join([r.role.name for r in self.user_roles])
ris = " ".join([r.name for r in self.rights]) ris = " ".join([r.name for r in self.rights])
return f"ContestRights(uid={self.user.user_id} is_admin={self.user.is_admin} contest=#{self.contest.contest_id} roles=<{ros}> rights=<{ris}>)" return f"ContestRights(uid={self.user.user_id} is_admin={self.user.is_admin} round=#{self.round.round_id} contest=#{self.contest.contest_id} roles=<{ros}> rights=<{ris}>)"
def can_upload_solutions(self) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_solutions) and self.contest.state == db.RoundState.running)
def can_upload_feedback(self) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_feedback) and self.contest.state == db.RoundState.grading)
def can_edit_points(self) -> bool:
return (self.have_right(Right.edit_points) and self.contest.state == db.RoundState.grading
or self.have_right(Right.manage_contest))
def can_view_statement(self) -> bool: def _get_state(self) -> db.RoundState:
return self._check_view_statement(self.contest.round) return self.contest.state
class Gatekeeper: class Gatekeeper:
...@@ -347,9 +349,11 @@ class Gatekeeper: ...@@ -347,9 +349,11 @@ class Gatekeeper:
"""Posbírá role a práva, ale ignoruje omezení rolí na místa a soutěže. Hodí se pro práva k editaci uživatelů apod.""" """Posbírá role a práva, ale ignoruje omezení rolí na místa a soutěže. Hodí se pro práva k editaci uživatelů apod."""
return self.rights_for() return self.rights_for()
def rights_for_round(self, round: db.Round, any_place: bool) -> RoundRights: def rights_for_round(self, round: db.Round, any_place: bool = False, for_place: Optional[db.Place] = None) -> RoundRights:
if any_place: if any_place:
place = None place = None
elif for_place:
place = for_place
else: else:
place = db.get_root_place() place = db.get_root_place()
rights = RoundRights() rights = RoundRights()
...@@ -364,6 +368,7 @@ class Gatekeeper: ...@@ -364,6 +368,7 @@ class Gatekeeper:
def rights_for_contest(self, contest: db.Contest, site: Optional[db.Place] = None) -> ContestRights: def rights_for_contest(self, contest: db.Contest, site: Optional[db.Place] = None) -> ContestRights:
rights = ContestRights() rights = ContestRights()
rights.round = contest.round
rights.contest = contest rights.contest = contest
rights._clone_from(self.rights_for( rights._clone_from(self.rights_for(
place=site or contest.place, place=site or contest.place,
......
...@@ -9,9 +9,9 @@ import urllib.parse ...@@ -9,9 +9,9 @@ import urllib.parse
import mo.config as config import mo.config as config
import mo.db as db import mo.db as db
from mo.rights import Right
import mo.util_format as util_format import mo.util_format as util_format
from mo.web import app from mo.web import app
from mo.web.org_contest import contest_breadcrumbs
from mo.web.org_place import place_breadcrumbs from mo.web.org_place import place_breadcrumbs
from mo.web.util import user_html_flags from mo.web.util import user_html_flags
...@@ -47,10 +47,10 @@ app.jinja_env.globals.update(JobState=db.JobState) ...@@ -47,10 +47,10 @@ app.jinja_env.globals.update(JobState=db.JobState)
# Další typy: # Další typy:
app.jinja_env.globals.update(Markup=Markup) app.jinja_env.globals.update(Markup=Markup)
app.jinja_env.globals.update(Right=Right)
# Vlastní pomocné funkce # Vlastní pomocné funkce
app.jinja_env.globals.update(contest_breadcrumbs=contest_breadcrumbs)
app.jinja_env.globals.update(place_breadcrumbs=place_breadcrumbs) app.jinja_env.globals.update(place_breadcrumbs=place_breadcrumbs)
# Funkce asset_url se přidává v mo.ext.assets # Funkce asset_url se přidává v mo.ext.assets
...@@ -60,6 +60,7 @@ def user_link(u: db.User) -> Markup: ...@@ -60,6 +60,7 @@ def user_link(u: db.User) -> Markup:
return Markup('<a href="{url}">{name}{test}</a>').format(url=user_url(u), name=u.full_name(), test=" (test)" if u.is_test else "") return Markup('<a href="{url}">{name}{test}</a>').format(url=user_url(u), name=u.full_name(), test=" (test)" if u.is_test else "")
@app.template_filter()
def user_url(u: db.User) -> str: def user_url(u: db.User) -> str:
if u.is_admin or u.is_org: if u.is_admin or u.is_org:
return url_for('org_org', id=u.user_id) return url_for('org_org', id=u.user_id)
...@@ -69,7 +70,7 @@ def user_url(u: db.User) -> str: ...@@ -69,7 +70,7 @@ def user_url(u: db.User) -> str:
@app.template_filter() @app.template_filter()
def pion_link(u: db.User, contest_id: int) -> Markup: def pion_link(u: db.User, contest_id: int) -> Markup:
url = url_for('org_contest_user', contest_id=contest_id, user_id=u.user_id) url = url_for('org_contest_user', ct_id=contest_id, user_id=u.user_id)
return Markup('<a href="{url}">{name}{test}</a>').format(url=url, name=u.full_name(), test=" (test)" if u.is_test else "") return Markup('<a href="{url}">{name}{test}</a>').format(url=url, name=u.full_name(), test=" (test)" if u.is_test else "")
......
This diff is collapsed.
import decimal import decimal
from flask import render_template, g, redirect, url_for, flash, request from flask import render_template, g, redirect, flash, request
import locale import locale
import flask_wtf.file import flask_wtf.file
from flask_wtf.form import FlaskForm from flask_wtf.form import FlaskForm
...@@ -10,7 +10,7 @@ import os ...@@ -10,7 +10,7 @@ import os
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from sqlalchemy.sql.functions import coalesce from sqlalchemy.sql.functions import coalesce
from typing import Optional, Tuple from typing import Optional
import werkzeug.exceptions import werkzeug.exceptions
import wtforms import wtforms
from wtforms import validators, ValidationError from wtforms import validators, ValidationError
...@@ -19,35 +19,11 @@ from wtforms.widgets.html5 import NumberInput ...@@ -19,35 +19,11 @@ from wtforms.widgets.html5 import NumberInput
import mo.config as config import mo.config as config
import mo.db as db import mo.db as db
import mo.imports import mo.imports
from mo.rights import Right, RoundRights from mo.rights import Right
import mo.util import mo.util
from mo.web import app from mo.web import app
import mo.web.fields as mo_fields import mo.web.fields as mo_fields
from mo.web.org_contest import ParticipantsActionForm, ParticipantsFilterForm, get_contestant_emails, get_contestants_query, make_contestant_table, \ from mo.web.org_contest import get_context
generic_import, generic_batch_download, generic_batch_upload, generic_batch_points
def get_round_rr(id: int, right_needed: Optional[Right], any_place: bool) -> Tuple[db.Round, db.Round, RoundRights]:
"""Vrací round, master_round a Rights objekt pro zadané round_id.
Pro nedělená kola platí round == master_round.
Operace s účastníky by měly probíhat vždy přes master_round."""
round = db.get_session().query(db.Round).options(joinedload(db.Round.master)).get(id)
if not round:
raise werkzeug.exceptions.NotFound()
rr = g.gatekeeper.rights_for_round(round, any_place)
if not (right_needed is None or rr.have_right(right_needed)):
raise werkzeug.exceptions.Forbidden()
return round, round.master, rr
def get_task(round: db.Round, task_id: int) -> db.Task:
task = db.get_session().query(db.Task).get(task_id)
if not task or task.round_id != round.round_id:
raise werkzeug.exceptions.NotFound()
return task
@app.route('/org/contest/') @app.route('/org/contest/')
...@@ -181,20 +157,19 @@ def create_subcontests(master_round: db.Round, master_contest: db.Contest): ...@@ -181,20 +157,19 @@ 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.logger.info(f"Podsoutěž #{subcontest.contest_id} založena: {db.row2dict(subcontest)}")
@app.route('/org/contest/r/<int:id>/', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/', methods=('GET', 'POST'))
def org_round(id: int): def org_round(round_id: int):
sess = db.get_session() sess = db.get_session()
round, _, rr = get_round_rr(id, None, True) ctx = get_context(round_id=round_id)
round = ctx.round
can_manage_round = rr.have_right(Right.manage_round) rights = ctx.rights
can_manage_contestants = rr.have_right(Right.manage_contest)
participants_count = sess.query( participants_count = sess.query(
db.Participation.contest_id, db.Participation.contest_id,
func.count(db.Participation.user_id).label('count') func.count(db.Participation.user_id).label('count')
).group_by(db.Participation.contest_id).subquery() ).group_by(db.Participation.contest_id).subquery()
# účastníci jsou jen pod master contesty # Účastníci jsou jen pod master contesty
contests_counts = (sess.query( contests_counts = (sess.query(
db.Contest, db.Contest,
coalesce(participants_count.c.count, 0) coalesce(participants_count.c.count, 0)
...@@ -222,30 +197,25 @@ def org_round(id: int): ...@@ -222,30 +197,25 @@ def org_round(id: int):
task.sol_count = sol_counts[task.task_id] if task.task_id in sol_counts else 0 task.sol_count = sol_counts[task.task_id] if task.task_id in sol_counts else 0
form_delete_task = TaskDeleteForm() form_delete_task = TaskDeleteForm()
if can_manage_round and delete_task(id, form_delete_task): if rights.have_right(Right.manage_round) and delete_task(round_id, form_delete_task):
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
form_add_contest = AddContestForm() form_add_contest = AddContestForm()
form_add_contest.place.label.text = "Nová soutěž " + round.get_level().in_name() form_add_contest.place.label.text = "Nová soutěž " + round.get_level().in_name()
if add_contest(round, form_add_contest): if add_contest(round, form_add_contest):
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
group_rounds = round.get_group_rounds(True) group_rounds = round.get_group_rounds(True)
group_rounds.sort(key=lambda r: r.round_code()) group_rounds.sort(key=lambda r: r.round_code())
return render_template( return render_template(
'org_round.html', 'org_round.html',
ctx=ctx, rights=rights,
round=round, group_rounds=group_rounds, round=round, group_rounds=group_rounds,
roles=[r.friendly_name() for r in rr.get_roles()], roles=[r.friendly_name() for r in rights.get_roles()],
contests_counts=contests_counts, contests_counts=contests_counts,
tasks=tasks, form_delete_task=form_delete_task, tasks=tasks, form_delete_task=form_delete_task,
form_add_contest=form_add_contest, form_add_contest=form_add_contest,
can_manage_round=can_manage_round,
can_manage_contestants=can_manage_contestants,
can_handle_submits=rr.have_right(Right.view_submits),
can_upload=rr.offer_upload_feedback(),
can_view_statement=rr.can_view_statement(),
can_add_contest=g.gatekeeper.rights_generic().have_right(Right.add_contest),
statement_exists=mo.web.util.task_statement_exists(round), statement_exists=mo.web.util.task_statement_exists(round),
) )
...@@ -267,18 +237,18 @@ class TaskEditForm(FlaskForm): ...@@ -267,18 +237,18 @@ class TaskEditForm(FlaskForm):
self.max_points.widget = NumberInput(min=0, step=points_step) self.max_points.widget = NumberInput(min=0, step=points_step)
@app.route('/org/contest/r/<int:id>/task/new', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/task/new', methods=('GET', 'POST'))
def org_round_task_new(id: int): def org_round_task_new(round_id: int):
sess = db.get_session() sess = db.get_session()
round, master_round, _ = get_round_rr(id, Right.manage_round, True) ctx = get_context(round_id=round_id, right_needed=Right.manage_round)
form = TaskEditForm(master_round.points_step) form = TaskEditForm(ctx.master_round.points_step)
if form.validate_on_submit(): if form.validate_on_submit():
task = db.Task() task = db.Task()
task.round = round task.round = ctx.round
form.populate_obj(task) form.populate_obj(task)
if sess.query(db.Task).filter_by(round_id=id, code=task.code).first(): if sess.query(db.Task).filter_by(round_id=round_id, code=task.code).first():
flash('Úloha se stejným kódem již v tomto kole existuje', 'danger') flash('Úloha se stejným kódem již v tomto kole existuje', 'danger')
else: else:
sess.add(task) sess.add(task)
...@@ -291,28 +261,25 @@ def org_round_task_new(id: int): ...@@ -291,28 +261,25 @@ def org_round_task_new(id: int):
sess.commit() sess.commit()
app.logger.info(f"Úloha {task.code} ({task.task_id}) přidána: {db.row2dict(task)}") app.logger.info(f"Úloha {task.code} ({task.task_id}) přidána: {db.row2dict(task)}")
flash('Nová úloha přidána', 'success') flash('Nová úloha přidána', 'success')
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
return render_template( return render_template(
'org_round_task_edit.html', 'org_round_task_edit.html',
round=round, task=None, form=form, ctx=ctx, form=form,
) )
@app.route('/org/contest/r/<int:id>/task/<int:task_id>/edit', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/edit', methods=('GET', 'POST'))
def org_round_task_edit(id: int, task_id: int): def org_round_task_edit(round_id: int, task_id: int):
sess = db.get_session() sess = db.get_session()
round, master_round, _ = get_round_rr(id, Right.manage_round, True) ctx = get_context(round_id=round_id, task_id=task_id, right_needed=Right.manage_round)
task = ctx.task
task = sess.query(db.Task).get(task_id) assert task
# FIXME: Check contest!
if not task:
raise werkzeug.exceptions.NotFound()
form = TaskEditForm(master_round.points_step, obj=task) form = TaskEditForm(ctx.master_round.points_step, obj=task)
if form.validate_on_submit(): if form.validate_on_submit():
if sess.query(db.Task).filter( if sess.query(db.Task).filter(
db.Task.task_id != task_id, db.Task.round_id == id, db.Task.code == form.code.data db.Task.task_id != task_id, db.Task.round_id == round_id, db.Task.code == form.code.data
).first(): ).first():
flash('Úloha se stejným kódem již v tomto kole existuje', 'danger') flash('Úloha se stejným kódem již v tomto kole existuje', 'danger')
else: else:
...@@ -329,93 +296,16 @@ def org_round_task_edit(id: int, task_id: int): ...@@ -329,93 +296,16 @@ def org_round_task_edit(id: int, task_id: int):
app.logger.info(f"Úloha {task.code} ({task_id}) modifikována, změny: {changes}") app.logger.info(f"Úloha {task.code} ({task_id}) modifikována, změny: {changes}")
flash('Změny úlohy uloženy', 'success') flash('Změny úlohy uloženy', 'success')
else: else:
flash(u'Žádné změny k uložení', 'info') flash('Žádné změny k uložení', 'info')
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round', task_id=None))
return render_template( return render_template(
'org_round_task_edit.html', 'org_round_task_edit.html',
round=round, task=task, form=form, ctx=ctx, form=form,
) )
@app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/download', methods=('GET', 'POST'))
def org_round_task_download(round_id: int, task_id: int):
round, _, _ = get_round_rr(round_id, Right.view_submits, False)
task = get_task(round, task_id)
return generic_batch_download(round=round, contest=None, site=None, task=task)
@app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/upload', methods=('GET', 'POST'))
def org_round_task_upload(round_id: int, task_id: int):
round, _, rr = get_round_rr(round_id, Right.view_submits, False)
task = get_task(round, task_id)
return generic_batch_upload(round=round, contest=None, site=None, task=task,
offer_upload_solutions=rr.offer_upload_solutions(),
offer_upload_feedback=rr.offer_upload_feedback())
@app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/batch-points', methods=('GET', 'POST'))
def org_round_task_batch_points(round_id: int, task_id: int):
round, _, _ = get_round_rr(round_id, Right.edit_points, True)
task = get_task(round, task_id)
return generic_batch_points(round=round, contest=None, task=task)
@app.route('/org/contest/r/<int:id>/list', methods=('GET', 'POST'))
@app.route('/org/contest/r/<int:id>/list/emails', endpoint="org_round_list_emails")
def org_round_list(id: int):
round, master_round, rr = get_round_rr(id, Right.view_contestants, True)
can_edit = rr.have_right(Right.manage_round) and request.endpoint != 'org_round_list_emails'
format = request.args.get('format', "")
filter = ParticipantsFilterForm(formdata=request.args)
filter.validate()
query = get_contestants_query(
round=master_round,
school=filter.school.place,
contest_place=filter.contest_place.place,
participation_place=filter.participation_place.place,
participation_state=mo.util.star_is_none(filter.participation_state.data),
)
action_form = None
if can_edit:
action_form = ParticipantsActionForm()
if action_form.do_action(round=master_round, query=query):
# Action happened, redirect
return redirect(request.url)
if format == "":
table = None
emails = None
mailto_link = None
if request.endpoint == 'org_round_list_emails':
(emails, mailto_link) = get_contestant_emails(query,
mailto_subject=f'{round.name} kategorie {round.category}')
count = len(emails)
else:
(count, query) = filter.apply_limits(query, pagesize=50)
# count = db.get_count(query)
table = make_contestant_table(query, round, add_contest_column=True, add_checkbox=True)
return render_template(
'org_round_list.html',
round=round,
table=table, emails=emails, mailto_link=mailto_link,
filter=filter, count=count, action_form=action_form,
)
else:
table = make_contestant_table(query, round, is_export=True)
return table.send_as(format)
@app.route('/org/contest/r/<int:id>/import', methods=('GET', 'POST'))
def org_round_import(id: int):
round, master_round, rr = get_round_rr(id, Right.manage_contest, True)
return generic_import(round, master_round, None, None)
class RoundEditForm(FlaskForm): class RoundEditForm(FlaskForm):
_for_round: Optional[db.Round] = None _for_round: Optional[db.Round] = None
...@@ -469,10 +359,11 @@ class RoundEditForm(FlaskForm): ...@@ -469,10 +359,11 @@ class RoundEditForm(FlaskForm):
self.abstract_validate_time_order(field) self.abstract_validate_time_order(field)
@app.route('/org/contest/r/<int:id>/edit', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/edit', methods=('GET', 'POST'))
def org_round_edit(id: int): def org_round_edit(round_id: int):
sess = db.get_session() sess = db.get_session()
round, _, rr = get_round_rr(id, Right.manage_round, True) ctx = get_context(round_id=round_id, right_needed=Right.manage_round)
round = ctx.round
form = RoundEditForm(obj=round) form = RoundEditForm(obj=round)
form._for_round = round form._for_round = round
...@@ -490,10 +381,10 @@ def org_round_edit(id: int): ...@@ -490,10 +381,10 @@ def org_round_edit(id: int):
if sess.is_modified(round): if sess.is_modified(round):
changes = db.get_object_changes(round) changes = db.get_object_changes(round)
app.logger.info(f"Round #{id} modified, changes: {changes}") app.logger.info(f"Round #{round_id} modified, changes: {changes}")
mo.util.log( mo.util.log(
type=db.LogType.round, type=db.LogType.round,
what=id, what=round_id,
details={'action': 'edit', 'changes': changes}, details={'action': 'edit', 'changes': changes},
) )
...@@ -512,26 +403,27 @@ def org_round_edit(id: int): ...@@ -512,26 +403,27 @@ def org_round_edit(id: int):
sess.commit() sess.commit()
flash('Změny kola uloženy', 'success') flash('Změny kola uloženy', 'success')
else: else:
flash(u'Žádné změny k uložení', 'info') flash('Žádné změny k uložení', 'info')
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
return render_template( return render_template(
'org_round_edit.html', 'org_round_edit.html',
ctx=ctx,
round=round, round=round,
form=form, form=form,
) )
@app.route('/org/contest/r/<int:id>/task-statement/zadani.pdf') @app.route('/org/contest/r/<int:round_id>/task-statement/zadani.pdf')
def org_task_statement(id: int): def org_task_statement(round_id: int):
round, _, rr = get_round_rr(id, None, True) ctx = get_context(round_id=round_id)
if not rr.can_view_statement(): if not ctx.rights.can_view_statement():
app.logger.warn(f'Organizátor #{g.user.user_id} chce zadání, na které nemá právo') app.logger.warn(f'Organizátor #{g.user.user_id} chce zadání, na které nemá právo')
raise werkzeug.exceptions.Forbidden() raise werkzeug.exceptions.Forbidden()
return mo.web.util.send_task_statement(round) return mo.web.util.send_task_statement(ctx.round)
class StatementEditForm(FlaskForm): class StatementEditForm(FlaskForm):
...@@ -540,18 +432,19 @@ class StatementEditForm(FlaskForm): ...@@ -540,18 +432,19 @@ class StatementEditForm(FlaskForm):
delete = wtforms.SubmitField('Smazat') delete = wtforms.SubmitField('Smazat')
@app.route('/org/contest/r/<int:id>/task-statement/edit', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/task-statement/edit', methods=('GET', 'POST'))
def org_edit_statement(id: int): def org_edit_statement(round_id: int):
sess = db.get_session() sess = db.get_session()
round, _, rr = get_round_rr(id, Right.manage_round, True) ctx = get_context(round_id=round_id, right_needed=Right.manage_round)
round = ctx.round
def log_changes(): def log_changes():
if sess.is_modified(round): if sess.is_modified(round):
changes = db.get_object_changes(round) changes = db.get_object_changes(round)
app.logger.info(f"Kolo #{id} změněno, změny: {changes}") app.logger.info(f"Kolo #{round_id} změněno, změny: {changes}")
mo.util.log( mo.util.log(
type=db.LogType.round, type=db.LogType.round,
what=id, what=round_id,
details={'action': 'edit', 'changes': changes}, details={'action': 'edit', 'changes': changes},
) )
...@@ -572,7 +465,7 @@ def org_edit_statement(id: int): ...@@ -572,7 +465,7 @@ def org_edit_statement(id: int):
log_changes() log_changes()
sess.commit() sess.commit()
flash('Zadání nahráno', 'success') flash('Zadání nahráno', 'success')
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
else: else:
flash('Vyberte si prosím soubor', 'danger') flash('Vyberte si prosím soubor', 'danger')
if form.delete.data: if form.delete.data:
...@@ -580,10 +473,11 @@ def org_edit_statement(id: int): ...@@ -580,10 +473,11 @@ def org_edit_statement(id: int):
log_changes() log_changes()
sess.commit() sess.commit()
flash('Zadání smazáno', 'success') flash('Zadání smazáno', 'success')
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
return render_template( return render_template(
'org_edit_statement.html', 'org_edit_statement.html',
ctx=ctx,
round=round, round=round,
form=form, form=form,
) )
...@@ -605,38 +499,39 @@ class MessageRemoveForm(FlaskForm): ...@@ -605,38 +499,39 @@ class MessageRemoveForm(FlaskForm):
message_remove = wtforms.SubmitField() message_remove = wtforms.SubmitField()
@app.route('/org/contest/r/<int:id>/messages/', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/messages/', methods=('GET', 'POST'))
def org_round_messages(id: int): def org_round_messages(round_id: int):
sess = db.get_session() sess = db.get_session()
round, _, rr = get_round_rr(id, None, True) ctx = get_context(round_id=round_id)
round = ctx.round
if not round.has_messages: if not round.has_messages:
flash('Toto kolo nemá aktivní zprávičky pro účastníky, aktivujte je v nastavení kola', 'warning') flash('Toto kolo nemá aktivní zprávičky pro účastníky, aktivujte je v nastavení kola', 'warning')
return redirect(url_for('org_round', id=id)) return redirect(ctx.url_for('org_round'))
messages = sess.query(db.Message).filter_by(round_id=id).order_by(db.Message.created_at).all() messages = sess.query(db.Message).filter_by(round_id=round_id).order_by(db.Message.created_at).all()
add_form: Optional[MessageAddForm] = None add_form: Optional[MessageAddForm] = None
remove_form: Optional[MessageRemoveForm] = None remove_form: Optional[MessageRemoveForm] = None
preview: Optional[db.Message] = None preview: Optional[db.Message] = None
if rr.have_right(Right.manage_round): if ctx.rights.have_right(Right.manage_round):
add_form = MessageAddForm() add_form = MessageAddForm()
remove_form = MessageRemoveForm() remove_form = MessageRemoveForm()
if remove_form.validate_on_submit() and remove_form.message_remove.data: if remove_form.validate_on_submit() and remove_form.message_remove.data:
msg = sess.query(db.Message).get(remove_form.message_id.data) msg = sess.query(db.Message).get(remove_form.message_id.data)
if not msg or msg.round_id != id: if not msg or msg.round_id != round_id:
raise werkzeug.exceptions.NotFound() raise werkzeug.exceptions.NotFound()
sess.delete(msg) sess.delete(msg)
sess.commit() sess.commit()
app.logger.info(f"Zprávička pro kolo {id} odstraněna: {db.row2dict(msg)}") app.logger.info(f"Zprávička pro kolo {round_id} odstraněna: {db.row2dict(msg)}")
flash('Zprávička odstraněna', 'success') flash('Zprávička odstraněna', 'success')
return redirect(url_for('org_round_messages', id=id)) return redirect(ctx.url_for('org_round_messages'))
if add_form.validate_on_submit(): if add_form.validate_on_submit():
msg = db.Message( msg = db.Message(
round_id=id, round_id=round_id,
created_by=g.user.user_id, created_by=g.user.user_id,
created_at=mo.now, created_at=mo.now,
) )
...@@ -651,14 +546,15 @@ def org_round_messages(id: int): ...@@ -651,14 +546,15 @@ def org_round_messages(id: int):
elif add_form.submit.data: elif add_form.submit.data:
sess.add(msg) sess.add(msg)
sess.commit() sess.commit()
app.logger.info(f"Vložena nová zprávička pro kolo {id}: {db.row2dict(msg)}") app.logger.info(f"Vložena nová zprávička pro kolo {round_id}: {db.row2dict(msg)}")
flash('Zprávička úspěšně vložena', 'success') flash('Zprávička úspěšně vložena', 'success')
return redirect(url_for('org_round_messages', id=id)) return redirect(ctx.url_for('org_round_messages'))
return render_template( return render_template(
'org_round_messages.html', 'org_round_messages.html',
round=round, rr=rr, messages=messages, ctx=ctx,
round=round, messages=messages,
add_form=add_form, remove_form=remove_form, add_form=add_form, remove_form=remove_form,
preview=preview, preview=preview,
) )
...@@ -9,6 +9,7 @@ import mo.db as db ...@@ -9,6 +9,7 @@ import mo.db as db
from mo.rights import Right from mo.rights import Right
from mo.score import Score from mo.score import Score
from mo.web import app from mo.web import app
from mo.web.org_contest import get_context
from mo.web.table import Cell, CellLink, Column, Row, Table, cell_pion_link from mo.web.table import Cell, CellLink, Column, Row, Table, cell_pion_link
from mo.util_format import format_decimal from mo.util_format import format_decimal
...@@ -74,33 +75,17 @@ class SolPointsCell(Cell): ...@@ -74,33 +75,17 @@ class SolPointsCell(Cell):
@app.route('/org/contest/r/<int:round_id>/score') @app.route('/org/contest/r/<int:round_id>/score')
@app.route('/org/contest/c/<int:contest_id>/score') @app.route('/org/contest/c/<int:ct_id>/score')
def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None): def org_score(round_id: Optional[int] = None, ct_id: Optional[int] = None):
if round_id is None and contest_id is None: ctx = get_context(round_id=round_id, ct_id=ct_id)
raise werkzeug.exceptions.BadRequest() contest = ctx.contest
if round_id is not None and contest_id is not None: round = ctx.round
raise werkzeug.exceptions.BadRequest()
format = request.args.get('format', "") format = request.args.get('format', "")
sess = db.get_session() sess = db.get_session()
if round_id:
contest = None # FIXME
round = sess.query(db.Round).options( if not ctx.rights.have_right(Right.view_submits):
joinedload(db.Round.master)
).get(round_id)
if not round:
raise werkzeug.exceptions.NotFound()
rr = g.gatekeeper.rights_for_round(round, True)
else:
contest = sess.query(db.Contest).options(
joinedload(db.Contest.round).joinedload(db.Round.master)
).get(contest_id)
if not contest:
raise werkzeug.exceptions.NotFound()
round = contest.round
rr = g.gatekeeper.rights_for_contest(contest)
if not rr.have_right(Right.view_submits):
raise werkzeug.exceptions.Forbidden() raise werkzeug.exceptions.Forbidden()
score = Score(round.master, contest) score = Score(round.master, contest)
...@@ -127,7 +112,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None): ...@@ -127,7 +112,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
columns.append(Column(key='participant', name='ucastnik', title='Účastník')) columns.append(Column(key='participant', name='ucastnik', title='Účastník'))
if is_export: if is_export:
columns.append(Column(key='email', name='email')) columns.append(Column(key='email', name='email'))
if not contest_id: if not ct_id:
columns.append(Column(key='contest', name='oblast', title=round.get_level().name.title())) columns.append(Column(key='contest', name='oblast', title=round.get_level().name.title()))
if is_export: if is_export:
columns.append(Column(key='pion_place', name='soutezni_misto')) columns.append(Column(key='pion_place', name='soutezni_misto'))
...@@ -140,12 +125,12 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None): ...@@ -140,12 +125,12 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
if contest: if contest:
local_ct_id = subcontest_id_map[(task.round_id, contest.master_contest_id)] local_ct_id = subcontest_id_map[(task.round_id, contest.master_contest_id)]
title = '<a href="{}">{}</a>'.format( title = '<a href="{}">{}</a>'.format(
url_for('org_contest_task', contest_id=local_ct_id, task_id=task.task_id), url_for('org_contest_task', ct_id=local_ct_id, task_id=task.task_id),
task.code task.code
) )
if rr.can_edit_points(): if ctx.rights.can_edit_points():
title += ' <a href="{}" title="Editovat body" class="icon">✎</a>'.format( title += ' <a href="{}" title="Editovat body" class="icon">✎</a>'.format(
url_for('org_contest_task_points', contest_id=local_ct_id, task_id=task.task_id), url_for('org_contest_task_points', ct_id=local_ct_id, task_id=task.task_id),
) )
columns.append(Column(key=f'task_{task.task_id}', name=task.code, title=title)) 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')) columns.append(Column(key='total_points', name='celkove_body', title='Celkové body'))
...@@ -177,7 +162,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None): ...@@ -177,7 +162,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
'user': user, 'user': user,
'email': user.email, 'email': user.email,
'participant': cell_pion_link(user, local_pion_ct_id, user.full_name()), 'participant': cell_pion_link(user, local_pion_ct_id, user.full_name()),
'contest': CellLink(pion.contest.place.name or "?", url_for('org_contest', id=pion.contest_id)), 'contest': CellLink(pion.contest.place.name or "?", url_for('org_contest', ct_id=pion.contest_id)),
'pion_place': pion.place.name, 'pion_place': pion.place.name,
'school': CellLink(school.name or "?", url_for('org_place', id=school.place_id)), 'school': CellLink(school.name or "?", url_for('org_place', id=school.place_id)),
'grade': pant.grade, 'grade': pant.grade,
...@@ -214,6 +199,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None): ...@@ -214,6 +199,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
if format == "": if format == "":
return render_template( return render_template(
'org_score.html', 'org_score.html',
ctx=ctx,
contest=contest, round=round, tasks=tasks, contest=contest, round=round, tasks=tasks,
table=table, messages=messages, table=table, messages=messages,
group_rounds=group_rounds, group_rounds=group_rounds,
......
...@@ -210,7 +210,7 @@ def cell_user_link(user: db.User, text: str) -> CellLink: ...@@ -210,7 +210,7 @@ def cell_user_link(user: db.User, text: str) -> CellLink:
def cell_pion_link(user: db.User, contest_id: int, text: str) -> CellLink: def cell_pion_link(user: db.User, contest_id: int, text: str) -> CellLink:
return CellLink(text, url_for('org_contest_user', contest_id=contest_id, user_id=user.user_id)) return CellLink(text, url_for('org_contest_user', ct_id=contest_id, user_id=user.user_id))
def cell_place_link(place: db.Place, text: str) -> CellLink: def cell_place_link(place: db.Place, text: str) -> CellLink:
......
...@@ -2,13 +2,17 @@ ...@@ -2,13 +2,17 @@
{% set round = contest.round %} {% set round = contest.round %}
{% set state = contest.state %} {% set state = contest.state %}
{% set ct_state = contest.ct_state() %} {% set ct_state = contest.ct_state() %}
{% set site_id = site.place_id if site else None %} {% set can_manage = rights.have_right(Right.manage_contest) %}
{% set can_upload = rights.can_upload_feedback() %}
{% set can_edit_points = rights.can_edit_points() %}
{% set can_create_solutions = rights.can_upload_feedback() or rights.can_upload_solutions() %}
{% set can_view_statement = rights.can_view_statement() %}
{% block title %} {% block title %}
{{ round.round_code() }}: {% if site %}soutěžní místo {{ site.name }}{% else %}{{ contest.place.name }}{% endif %} {{ round.round_code() }}: {% if site %}soutěžní místo {{ site.name }}{% else %}{{ contest.place.name }}{% endif %}
{% endblock %} {% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site) }} {{ ctx.breadcrumbs() }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -27,7 +31,7 @@ ...@@ -27,7 +31,7 @@
{% if group_contests|length > 1 %} {% if group_contests|length > 1 %}
<tr><td>Soutěže ve skupině kol:<td> <tr><td>Soutěže ve skupině kol:<td>
{% for c in group_contests %} {% for c in group_contests %}
{% if c == contest %}<i>{% else %}<a href="{{ url_for('org_contest', id=c.contest_id) }}">{% endif %} {% if c == contest %}<i>{% else %}<a href="{{ url_for('org_contest', ct_id=c.contest_id) }}">{% endif %}
{{ c.round.round_code() }}: {% if site %}soutěžní místo {{ site.name }}{% else %}{{ contest.place.name }}{% endif %} {{ c.round.round_code() }}: {% if site %}soutěžní místo {{ site.name }}{% else %}{{ contest.place.name }}{% endif %}
{% if c == contest %} (tato soutěž)</i>{% else %}</a>{% endif %}<br> {% if c == contest %} (tato soutěž)</i>{% else %}</a>{% endif %}<br>
{% endfor %} {% endfor %}
...@@ -35,7 +39,7 @@ ...@@ -35,7 +39,7 @@
<tr><td>Zadání<td> <tr><td>Zadání<td>
{% if round.tasks_file %} {% if round.tasks_file %}
{% if can_view_statement %} {% if can_view_statement %}
<a href='{{ url_for('org_task_statement', id=round.round_id) }}'>stáhnout</a> <a href='{{ ctx.url_for('org_task_statement', ct_id=None) }}'>stáhnout</a>
{% else %} {% else %}
není dostupné není dostupné
{% endif %} {% endif %}
...@@ -45,28 +49,28 @@ ...@@ -45,28 +49,28 @@
</table> </table>
<div class="btn-group"> <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> <a class="btn btn-primary" href='{{ ctx.url_for('org_generic_list') }}'>Seznam účastníků</a>
{% if state != RoundState.preparing %} {% if 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> <a class="btn btn-primary" href='{{ ctx.url_for('org_contest_solutions') }}'>Odevzdaná řešení</a>
{% endif %} {% endif %}
{% if can_manage and site %} {% if can_manage and site %}
<a class="btn btn-default" href="{{ url_for('org_contest_add_user', id=contest.contest_id, site_id=site_id) }}">Přidat účastníka</a> <a class="btn btn-default" href="{{ ctx.url_for('org_contest_add_user') }}">Přidat účastníka</a>
{% endif %} {% endif %}
{% if not site %} {% if not site %}
{% if state in [RoundState.grading, RoundState.closed] %} {% if state in [RoundState.grading, RoundState.closed] %}
<a class="btn btn-primary" href='{{ url_for('org_score', contest_id=contest.contest_id) }}'>Výsledky</a> <a class="btn btn-primary" href='{{ ctx.url_for('org_score') }}'>Výsledky</a>
{% endif %} {% endif %}
{% if state == RoundState.preparing and round.seq > 1 %} {% if 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> <a class="btn btn-primary" href='{{ ctx.url_for('org_contest_advance') }}'>Postup z minulého kola</a>
{% endif %} {% endif %}
{% if can_manage %} {% if can_manage %}
<a class="btn btn-default" href='{{ url_for('org_contest_import', id=contest.contest_id) }}'>Importovat data</a> <a class="btn btn-default" href='{{ ctx.url_for('org_generic_import') }}'>Importovat data</a>
{% endif %} {% endif %}
{% if can_manage and not site %} {% if can_manage and not site %}
<a class="btn btn-default" href='{{ url_for('org_contest_edit', id=contest.contest_id) }}'>Nastavení</a> <a class="btn btn-default" href='{{ ctx.url_for('org_contest_edit') }}'>Nastavení</a>
{% endif %} {% endif %}
{% if g.user.is_admin %} {% if g.user.is_admin %}
<a class="btn btn-default" href="{{ log_url('contest', contest.contest_id) }}">Historie</a> <a class="btn btn-default" href="{{ log_url('contest', ctx.ct_id) }}">Historie</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
...@@ -80,12 +84,12 @@ ...@@ -80,12 +84,12 @@
</thead> </thead>
{% for (place, count) in places_counts %} {% for (place, count) in places_counts %}
<tr> <tr>
<td><a href="{{ url_for('org_contest', id=contest.contest_id, site_id=place.place_id) }}">{{ place.name }}</a> <td><a href="{{ ctx.url_for('org_contest', site_id=place.place_id) }}">{{ place.name }}</a>
<td>{{ count }} <td>{{ count }}
<td><div class="btn-group"> <td><div class="btn-group">
<a class="btn btn-xs btn-primary" href="{{ url_for('org_contest', id=contest.contest_id, site_id=place.place_id) }}">Detail</a> <a class="btn btn-xs btn-primary" href="{{ ctx.url_for('org_contest', site_id=place.place_id) }}">Detail</a>
{% if can_manage %} {% if can_manage %}
<a class="btn btn-xs btn-default" href="{{ url_for('org_contest_add_user', id=contest.contest_id, site_id=place.place_id) }}">Přidat účastníka</a> <a class="btn btn-xs btn-default" href="{{ ctx.url_for('org_contest_add_user', site_id=place.place_id) }}">Přidat účastníka</a>
</div> </div>
{% endif %} {% endif %}
</tr> </tr>
...@@ -104,7 +108,7 @@ ...@@ -104,7 +108,7 @@
{% endif %} {% endif %}
<div class="btn-group"> <div class="btn-group">
{% if can_manage and not site %} {% if can_manage and not site %}
<a class="btn btn-default" href='{{ url_for('org_contest_add_user', id=contest.contest_id) }}'>Přidat účastníka</a> <a class="btn btn-default" href='{{ ctx.url_for('org_contest_add_user') }}'>Přidat účastníka</a>
{% endif %} {% endif %}
</div> </div>
...@@ -128,21 +132,21 @@ ...@@ -128,21 +132,21 @@
<td>{{ task.sol_count }} <td>{{ task.sol_count }}
<td>{{ task.max_points|decimal|none_value('–') }} <td>{{ task.max_points|decimal|none_value('–') }}
<td><div class="btn-group"> <td><div class="btn-group">
<a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Odevzdaná řešení</a> <a class="btn btn-xs btn-primary" href="{{ ctx.url_for('org_contest_task', task_id=task.task_id) }}">Odevzdaná řešení</a>
{% if not site and can_edit_points %} {% if not site and can_edit_points %}
<a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Zadat body</a> <a class="btn btn-xs btn-default" href="{{ ctx.url_for('org_contest_task_points', task_id=task.task_id) }}">Zadat body</a>
{% endif %} {% endif %}
{% if can_create_solutions %} {% if can_create_solutions %}
<a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_create', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Založit řešení</a> <a class="btn btn-xs btn-default" href="{{ ctx.url_for('org_contest_task_create', task_id=task.task_id) }}">Založit řešení</a>
{% endif %} {% endif %}
</div> </div>
<td><div class="btn-group"> <td><div class="btn-group">
<a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Stáhnout ZIP</a> <a class="btn btn-xs btn-primary" href="{{ ctx.url_for('org_generic_batch_download', task_id=task.task_id) }}">Stáhnout ZIP</a>
{% if can_upload %} {% if can_upload %}
<a class='btn btn-xs btn-default' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Nahrát ZIP</a> <a class='btn btn-xs btn-default' href="{{ ctx.url_for('org_generic_batch_upload', task_id=task.task_id) }}">Nahrát ZIP</a>
{% endif %} {% endif %}
{% if not site and can_edit_points %} {% if not site and can_edit_points %}
<a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_batch_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Nahrát body</a> <a class="btn btn-xs btn-default" href="{{ ctx.url_for('org_generic_batch_points', task_id=task.task_id) }}">Nahrát body</a>
{% endif %} {% endif %}
</div> </div>
</tr> </tr>
...@@ -156,8 +160,8 @@ ...@@ -156,8 +160,8 @@
Práva k {% if site %}soutěžními místu{% else %}soutěži{% endif %}: Práva k {% if site %}soutěžními místu{% else %}soutěži{% endif %}:
{% if g.user.is_admin %} {% if g.user.is_admin %}
admin admin
{% elif rights %} {% elif rights_list %}
{% for r in rights %} {% for r in rights_list %}
{{ r.name }} {{ r.name }}
{% endfor %} {% endfor %}
{% else %} {% else %}
......
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% set round = contest.round %}
{% block title %} {% block title %}
{{ round.round_code() }}: Přidat účastníka {% if site %}do soutěžního místa {{ site.name }}{% else %}do oblasti {{ contest.place.name }}{% endif %} {{ round.round_code() }}: Přidat účastníka {% if site %}do soutěžního místa {{ site.name }}{% else %}do oblasti {{ contest.place.name }}{% endif %}
{% endblock %} {% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Přidat účastníka") }} {{ ctx.breadcrumbs(action="Přidat účastníka") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% block title %}Postup z {{ prev_round.round_code() }} ({{ prev_round.name }}) do {{ round.round_code() }} ({{ round.name }}){% endblock %} {% block title %}Postup z {{ prev_round.round_code() }} ({{ prev_round.name }}) do {{ round.round_code() }} ({{ round.name }}){% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, action="Postup") }} {{ ctx.breadcrumbs(action="Postup") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -25,10 +25,10 @@ ...@@ -25,10 +25,10 @@
<tbody> <tbody>
{% for c in prev_contests %} {% for c in prev_contests %}
<tr> <tr>
<td><a href='{{ url_for('org_contest', id=c.contest_id) }}'>{{ c.place.name }}</a> <td><a href='{{ url_for('org_contest', ct_id=c.contest_id) }}'>{{ c.place.name }}</a>
<td>{{ accept_by_place_id[c.place.place_id] }} <td>{{ accept_by_place_id[c.place.place_id] }}
<td>{{ reject_by_place_id[c.place.place_id] }} <td>{{ reject_by_place_id[c.place.place_id] }}
<td><a class='btn btn-warning btn-xs' href='{{ url_for('org_score', contest_id=c.contest_id) }}'>Výsledková listina</a> <td><a class='btn btn-warning btn-xs' href='{{ url_for('org_score', ct_id=c.contest_id) }}'>Výsledková listina</a>
{% endfor %} {% endfor %}
<tfoot> <tfoot>
<tr> <tr>
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% block title %}Editace soutěže {{ round.round_code() }}: {{ contest.place.name }}{% endblock %} {% block title %}Editace soutěže {{ round.round_code() }}: {{ contest.place.name }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, action="Editace") }} {{ ctx.breadcrumbs(action="Editace") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% set round = contest.round %}
{% block title %}
Seznam účastníků {% if site %}v soutěžním místě {{ site.name }}{% else %}{{ contest.place.name_locative() }}{% endif %}
{% endblock %}
{% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Seznam účastníků" if table else "E-maily", table=False if table else True) }}
{% endblock %}
{% set id = contest.contest_id %}
{% set site_id = site.place_id if site else None %}
{% block body %}
<div class="form-frame">
<form action="" method="GET" class="form form-inline" role="form">
<div class="form-row">
{% if not site %}
{{ wtf.form_field(filter.participation_place, size=8) }}
{% endif %}
{{ wtf.form_field(filter.school, size=8) }}
{{ wtf.form_field(filter.participation_state) }}
<div class="btn-group">
{{ wtf.form_field(filter.submit, class='btn btn-primary') }}
{% if table %}
<button class="btn btn-default" name="format" value="cs_csv" title="Stáhnout celý výsledek v CSV">↓ CSV</button>
<button class="btn btn-default" name="format" value="tsv" title="Stáhnout celý výsledek v TSV">↓ TSV</button>
{% endif %}
</div>
{% if not site %}
</div>
<div class="form-row" style="margin-top: 5px;">
{% endif %}
Celkem <b>{{count|inflected('nalezený účastník', 'nalezení účastníci', 'nalezených účastníků')}}</b>.
</div>
</form>
</div>
{% if table %}
{% include 'parts/org_participants_table_actions.html' %}
{% else %}
{% include 'parts/org_participants_emails.html' %}
{% endif %}
{% endblock %}
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% set round = contest.round %} {% set round = contest.round %}
{% set site_id = site.place_id if site else None %}
{% block title %} {% block title %}
{{ "Založení řešení" if edit_form else "Tabulka řešení" }} {% if site %}soutěžního místa {{ site.name }}{% else %}{{ contest.place.name_locative() }}{% endif %} {{ "Založení řešení" if edit_form else "Tabulka řešení" }} {% if site %}soutěžního místa {{ site.name }}{% else %}{{ contest.place.name_locative() }}{% endif %}
{% endblock %} {% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Založení řešení" if edit_form else "Tabulka řešení") }} {{ ctx.breadcrumbs(action="Založení řešení" if edit_form else "Tabulka řešení") }}
{% endblock %} {% endblock %}
{% block pretitle %} {% block pretitle %}
{% if contest.state in [RoundState.grading, RoundState.closed] %} {% if contest.state in [RoundState.grading, RoundState.closed] %}
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-default" href="{{ url_for('org_score', contest_id=contest.contest_id) }}">Výsledky {{ round.get_level().name_genitive() }}</a> <a class="btn btn-default" href="{{ ctx.url_for('org_score') }}">Výsledky {{ round.get_level().name_genitive() }}</a>
<a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a> <a class="btn btn-default" href="{{ ctx.url_for('org_score', ct_id=None) }}">Výsledky kola</a>
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
...@@ -48,9 +47,9 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje ...@@ -48,9 +47,9 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
<th rowspan=2>Účastník <th rowspan=2>Účastník
<th rowspan=2>Stav účasti</th> <th rowspan=2>Stav účasti</th>
{% for task in tasks %}<th colspan=4> {% for task in tasks %}<th colspan=4>
<a href="{{ url_for('org_contest_task', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">{{ task.code }}</a> <a href="{{ ctx.url_for('org_contest_task', task_id=task.task_id) }}">{{ task.code }}</a>
{% if sc.allow_edit_points %} {% if rights.can_edit_points() %}
<a title="Editovat body" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}" class="icon pull-right"></a> <a title="Editovat body" href="{{ ctx.url_for('org_contest_task_points', task_id=task.task_id) }}" class="icon pull-right"></a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<th rowspan=2>Body celkem <th rowspan=2>Body celkem
...@@ -61,7 +60,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje ...@@ -61,7 +60,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
</thead> </thead>
{% for pion in pions %} {% for pion in pions %}
{% set u = pion.user %} {% set u = pion.user %}
<tr class="state-{{ pion.state.name }}> <tr class="state-{{ pion.state.name }}">
<th>{{ u|pion_link(contest.contest_id) }} <th>{{ u|pion_link(contest.contest_id) }}
<td>{{ pion.state.friendly_name() }} <td>{{ pion.state.friendly_name() }}
{% set sum_points = [] %} {% set sum_points = [] %}
...@@ -109,7 +108,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje ...@@ -109,7 +108,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
{% else %}–{% endif %} {% else %}–{% endif %}
<td> <td>
{% endif %} {% endif %}
<a class="btn btn-xs btn-link icon" title="Detail řešení" href="{{ url_for('org_submit_list', contest_id=contest.contest_id, user_id=u.user_id, task_id=task.task_id, site_id=site_id) }}">🔍</a> <a class="btn btn-xs btn-link icon" title="Detail řešení" href="{{ ctx.url_for('org_submit_list', user_id=u.user_id, task_id=task.task_id) }}">🔍</a>
{% endfor %} {% endfor %}
<th>{{ sum_points|sum|decimal }}</th> <th>{{ sum_points|sum|decimal }}</th>
</tr> </tr>
...@@ -118,9 +117,9 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje ...@@ -118,9 +117,9 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
<tr><td><td> <tr><td><td>
{% for task in tasks %} {% for task in tasks %}
<td colspan=4><div class='btn-group'> <td colspan=4><div class='btn-group'>
<a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">Stáhnout</a> <a class='btn btn-xs btn-primary' href="{{ ctx.url_for('org_generic_batch_download', task_id=task.task_id) }}">Stáhnout</a>
{% if sc.allow_upload_feedback %} {% if rights.can_upload_feedback() %}
<a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">Nahrát</a> <a class='btn btn-xs btn-primary' href="{{ ctx.url_for('org_generic_batch_upload', task_id=task.task_id) }}">Nahrát</a>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
...@@ -130,13 +129,13 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje ...@@ -130,13 +129,13 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
{% if edit_form %} {% if edit_form %}
<div class='btn-group'> <div class='btn-group'>
{{ wtf.form_field(edit_form.submit, class="btn btn-primary") }} {{ wtf.form_field(edit_form.submit, class="btn btn-primary") }}
<a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=contest.contest_id, site_id=site_id) }}">Zrušit</a> <a class="btn btn-default" href="{{ ctx.url_for('org_contest_solutions') }}">Zrušit</a>
</div> </div>
</form> </form>
{% else %} {% else %}
<div class='btn-group'> <div class='btn-group'>
{% if sc.allow_create_solutions %} {% if rights.can_create_solutions() %}
<a class="btn btn-primary" href="{{ url_for('org_contest_solutions_edit', id=contest.contest_id, site_id=site_id) }}">Založit řešení</a> <a class="btn btn-primary" href="{{ ctx.url_for('org_contest_solutions_edit') }}">Založit řešení</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
......
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% set contest = sc.contest %} {% set allow_edit_points=rights.can_edit_points() %}
{% set ct_id = contest.contest_id %} {% set allow_upload_solutions=rights.can_upload_solutions() %}
{% set round = sc.round %} {% set allow_upload_feedback=rights.can_upload_feedback() %}
{% set site = sc.site %}
{% set site_id = site.place_id if site else None %}
{% set task = sc.task %}
{% block title %}{{ "Zadávání bodů" if points_form else "Založení řešení" if create_form else "Odevzdaná řešení" }} úlohy {{ task.code }} {{ task.name }}{% endblock %} {% block title %}{{ "Zadávání bodů" if points_form else "Založení řešení" if create_form else "Odevzdaná řešení" }} úlohy {{ ctx.task.code }} {{ ctx.task.name }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Zadávání bodů" if points_form else "Založení řešení" if create_form else None) }} {{ ctx.breadcrumbs(action="Zadávání bodů" if points_form else "Založení řešení" if create_form else None) }}
{% endblock %} {% endblock %}
{% block pretitle %} {% block pretitle %}
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=ct_id, site_id=site_id) }}">Všechny úlohy</a> <a class="btn btn-default" href="{{ ctx.url_for('org_contest_solutions', task_id=None) }}">Všechny úlohy</a>
{% if contest.state in [RoundState.grading, RoundState.closed] %} {% if ctx.contest.state in [RoundState.grading, RoundState.closed] %}
<a class="btn btn-default" href="{{ url_for('org_score', contest_id=ct_id) }}">Výsledky {{ round.get_level().name_genitive() }}</a> <a class="btn btn-default" href="{{ ctx.url_for('org_score', task_id=None) }}">Výsledky {{ ctx.round.get_level().name_genitive() }}</a>
<a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a> <a class="btn btn-default" href="{{ ctx.url_for('org_score', ct_id=None, task_id=None) }}">Výsledky kola</a>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}
...@@ -29,26 +26,26 @@ ...@@ -29,26 +26,26 @@
<form class="form" method="POST"> <form class="form" method="POST">
{{ form.csrf_token }} {{ form.csrf_token }}
{% endif %} {% endif %}
{% with for_user=None, for_task=task, rows=rows %} {% with for_user=None, for_task=ctx.task, rows=rows %}
{% include "parts/org_solution_table.html" %} {% include "parts/org_solution_table.html" %}
{% endwith %} {% endwith %}
{% if form %} {% if form %}
<div class='btn-group'> <div class='btn-group'>
{{ wtf.form_field(form.submit, class="btn btn-primary" ) }} {{ wtf.form_field(form.submit, class="btn btn-primary" ) }}
<a class="btn btn-default" href="{{ url_for('org_contest_task', contest_id=ct_id, task_id=task.task_id, site_id=site_id) }}">Zrušit</a> <a class="btn btn-default" href="{{ ctx.url_for('org_contest_task') }}">Zrušit</a>
</div> </div>
</form> </form>
{% else %} {% else %}
<div class='btn-group'> <div class='btn-group'>
<a class='btn btn-primary' href="{{ url_for('org_contest_task_download', contest_id=ct_id, site_id=site_id, task_id=task.task_id) }}">Stáhnout řešení</a> <a class='btn btn-primary' href="{{ ctx.url_for('org_generic_batch_download') }}">Stáhnout řešení</a>
{% if sc.allow_upload_feedback %} {% if allow_upload_feedback %}
<a class='btn btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=ct_id, site_id=site_id, task_id=task.task_id) }}">Nahrát opravená řešení</a> <a class='btn btn-primary' href="{{ ctx.url_for('org_generic_batch_upload') }}">Nahrát opravená řešení</a>
{% endif %} {% endif %}
{% if sc.allow_create_solutions %} {% if allow_create_solutions %}
<a class="btn btn-primary" href="{{ url_for('org_contest_task_create', contest_id=ct_id, task_id=task.task_id, site_id=site_id) }}">Založit řešení</a> <a class="btn btn-primary" href="{{ ctx.url_for('org_contest_task_create') }}">Založit řešení</a>
{% endif %} {% endif %}
{% if not site and sc.allow_edit_points %} {% if not ctx.site and allow_edit_points %}
<a class="btn btn-primary" href="{{ url_for('org_contest_task_points', contest_id=ct_id, task_id=task.task_id) }}">Zadat body</a> <a class="btn btn-primary" href="{{ ctx.url_for('org_contest_task_points') }}">Zadat body</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
......
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% set contest = sc.contest %} {% set contest = ctx.contest %}
{% set ct_id = contest.contest_id %} {% set ct_id = contest.contest_id %}
{% set round = sc.round %} {% set round = ctx.round %}
{% set user = sc.user %} {% set user = ctx.user %}
{% block title %}{{ round.round_code() }}: účastník {{ user.full_name() }}{% endblock %} {% block title %}{{ round.round_code() }}: účastník {{ user.full_name() }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, user=user) }} {{ ctx.breadcrumbs() }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -15,14 +15,14 @@ ...@@ -15,14 +15,14 @@
<h4>Rychlé odkazy</h4> <h4>Rychlé odkazy</h4>
Soutěžní kolo: Soutěžní kolo:
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-default" href="{{ url_for('org_round_list', id=round.round_id) }}">Účastníci</a> <a class="btn btn-default" href="{{ ctx.url_for('org_generic_list', ct_id=None) }}">Účastníci</a>
<a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky</a> <a class="btn btn-default" href="{{ ctx.url_for('org_score', ct_id=None) }}">Výsledky</a>
</div> </div>
<br>{{ round.get_level().name|capitalize }}: <br>{{ round.get_level().name|capitalize }}:
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=ct_id) }}">Tabulka řešení</a> <a class="btn btn-default" href="{{ ctx.url_for('org_contest_solutions') }}">Tabulka řešení</a>
<a class="btn btn-default" href="{{ url_for('org_contest_list', id=ct_id) }}">Účastníci</a> <a class="btn btn-default" href="{{ ctx.url_for('org_generic_list') }}">Účastníci</a>
<a class="btn btn-default" href="{{ url_for('org_score', contest_id=ct_id) }}">Výsledky</a> <a class="btn btn-default" href="{{ ctx.url_for('org_score') }}">Výsledky</a>
</div> </div>
</div> </div>
...@@ -40,12 +40,12 @@ ...@@ -40,12 +40,12 @@
<thead> <thead>
<tr><th colspan='2'>Účast v kole <tr><th colspan='2'>Účast v kole
</thead> </thead>
<tr><td>{{ round.get_level().name|capitalize }}:<td><a href='{{ url_for('org_contest', id=ct_id) }}'>{{ contest.place.name }}</a> <tr><td>{{ round.get_level().name|capitalize }}:<td><a href='{{ ctx.url_for('org_contest') }}'>{{ contest.place.name }}</a>
<tr><td>Soutěžní místo:<td><a href='{{ url_for('org_contest', id=ct_id, site_id=sc.pion.place_id) }}'>{{ sc.pion.place.name }}</a> <tr><td>Soutěžní místo:<td><a href='{{ ctx.url_for('org_contest', site_id=ctx.pion.place_id) }}'>{{ ctx.pion.place.name }}</a>
<tr><td>Stav účasti:<td>{{ sc.pion.state.friendly_name() }} <tr><td>Stav účasti:<td>{{ ctx.pion.state.friendly_name() }}
</table> </table>
<a class="btn btn-default" href="{{ url_for('org_user', id=user.user_id) }}">Detail uživatele</a> <a class="btn btn-default" href="{{ user|user_url }}">Detail uživatele</a>
{% include "parts/org_submit_warning.html" %} {% include "parts/org_submit_warning.html" %}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% block title %}Zadání kola {{ round.round_code() }}{% endblock %} {% block title %}Zadání kola {{ round.round_code() }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, action="Zadáni") }} {{ ctx.breadcrumbs(action="Zadáni") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% block title %}Stažení řešení úlohy {{ task.code }} {{ task.name }}{% endblock %} {% block title %}Stažení řešení úlohy {{ task.code }} {{ task.name }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Stažení řešení") }} {{ ctx.breadcrumbs(action="Stažení řešení") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% block title %}Dávkové bodování úlohy {{ task.code }} {{ task.name }}{% endblock %} {% block title %}Dávkové bodování úlohy {{ task.code }} {{ task.name }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, task=task, action="Dávkové bodování") }} {{ ctx.breadcrumbs(action="Dávkové bodování") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% set site_id = site.place_id if site else None %}
{% block title %}Nahrání opravených řešení úlohy {{ task.code }} {{ task.name }}{% endblock %} {% block title %}Nahrání opravených řešení úlohy {{ task.code }} {{ task.name }}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Nahrání opravených řešení") }} {{ ctx.breadcrumbs(action="Nahrání opravených řešení") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
Import dat do {% if contest %}soutěže {{ contest.place.name_locative() }}{% else %}kola {{ round.round_code() }}{% endif %} Import dat do {% if contest %}soutěže {{ contest.place.name_locative() }}{% else %}kola {{ round.round_code() }}{% endif %}
{% endblock %} {% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, contest=contest, action="Import dat") }} {{ ctx.breadcrumbs(action="Import dat") }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
......
{% extends "base.html" %} {% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} {% import "bootstrap/wtf.html" as wtf %}
{% block title %}Seznam účastníků kola {{ round.round_code() }}{% endblock %} {% block title %}
{% if contest %}
Seznam účastníků {% if site %}v soutěžním místě {{ site.name }}{% else %}{{ contest.place.name_locative() }}{% endif %}
{% else %}
Seznam účastníků kola {{ round.round_code() }}
{% endif %}
{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ contest_breadcrumbs(round=round, action="Seznam účastníků" if table else "E-maily", table=False if table else True) }} {{ ctx.breadcrumbs(action="Seznam účastníků" if table else "E-maily", table=False if table else True) }}
{% endblock %} {% endblock %}
{% set id = round.round_id %} {% set id = contest.contest_id %}
{% set site_id = site.place_id if site else None %}
{% block body %} {% block body %}
<div class="form-frame"> <div class="form-frame">
<form action="" method="GET" class="form form-inline" role="form"> <form action="" method="GET" class="form form-inline" role="form">
<div class="form-row"> <div class="form-row">
{{ wtf.form_field(filter.contest_place, size=8) }} {% if not contest %}
{{ wtf.form_field(filter.contest_place, placeholder='Kód', size=8) }}
{% endif %}
{% if not site %}
{{ wtf.form_field(filter.participation_place, size=8) }} {{ wtf.form_field(filter.participation_place, size=8) }}
{% endif %}
{{ wtf.form_field(filter.school, size=8) }} {{ wtf.form_field(filter.school, size=8) }}
{{ wtf.form_field(filter.participation_state) }} {{ wtf.form_field(filter.participation_state) }}
</div> </div>
...@@ -56,12 +67,96 @@ ...@@ -56,12 +67,96 @@
</div> </div>
{% if table %} {% if table %}
{% include 'parts/org_participants_table_actions.html' %} {% if action_form %}
{% if form_actions %} <form action="" method="POST" class="form form-horizontal" role="form">
<br>
<i>Upozornění: Můžete editovat jen účastníky soutěžící v oblastech, ke kterým máte právo.</i>
{% endif %} {% endif %}
{{ table.to_html() }}
{% if contest %}
<a class="btn btn-primary" href="{{ url_for('org_contest_add_user', ct_id=contest.contest_id, site_id=site.place_id if site else None) }}">Přidat účastníka</a>
{% endif %}
<a class="btn btn-default"
title="Zobrazí emailové adresy ve snadno zkopírovatelném formátu"
href="{{ ctx.url_for('org_generic_list_emails', **request.args) }}">
Vypsat e-mailové adresy
</a>
{% if action_form %}
{{ action_form.csrf_token }}
<h3>Provést akci</h3>
<div class="form-frame">
<div class="form-group">
<label class="control-label col-sm-2">Provést akci na:</label>
<div class="col-sm-5 radio">
<label>
<input id="action_on-0" name="action_on" type="radio" value="all" required{% if action_form.action_on.data == 'all' %} checked{% endif %}>
všech vyfiltrovaných účastnících
</label>
</div><div class="col-sm-5 radio">
<label>
<input id="action_on-1" name="action_on" type="radio" value="checked" required{% if action_form.action_on.data == 'checked' %} checked{% endif %}>
pouze zaškrtnutých účastnících
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="participation_state">Stav účasti</label>
<div class="col-sm-6">{{ wtf.form_field(action_form.participation_state, form_type='inline') }}</div>
<div class="col-sm-4">{{ wtf.form_field(action_form.set_participation_state, form_type='inline', class='btn btn-primary') }}</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="participation_place">Soutěžní místo</label>
<div class="col-sm-6">{{ wtf.form_field(action_form.participation_place, form_type='inline', placeholder='Kód místa') }}</div>
<div class="col-sm-4">{{ wtf.form_field(action_form.set_participation_place, form_type='inline', class='btn btn-primary') }}</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="contest_place">
{{ round.get_level().name|capitalize }}
</label>
<div class="col-sm-6">
{{ wtf.form_field(action_form.contest_place, form_type='inline', placeholder='Kód místa') }}
<p class="help-block">
{{ round.get_level().name_locative("V tomto", "V této", "V tomto") }} musí existovat soutěž pro {{ round.name|lower }} kategorie {{ round.category }}.
</p>
</div>
<div class="col-sm-4">{{ wtf.form_field(action_form.set_contest, form_type='inline', class='btn btn-primary', value='Přesunout do ' + round.get_level().name_genitive('jiného', 'jiné', 'jiného')) }}</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="contest_place">Smazání účasti</label>
<div class="col-sm-6"><p class="help-block">Dojde ke smazání účasti v tomto kole, ne účastníka z ročníku.</p></div>
<div class="col-sm-4">{{ wtf.form_field(action_form.remove_participation, form_type='inline', class='btn btn-danger') }}</div>
</div>
</div>
</form>
{% else %} {% else %}
{% include 'parts/org_participants_emails.html' %} <p>
<i>Nemáte právo k editaci účastníků v {{ round.get_level().name_locative("v tomto", "v této", "v tomto") }}.</i>
</p>
{% endif %}
{% else %}
<h3>E-mailové adresy</h3>
{% if emails %}
<pre>{{ emails|join('\n')|escape }}</pre>
<textarea id="emails-textarea" style="display: none">{{ emails|join('\n')|escape }}</textarea>
<p>
<a class="btn btn-primary" href="{{ mailto_link }}">Vytvořit e-mail pro {{ count|inflected("adresáta", "adresáty", "adresátů") }}</a>
<button class="btn btn-default" id="copy-emails">Zkopírovat všechny adresy do schránky</button>
<script type="text/javascript">
var ta = document.getElementById('emails-textarea');
document.getElementById('copy-emails').addEventListener('click', function () {
ta.style.display = 'block';
ta.select();
document.execCommand('copy', false);
ta.style.display = 'none';
});
</script>
</p>
<p>E-mailové adresy účastníků prosím vkládejte do pole pro <b>skrytou kopii (Bcc)</b>, ať si navzájem nevidí své e-maily.</p>
{% else %}<i>Žádné e-mailové adresy k vypsání.</i>{% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment