From 889f70fac2239808dec734e6acf198ca44338881 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ji=C5=99=C3=AD=20Setni=C4=8Dka?= <setnicka@seznam.cz>
Date: Thu, 4 Mar 2021 22:30:24 +0100
Subject: [PATCH] =?UTF-8?q?Drobn=C3=A9=20zm=C4=9Bny=20v=20org=20=C4=8D?=
 =?UTF-8?q?=C3=A1sti=20webu=20souvisej=C3=ADc=C3=AD=20se=20zaveden=C3=AD?=
 =?UTF-8?q?=20=C4=8D=C3=A1st=C3=AD=20kol?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* importy kontrolují práva podle stavu u RoundPart namísto Round
* job na upload feedbacku bere stav pro kontrolu práv podle round_part_id
  dané úlohy
* soubory se zadáním jsou určeny kombinací Round + RoundPart
---
 mo/imports.py                                  |  6 +++---
 mo/jobs/submit.py                              |  8 +++++---
 mo/web/org_score.py                            |  2 +-
 mo/web/templates/org_contest_solutions.html    |  2 +-
 mo/web/templates/org_submit_list.html          |  2 +-
 mo/web/templates/parts/org_solution_table.html |  2 +-
 mo/web/user.py                                 | 12 ++++++++----
 mo/web/util.py                                 |  6 +++---
 8 files changed, 23 insertions(+), 17 deletions(-)

diff --git a/mo/imports.py b/mo/imports.py
index 72c7b7b3..7cedac5d 100644
--- a/mo/imports.py
+++ b/mo/imports.py
@@ -670,7 +670,7 @@ class PointsImport(Import):
                 return self.error('Soutěžící nesoutěží v této oblasti')
 
         rights = self.gatekeeper.rights_for_contest(pion.contest)
-        if not rights.can_edit_points(self.round):
+        if not rights.can_edit_points(self.task.round_part):
             return self.error('Nemáte právo na úpravu bodů')
 
         user = pion.user
@@ -682,7 +682,7 @@ class PointsImport(Import):
                 return
             if not self.allow_add_del:
                 return self.error('Tento soutěžící úlohu neodevzdal')
-            if not rights.can_upload_solutions(round):
+            if not rights.can_upload_solutions(self.task.round_part):
                 return self.error('Nemáte právo na zakládání nových řešení')
             sol = db.Solution(user_id=user_id, task_id=task_id)
             sess.add(sol)
@@ -698,7 +698,7 @@ class PointsImport(Import):
                 return self.error('Tento soutěžící úlohu odevzdal')
             if sol.final_submit is not None or sol.final_feedback is not None:
                 return self.error('Nelze smazat řešení, ke kterému existují odevzdané soubory')
-            if not rights.can_upload_solutions(round):
+            if not rights.can_upload_solutions(self.task.round_part):
                 return self.error('Nemáte právo na mazání řešení')
             logger.info(f'Import: Smazáno řešení user=#{user_id} task=#{task_id}')
             mo.util.log(
diff --git a/mo/jobs/submit.py b/mo/jobs/submit.py
index 019b9074..8bc74947 100644
--- a/mo/jobs/submit.py
+++ b/mo/jobs/submit.py
@@ -227,7 +227,9 @@ def handle_upload_feedback(the_job: TheJob):
             contest_dict[user.user_id] = contest
             site_id_dict[user.user_id] = pion.place_id
             rr = the_job.gatekeeper.rights_for_contest(contest)
-            user_rights[user.user_id] = rr.can_upload_feedback(round)
+            user_rights[user.user_id] = {
+                part.round_part_id: rr.can_upload_feedback(part) for part in round.parts
+            }
 
         for f in files:
             f.user = user_dict[f.user_id]
@@ -237,8 +239,8 @@ def handle_upload_feedback(the_job: TheJob):
                 the_job.error(f'{f.file_name}: Účastník leží mimo vybranou soutěž')
             elif only_site_id is not None and site_id_dict[f.user_id] != only_site_id:
                 the_job.error(f'{f.file_name}: Účastník leží mimo vybrané soutěžní místo')
-            elif not user_rights[f.user_id]:
-                the_job.error(f'{f.file_name}: K tomuto účastníkovi nemáte dostatečná oprávnění')
+            elif not user_rights[f.user_id][f.task.round_part_id]:
+                the_job.error(f'{f.file_name}: K tomuto účastníkovi a úloze nemáte dostatečná oprávnění')
 
     def process_file(fb: UploadFeedback) -> bool:
         assert fb.user and fb.task
diff --git a/mo/web/org_score.py b/mo/web/org_score.py
index 4925de52..ed0b0dcc 100644
--- a/mo/web/org_score.py
+++ b/mo/web/org_score.py
@@ -127,7 +127,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None):
                 url_for('org_contest_task', contest_id=contest_id, task_id=task.task_id),
                 task.code
             )
-            if rr.can_edit_points(round):
+            if rr.can_edit_points(task.round_part):
                 title += ' <a href="{}" title="Editovat body" class="icon">✎</a>'.format(
                     url_for('org_contest_task_points', contest_id=contest_id, task_id=task.task_id),
                 )
diff --git a/mo/web/templates/org_contest_solutions.html b/mo/web/templates/org_contest_solutions.html
index dcb73a6f..6ca24602 100644
--- a/mo/web/templates/org_contest_solutions.html
+++ b/mo/web/templates/org_contest_solutions.html
@@ -69,7 +69,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje
 				{% set sol = tasks_sols[task.task_id][u.user_id] %}
 				{% if sol.final_submit_obj %}
 					{% set p = sol.final_submit_obj %}
-					{% set late = p.check_deadline(round) %}
+					{% set late = p.check_deadline(task.round_part) %}
 					<td class="sol{% if late or p.broken %} sol-warn{% endif %}">
 						<a href="{{ paper_link(u, p) }}" title="{{ p.uploaded_at|timeformat }}{% if p.broken %} - nekorektní PDF{% endif %}{% if p.pages != None %} - {{ p.pages|inflected('stránka', 'stránky', 'stránek') }}{% endif %}{% if late %} - {{ late }}{% endif %}">🖺</a>
 						{% set key = (u.user_id, task.task_id, "solution") %}
diff --git a/mo/web/templates/org_submit_list.html b/mo/web/templates/org_submit_list.html
index 63c90511..d6f4f245 100644
--- a/mo/web/templates/org_submit_list.html
+++ b/mo/web/templates/org_submit_list.html
@@ -61,7 +61,7 @@ Existuje více než jedna verze řešení, finální je podbarvená.
 		{% set active_sol_id = None %}
 	{% endif %}
 	{% for p in sol_papers %}
-		{% set late = p.check_deadline(sc.round) %}
+		{% set late = p.check_deadline(sc.task.round_part) %}
 		<tr{% if p.paper_id == active_sol_id %} class='sol-active'{% endif %}>
 			<td{% if late %} class='sol-warn'{% endif %}>{{ p.uploaded_at|timeformat }}
 			<td>{% if p.broken %}nekorektní PDF{% else %}{{ p.pages|or_dash }}{% endif %}
diff --git a/mo/web/templates/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html
index 150d77ec..a91f604c 100644
--- a/mo/web/templates/parts/org_solution_table.html
+++ b/mo/web/templates/parts/org_solution_table.html
@@ -47,7 +47,7 @@ finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solut
 		{% if sol %}
 		<td>{% if sol.final_submit_obj %}
 			{% set p = sol.final_submit_obj %}
-			{% set late = p.check_deadline(round) %}
+			{% set late = p.check_deadline(task.round_part) %}
 			{% if late %}<span class='sol-warn icon' title="{{ late }}">⚠</span>{% endif %}
 			<a href='{{ paper_link(u, p) }}'>
 				{{- p.uploaded_at|timeformat }}
diff --git a/mo/web/user.py b/mo/web/user.py
index f4ac0297..a0b79284 100644
--- a/mo/web/user.py
+++ b/mo/web/user.py
@@ -85,15 +85,19 @@ def user_contest(id: int):
     )
 
 
-@app.route('/user/contest/<int:id>/task-statement/zadani.pdf')
-def user_task_statement(id: int):
+@app.route('/user/contest/<int:id>/part/<int:part_id>/task-statement/zadani.pdf')
+def user_task_statement(id: int, part_id: int):
     contest = get_contest(id)
 
-    if not contest.round.task_statement_available():
+    round_part = db.get_session().query(db.RoundPart).get(part_id)
+    if not round_part or round_part.round_id != contest.round_id:
+        raise werkzeug.exceptions.NotFound()
+
+    if not round_part.task_statement_available():
         logger.warn(f'Účastník #{g.user.user_id} chce zadání, na které nemá právo')
         raise werkzeug.exceptions.Forbidden()
 
-    return mo.web.util.send_task_statement(contest.round)
+    return mo.web.util.send_task_statement(round_part)
 
 
 class SubmitForm(FlaskForm):
diff --git a/mo/web/util.py b/mo/web/util.py
index 56dc2e9b..31ffd6d2 100644
--- a/mo/web/util.py
+++ b/mo/web/util.py
@@ -40,9 +40,9 @@ class PagerForm(FlaskForm):
         return (count, query)
 
 
-def send_task_statement(round: db.Round) -> Response:
-    assert round.tasks_file is not None
-    file = os.path.join(mo.util.data_dir('statements'), round.tasks_file)
+def send_task_statement(round_part: db.RoundPart) -> Response:
+    assert round_part.tasks_file is not None
+    file = os.path.join(mo.util.data_dir('statements'), round_part.tasks_file)
     if os.path.isfile(file):
         return send_file(file, mimetype='application/pdf')
     else:
-- 
GitLab