diff --git a/mo/rights.py b/mo/rights.py index d990340e48afa92d50799b1ef35bb5769a83fa1c..e581dfe6d304c8a8971fe2503bee0baf410f3daf 100644 --- a/mo/rights.py +++ b/mo/rights.py @@ -200,28 +200,31 @@ class Rights: # Interní rozhodovaní o dostupnosti zadání - def _check_view_statement(self, round: db.Round): + def _check_view_statement(self, round: db.Round) -> tuple[bool, str]: if round.tasks_file is None: - return False + return False, "zatím chybí" 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 + 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 + if (self.have_right(Right.view_statement)): + # Ve zbylých případech jsme konzervativní a zadání neukazujeme + if (round.state == db.RoundState.preparing + or round.pr_tasks_start is None): + return False, "až začne soutěž" + if mo.now < round.pr_tasks_start: + return False, mo.util_format.timedelta(round.pr_tasks_start) - # Ve zbylých případech jsme konzervativní a zadání neukazujeme - return False + return True, "" + + if round.state not in [db.RoundState.grading, db.RoundState.closed]: + return False, "až začnou opravy" + + # Pokud už soutěž skončila, přístup k zadání má každý org. + # XXX: Rozhodujeme podle stavu kola, nikoliv soutěže! + return True, "" class RoundRights(Rights): @@ -279,25 +282,11 @@ class ContestRights(Rights): or self.have_right(Right.manage_contest)) def can_view_statement(self) -> bool: - return self._check_view_statement(self.contest.round) + can, _ = self._check_view_statement(self.contest.round) + return can - def cannot_view_statement_reason(self) -> str: - round = self.contest.round - if round.tasks_file is None: - return "zatím chybí" - if self.have_right(Right.manage_round): - # Správce kola může vždy všechno - return "" - if self.have_right(Right.view_statement): - if round.state == db.RoundState.preparing: - return "až začne soutěž" - if round.state == db.RoundState.running and round.pr_tasks_start is None: - return "až začnou opravy" - if mo.now < round.pr_tasks_start: - return mo.util_format.timedelta(round.pr_tasks_start) - if round.state not in [db.RoundState.grading, db.RoundState.closed]: - return "až začnou opravy" - return "" + def can_view_statement_with_reason(self) -> (bool, str): + return self._check_view_statement(self.contest.round) class Gatekeeper: diff --git a/mo/web/org.py b/mo/web/org.py index 142c741fc7a209d0155d2e2b6d2f9a7dd0e27374..d9d49b04e5ea44e0c25614ed31ec792599a08734 100644 --- a/mo/web/org.py +++ b/mo/web/org.py @@ -30,30 +30,38 @@ def org_index(): flash('ID uživatele musí být číslo', 'danger') sess = db.get_session() + contests_with_role = sess.execute(""" + SELECT contest_id, MIN(role) as role + FROM contests + JOIN user_roles ur USING(place_id) + JOIN rounds r USING(round_id) + JOIN users USING(user_id) + WHERE + user_id = :user_id + AND r.year = :current_year + AND (ur.year IS NULL OR ur.year = r.year) + AND (ur.category IS NULL OR ur.category = r.category) + AND (ur.seq IS NULL OR ur.seq = r.seq) + GROUP BY contest_id, r.level, r.category, place_id, r.seq, r.part + ORDER BY r.level, r.category, place_id, r.seq, r.part + """, {'user_id': g.user.user_id, 'current_year': mo.current_year}) - roles = (sess.query(db.UserRole) - .filter_by(user_id=g.user.user_id) - .options(joinedload(db.UserRole.place)) - .all()) + contest_ids = [] + roles = [] + for row in contests_with_role: + contest_ids.append(row['contest_id']) + roles.append(row['role']) - contests = [] - for role in roles: - q = (sess.query(db.Contest, db.UserRole.role, db.Round) - .select_from(db.Contest) - .filter_by(place_id=role.place_id) - .join(db.UserRole, db.UserRole.user_role_id == role.user_role_id) - .join(db.Round)) - if role.year: - q = q.filter(db.Round.year == role.year) - if role.category: - q = q.filter(db.Round.category == role.category) - if role.seq: - q = q.filter(db.Round.seq == role.seq) - contests += q.options(joinedload(db.Contest.place)).all() + contests = (sess.query(db.Contest) + .select_from(db.Contest) + .join(db.Round) + .filter(db.Contest.contest_id.in_(contest_ids)) + .options(joinedload(db.Contest.place)) + .order_by(db.Round.level, db.Round.category, db.Contest.place_id, + db.Round.seq, db.Round.part) + .all()) - contests.sort(key=lambda r: (r[2].year, r[2].category, r[2].seq, r[2].part)) - - return render_template('org_index.html', contests=contests, Right=Right, + return render_template('org_index.html', contests=zip(contests, roles), Right=Right, role_names=db.role_type_names, gatekeeper=g.gatekeeper) diff --git a/mo/web/templates/org_index.html b/mo/web/templates/org_index.html index b631bcb29c1fe6c74565b3154791b7b09f3c01e9..f35a95a7a1d30629fc9c31acb4f751e3c6b436dd 100644 --- a/mo/web/templates/org_index.html +++ b/mo/web/templates/org_index.html @@ -6,51 +6,49 @@ <h3>Moje soutěže</h3> <table class="table table-bordered"> +{% set curr = namespace(level = -1) %} +{% for c, role in contests %} + {% set cr = gatekeeper.rights_for_contest(c) %} + {% if curr.level != c.round.level %} <thead><tr> <th>Kategorie <th>Kolo + <th>{{ c.round.get_level().name|capitalize }} <th>Stav <th>Zadání <th>Moje role <th>Odkazy </thead> - {% for c,role,r in contests %} - {% set rr = gatekeeper.rights_for_contest(c) %} - {% if c.state == RoundState.preparing %} - <tr class="warning"> - {% elif c.state == RoundState.running %} - <tr class="success"> - {% elif c.state == RoundState.grading %} - <tr class="info"> - {% else %} - <tr> + {% set curr.level = c.round.level %} {% endif %} - <td class="text-center" style="font-size: 1.2em"><b>{{ r.category }}</b> - <td>{{ r.name }} {{ c.place.name_locativ() if c.place.level > 0 else '' }} + <tr class="rstate-{{c.state.name}}"> + <td class="text-center"><b>{{ c.round.category }}</b> + <td>{{ c.round.name }} + <td>{{ c.place.name }} <td>{{ c.state.friendly_name() }} <td> - {% if rr.can_view_statement() %} - <a href='{{ url_for('org_task_statement', id=r.round_id) }}'>stáhnout</a> + {% set can, reason = cr.can_view_statement_with_reason() %} + {% if can %} + <a href='{{ url_for('org_task_statement', id=c.round_id) }}'>stáhnout</a> {% else %} - {{ rr.cannot_view_statement_reason() }} + {{ reason }} {% endif %} <td>{{ role_names[role] }} <td> <a class="btn btn-xs btn-primary" href='{{ url_for('org_contest', id=c.contest_id) }}'>Detail</a> <a class="btn btn-xs btn-default" href='{{ url_for('org_contest_list', id=c.contest_id) }}'>Účastníci</a> - {% if rr.can_view_submits() and c.state != RoundState.preparing %} + {% if cr.have_right(Right.view_submits) and c.state != RoundState.preparing %} <a class="btn btn-xs btn-success" href='{{ url_for('org_contest_solutions', id=c.contest_id) }}'>Odevzdaná řešení</a> {% endif %} - {% if (c.state == RoundState.grading and rr.can_view_submits()) or c.state == RoundState.closed %} + {% if (c.state == RoundState.grading and cr.have_right(Right.view_submits)) or c.state == RoundState.closed %} <a class="btn btn-xs btn-warning" href='{{ url_for('org_score', contest_id=c.contest_id) }}'>Výsledky</a> {% endif %} - {% if rr.have_right(Right.manage_contest) and c.state != RoundState.closed %} - <a class="btn btn-xs btn-primary" href='{{ url_for('org_contest_add_user', id=c.contest_id) }}'>+ Přidat účastníka</a> + {% if cr.have_right(Right.manage_contest) and c.state == RoundState.preparing %} <a class="btn btn-xs btn-default" href='{{ url_for('org_contest_import', id=c.contest_id) }}'>Import</a> {% endif %} </td> </tr> - {% endfor %} +{% endfor %} </table> {% endif %}