diff --git a/mo/imports.py b/mo/imports.py index 4cf8deede795e083e67eb21f7d97b0bd1a6c1408..8e894710bdca8d88b1a5d845b368d1d40a2c33e0 100644 --- a/mo/imports.py +++ b/mo/imports.py @@ -5,7 +5,7 @@ import io import re from sqlalchemy import and_ from sqlalchemy.orm import joinedload, Query -from typing import List, Optional, Any, Dict, Type, Union +from typing import List, Optional, Any, Dict, Type, Union, Set import mo.config as config import mo.csv @@ -57,6 +57,7 @@ class Import: user: db.User round: Optional[db.Round] contest: Optional[db.Contest] + only_region: Optional[db.Place] task: Optional[db.Task] # pro Import bodů allow_add_del: bool # pro Import bodů: je povoleno zakládat/mazat řešení fmt: FileFormat @@ -231,19 +232,30 @@ class Import: self.cnt_new_participations += 1 return pion + def place_is_allowed(self, place: db.Place) -> bool: + if self.contest is not None and self.contest.place_id != place.place_id: + return False + if self.only_region is not None and not self.gatekeeper.is_ancestor_of(self.only_region, place): + return False + return True + def obtain_contest(self, oblast: Optional[db.Place], allow_none: bool = False): + assert self.round + if oblast is not None and not self.place_is_allowed(oblast): + return self.error('Oblast neodpovídá té, do které se importuje') if self.contest: contest = self.contest - if oblast is not None and oblast.place_id != contest.place.place_id: - return self.error('Oblast neodpovídá té, do které se importuje') else: + # Zde mluvíme o oblastech, místo abychom používali place_levels, + # protože sloupec má ve jménu oblast a také je potřeba rozlišovat školu + # účastníka a školu jako oblast. if oblast is None: if not allow_none: - self.error('Je nutné uvést ' + self.round.get_level().name) + self.error('Je nutné uvést kód oblasti') return None contest = db.get_session().query(db.Contest).filter_by(round=self.round, place=oblast).one_or_none() if contest is None: - return self.error('V ' + self.round.get_level().name_locative("uvedeném", "uvedené", "uvedeném") + ' toto kolo neprobíhá') + return self.error('V uvedené oblasti toto kolo neprobíhá') return contest @@ -281,6 +293,8 @@ class Import: args.append(f'round=#{self.round.round_id}') if self.contest is not None: args.append(f'contest=#{self.contest.contest_id}') + if self.only_region is not None: + args.append(f'region=#{self.only_region.place_id}') if self.task is not None: args.append(f'task=#{self.task.task_id}') @@ -301,17 +315,21 @@ class Import: args.append(f'{key}={val}') logger.info('Import: Hotovo (%s)', " ".join(args)) + details = self.log_details.copy() + if self.only_region: + details['region'] = self.only_region.place_id + if self.contest is not None: mo.util.log( type=db.LogType.contest, what=self.contest.contest_id, - details=self.log_details, + details=details, ) elif self.round is not None: mo.util.log( type=db.LogType.round, what=self.round.round_id, - details=self.log_details, + details=details, ) else: assert False @@ -588,6 +606,9 @@ class PointsImport(Import): query = query.filter(db.Participation.contest_id == self.contest.master_contest_id) else: contest_query = sess.query(db.Contest.master_contest_id).filter_by(round=self.round) + if self.only_region: + assert self.round + contest_query = db.filter_place_nth_parent(contest_query, db.Contest.place_id, self.round.level - self.only_region.level, self.only_region.place_id) query = query.filter(db.Participation.contest_id.in_(contest_query.subquery())) return query @@ -615,7 +636,13 @@ class PointsImport(Import): query = self._pion_sol_query().filter(db.Participation.user_id == user_id) pion_sols = query.all() if not pion_sols: - return self.error('Soutěžící nenalezen v tomto kole') + if self.contest is not None: + msg = self.round.get_level().name_locative('tomto', 'této', 'tomto') + elif self.only_region is not None: + msg = self.only_region.get_level().name_locative('tomto', 'této', 'tomto') + else: + msg = 'tomto kole' + return self.error(f'Soutěžící nenalezen v {msg}') elif len(pion_sols) > 1: return self.error('Soutěžící v tomto kole soutěží vícekrát, neumím zpracovat') pion, sol = pion_sols[0] @@ -625,10 +652,6 @@ class PointsImport(Import): else: contest = sess.query(db.Contest).filter_by(round=self.round, master_contest_id=pion.contest_id).one() - if self.contest is not None: - if contest != self.contest: - return self.error('Soutěžící nesoutěží v ' + self.round.get_level().name_locative('tomto', 'této', 'tomto')) - rights = self.gatekeeper.rights_for_contest(contest) if not rights.can_edit_points(): return self.error('Nemáte právo na úpravu bodů') @@ -709,6 +732,7 @@ def create_import(user: db.User, fmt: FileFormat, round: Optional[db.Round] = None, contest: Optional[db.Contest] = None, + only_region: Optional[db.Place] = None, task: Optional[db.Task] = None, allow_add_del: bool = False): imp: Import @@ -726,6 +750,7 @@ def create_import(user: db.User, imp.user = user imp.round = round imp.contest = contest + imp.only_region = only_region imp.task = task imp.allow_add_del = allow_add_del imp.fmt = fmt diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index 7b8379bae55cc03b0a595e1c38478b8fe2337fbf..7fb44d42d14c7c92a5a15ebad315637fb0983a14 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -433,8 +433,9 @@ class ImportForm(FlaskForm): @app.route('/org/contest/c/<int:ct_id>/import', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/import', methods=('GET', 'POST')) -def org_generic_import(round_id: Optional[int] = None, ct_id: Optional[int] = None): - ctx = get_context(round_id=round_id, ct_id=ct_id, right_needed=Right.manage_contest) +@app.route('/org/contest/r/<int:round_id>/h/<int:hier_id>/import', methods=('GET', 'POST')) +def org_generic_import(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_id: Optional[int] = None): + ctx = get_context(round_id=round_id, hier_id=hier_id, ct_id=ct_id, right_needed=Right.manage_contest) round, contest = ctx.master_round, ctx.master_contest form = ImportForm() @@ -442,7 +443,7 @@ def org_generic_import(round_id: Optional[int] = None, ct_id: Optional[int] = No warnings = [] if form.validate_on_submit(): fmt = form.fmt.data - imp = create_import(user=g.user, type=form.typ.data, fmt=fmt, round=round, contest=contest) + imp = create_import(user=g.user, type=form.typ.data, fmt=fmt, round=round, contest=contest, only_region=ctx.hier_place) if form.submit.data: if form.file.data is not None: file = form.file.data.stream @@ -1308,9 +1309,10 @@ class BatchPointsForm(FlaskForm): @app.route('/org/contest/c/<int:ct_id>/task/<int:task_id>/batch-points', methods=('GET', 'POST')) @app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/batch-points', methods=('GET', 'POST')) -def org_generic_batch_points(task_id: int, round_id: Optional[int] = None, ct_id: Optional[int] = None): - ctx = get_context(round_id=round_id, ct_id=ct_id, task_id=task_id) - round, contest, task = ctx.round, ctx.contest, ctx.task +@app.route('/org/contest/r/<int:round_id>/h/<int:hier_id>/task/<int:task_id>/batch-points', methods=('GET', 'POST')) +def org_generic_batch_points(task_id: int, round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_id: Optional[int] = None): + ctx = get_context(round_id=round_id, hier_id=hier_id, ct_id=ct_id, task_id=task_id) + round, hier_place, contest, task = ctx.round, ctx.hier_place, ctx.contest, ctx.task if not ctx.rights.can_edit_points(): raise werkzeug.exceptions.Forbidden() @@ -1320,7 +1322,7 @@ def org_generic_batch_points(task_id: int, round_id: Optional[int] = None, ct_id warnings = [] if form.validate_on_submit(): fmt = form.fmt.data - imp = create_import(user=g.user, type=ImportType.points, fmt=fmt, round=round, contest=contest, task=task, allow_add_del=form.add_del_sols.data) + imp = create_import(user=g.user, type=ImportType.points, fmt=fmt, round=round, only_region=hier_place, contest=contest, task=task, allow_add_del=form.add_del_sols.data) if form.submit.data: if form.file.data is not None: file = form.file.data.stream diff --git a/mo/web/templates/org_generic_import.html b/mo/web/templates/org_generic_import.html index 2d9c95049172ec5b0badb5a406a004b7853b303e..6ed927d607648ab8660340faeb70a481ebc5831b 100644 --- a/mo/web/templates/org_generic_import.html +++ b/mo/web/templates/org_generic_import.html @@ -33,7 +33,7 @@ Import dat do {% if contest %}soutěže {{ contest.place.name_locative() }}{% el Detaily fungování importu najdete v <a href='{{ url_for('doc_import') }}'>dokumentaci</a>. {% if not contest %} -<p><em>Pozor, zde se importuje do všech oblastí najednou, takže je nutné uvádět +<p><em>Pozor, zde se importuje do více soutěží najednou, takže je nutné uvádět kód oblasti. Nechcete raději importovat do konkrétní oblasti?</em> {% endif %}