From c903a852037a5198c9f759626fd7844c3bb303ac Mon Sep 17 00:00:00 2001
From: Martin Mares <mj@ucw.cz>
Date: Sat, 12 Nov 2022 15:54:01 +0100
Subject: [PATCH] =?UTF-8?q?P=C5=99edchoz=C3=AD=20kolo=20m=C5=AF=C5=BEe=20m?=
 =?UTF-8?q?=C3=ADt=20ni=C5=BE=C5=A1=C3=AD=20level?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Letos nastává v kategorii P na přechodu z domácího (level=1)
do krajského (level=2) kola.

Upravuji jak automatické zakládání soutěží, tak postup mezi koly,
aby s tímto případem počítaly.

Také místo CTE na hledání v hierarchii regionů používám view
RegionDescendant. Možná to je pomalejší, ale zde není rychlost nijak
kritická, takže je preferuji jednodušší kód.

Closes #296.
---
 mo/web/org_contest.py | 30 +++++++++++++++++-------------
 mo/web/org_round.py   | 21 +++++++++++++--------
 2 files changed, 30 insertions(+), 21 deletions(-)

diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 6bbc463d..552a569e 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -8,7 +8,7 @@ import json
 import locale
 import magic
 from markupsafe import Markup
-from sqlalchemy import func, and_, select, not_
+from sqlalchemy import func, and_, not_
 from sqlalchemy.orm import joinedload, aliased
 from sqlalchemy.orm.query import Query
 from sqlalchemy.dialects.postgresql import insert as pgsql_insert
@@ -28,6 +28,7 @@ import mo.jobs.protocols
 import mo.jobs.submit
 from mo.rights import Right, RoundRights
 import mo.util
+from mo.util import assert_not_none
 from mo.util_format import inflect_number, inflect_by_number, inflect_with_number
 from mo.web import app
 import mo.web.fields as mo_fields
@@ -1533,12 +1534,12 @@ def org_contest_advance(ct_id: int):
     reject_by_place_id: Dict[int, int] = {}
     prev_pions_by_place_id: Dict[int, List[Tuple[db.Participation, Optional[decimal.Decimal], bool]]] = {}
 
-    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_q = sess.query(db.Contest).filter(db.Contest.round == prev_round)
+    if prev_round.level >= round.level:
+        prev_contests_q = prev_contests_q.join(db.RegionDescendant, and_(db.RegionDescendant.region == contest.place_id, db.RegionDescendant.descendant == db.Contest.place_id))
+    else:
+        prev_contests_q = prev_contests_q.join(db.RegionDescendant, and_(db.RegionDescendant.descendant == contest.place_id, db.RegionDescendant.region == db.Contest.place_id))
+    prev_contests = prev_contests_q.options(joinedload(db.Contest.place)).all()
     prev_contests.sort(key=lambda c: locale.strxfrm(c.place.name or ""))
 
     form = AdvanceForm()
@@ -1553,6 +1554,12 @@ def org_contest_advance(ct_id: int):
         prev_pion_query = (sess.query(db.Participation)
                            .filter(db.Participation.contest_id.in_([c.contest_id for c in prev_contests]))
                            .filter_by(state=db.PartState.active))
+        if prev_round.level < round.level:
+            prev_pion_query = (prev_pion_query
+                               .join(db.Participant, and_(db.Participant.user_id == db.Participation.user_id,
+                                                          db.Participant.year == round.year))
+                               .join(db.RegionDescendant, and_(db.RegionDescendant.descendant == db.Participant.school,
+                                                               db.RegionDescendant.region == contest.place_id)))
         if want_select:
             prev_pions = prev_pion_query.options(joinedload(db.Participation.user)).all()
         else:
@@ -1581,9 +1588,9 @@ def org_contest_advance(ct_id: int):
         really_inserted = 0
         for pp in prev_pions:
             # This incurs no real queries as we have all the contests cached
-            prev_place_id = sess.query(db.Contest).get(pp.contest_id).place_id
-            points = points_map[pp.user_id] if pp.user_id in points_map else None
-            checked = points is not None and points >= form.boundary.data
+            prev_place_id = assert_not_none(sess.query(db.Contest).get(pp.contest_id)).place_id
+            points = points_map.get(pp.user_id, 0)
+            checked = points >= form.boundary.data
             prev_pions_by_place_id[prev_place_id].append((pp, points, checked))
 
             if want_execute and want_select:
@@ -1665,9 +1672,6 @@ def get_prev_round(round: db.Round) -> Optional[db.Round]:
     if prev_round is None:
         flash('Předchozí kolo nenalezeno', 'danger')
         return None
-    elif prev_round.level < round.level:
-        flash('Předchozí kolo se koná ve vyšší oblasti než toto kolo', 'danger')
-        return None
     return prev_round
 
 
diff --git a/mo/web/org_round.py b/mo/web/org_round.py
index fec02c95..9b10d069 100644
--- a/mo/web/org_round.py
+++ b/mo/web/org_round.py
@@ -678,14 +678,19 @@ def org_round_create_contests(round_id: int):
     have_places_subq = (sess.query(db.Contest.place_id)
                             .filter_by(round=round))
 
-    new_places = (sess.query(db.Place)
-                      .select_from(db.Contest)
-                      .filter(db.Contest.round == prev_round)
-                      .join(db.RegionDescendant, db.RegionDescendant.descendant == db.Contest.place_id)
-                      .join(db.Place, db.Place.place_id == db.RegionDescendant.region)
-                      .filter(db.Place.level == round.level)
-                      .filter(db.Place.place_id.notin_(have_places_subq))
-                      .all())
+    new_places_q = sess.query(db.Place).select_from(db.Contest).filter(db.Contest.round == prev_round)
+    if prev_round.level >= round.level:
+        new_places_q = (new_places_q
+                        .join(db.RegionDescendant, db.RegionDescendant.descendant == db.Contest.place_id)
+                        .join(db.Place, db.Place.place_id == db.RegionDescendant.region))
+    else:
+        new_places_q = (new_places_q
+                        .join(db.RegionDescendant, db.RegionDescendant.region == db.Contest.place_id)
+                        .join(db.Place, db.Place.place_id == db.RegionDescendant.descendant))
+    new_places = (new_places_q
+                  .filter(db.Place.level == round.level)
+                  .filter(db.Place.place_id.notin_(have_places_subq))
+                  .all())
 
     form = CreateContestsForm()
     if form.validate_on_submit():
-- 
GitLab