From 12bbb858c520bee0bebe71d847f3e491fbdec633 Mon Sep 17 00:00:00 2001
From: Martin Mares <mj@ucw.cz>
Date: Fri, 19 Feb 2021 18:11:03 +0100
Subject: [PATCH] =?UTF-8?q?Import:=20Bodov=C3=A1n=C3=AD=20um=C3=AD=20i=20z?=
=?UTF-8?q?akl=C3=A1dat/odstra=C5=88ovat=20=C5=99e=C5=A1en=C3=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
mo/imports.py | 87 +++++++++++++++++++++++++++++++++----------
mo/web/org_contest.py | 7 ++--
2 files changed, 72 insertions(+), 22 deletions(-)
diff --git a/mo/imports.py b/mo/imports.py
index 0648efb3..1922896b 100644
--- a/mo/imports.py
+++ b/mo/imports.py
@@ -4,7 +4,7 @@ import io
import re
from sqlalchemy import and_
from sqlalchemy.orm import joinedload, Query
-from typing import List, Optional, Any, Dict, Type
+from typing import List, Optional, Any, Dict, Type, Union
import mo.csv
from mo.csv import FileFormat, MissingHeaderError
@@ -42,6 +42,8 @@ class Import:
cnt_new_participations: int = 0
cnt_new_roles: int = 0
cnt_set_points: int = 0
+ cnt_add_sols: int = 0
+ cnt_del_sols: int = 0
# Veřejné vlastnosti importu
template_basename: str = "sablona"
@@ -50,7 +52,8 @@ class Import:
user: db.User
round: Optional[db.Round]
contest: Optional[db.Contest]
- task: Optional[db.Task]
+ task: Optional[db.Task] # pro Import bodů
+ allow_add_del: bool # pro Import bodů: je povoleno zakládat/mazat řešení
fmt: FileFormat
row_class: Type[mo.csv.Row]
row_example: mo.csv.Row
@@ -202,9 +205,13 @@ class Import:
self.new_user_ids.append(user.user_id)
return user
- def parse_points(self, points_str: str) -> Optional[int]:
+ def parse_points(self, points_str: str) -> Union[int, str, None]:
if points_str == "":
- return None
+ return self.error('Body musí být vyplněny')
+
+ points_str = points_str.upper()
+ if points_str in ['X', '?']:
+ return points_str
try:
pts = int(points_str)
@@ -329,6 +336,8 @@ class Import:
('p-ions', self.cnt_new_participations),
('roles', self.cnt_new_roles),
('points', self.cnt_set_points),
+ ('add-sols', self.cnt_add_sols),
+ ('del-sols', self.cnt_del_sols),
]:
if val > 0:
args.append(f'{key}={val}')
@@ -628,14 +637,16 @@ class PointsImport(Import):
if (len(self.errors) > num_prev_errs
or user_id is None
or krestni is None
- or prijmeni is None):
+ or prijmeni is None
+ or body is None):
return
assert self.round is not None
assert self.task is not None
+ task_id = self.task.task_id
sess = db.get_session()
- query = self._pion_sol_query().filter(db.Solution.user_id == user_id)
+ 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')
@@ -656,30 +667,66 @@ class PointsImport(Import):
return self.error('Neodpovídá ID a jméno soutěžícího')
if sol is None:
- return self.error('Tento soutěžící úlohu neodevzdal')
+ if body == 'X':
+ return
+ if not self.allow_add_del:
+ return self.error('Tento soutěžící úlohu neodevzdal')
+ if not rights.can_upload_solutions(round):
+ 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)
+ logger.info(f'Import: Založeno řešení user=#{user_id} task=#{task_id}')
+ mo.util.log(
+ type=db.LogType.participant,
+ what=user_id,
+ details={'action': 'solution-created', 'task': task_id},
+ )
+ self.cnt_add_sols += 1
+ elif body == 'X':
+ if not self.allow_add_del:
+ 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):
+ 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(
+ type=db.LogType.participant,
+ what=user_id,
+ details={'action': 'solution-removed', 'task': task_id},
+ )
+ self.cnt_del_sols += 1
+ sess.delete(sol)
+ return
- if sol.points != body:
- sol.points = body
+ points = body if isinstance(body, int) else None
+ if sol.points != points:
+ sol.points = points
sess.add(db.PointsHistory(
task=self.task,
participant_id=user_id,
user=self.user,
points_at=mo.now,
- points=body,
+ points=points,
))
self.cnt_set_points += 1
def get_template(self) -> str:
rows = []
for pion, sol in sorted(self._pion_sol_query().all(), key=lambda pair: pair[0].user.sort_key()):
- if sol is not None:
- user = pion.user
- rows.append(PointsImportRow(
- user_id=user.user_id,
- krestni=user.first_name,
- prijmeni=user.last_name,
- body=sol.points,
- ))
+ if sol is None:
+ pts = 'X'
+ elif sol.points is None:
+ pts = '?'
+ else:
+ pts = str(sol.points)
+ user = pion.user
+ rows.append(PointsImportRow(
+ user_id=user.user_id,
+ krestni=user.first_name,
+ prijmeni=user.last_name,
+ body=pts,
+ ))
out = io.StringIO()
mo.csv.write(file=out, fmt=self.fmt, row_class=self.row_class, rows=rows)
@@ -691,7 +738,8 @@ def create_import(user: db.User,
fmt: FileFormat,
round: Optional[db.Round] = None,
contest: Optional[db.Contest] = None,
- task: Optional[db.Task] = None):
+ task: Optional[db.Task] = None,
+ allow_add_del: bool = False):
imp: Import
if type == ImportType.participants:
imp = ContestImport()
@@ -708,6 +756,7 @@ def create_import(user: db.User,
imp.round = round
imp.contest = contest
imp.task = task
+ imp.allow_add_del = allow_add_del
imp.fmt = fmt
imp.gatekeeper = mo.rights.Gatekeeper(user)
imp.setup()
diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 839b3435..8b0d5edd 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -15,7 +15,7 @@ import wtforms
import mo
from mo.csv import FileFormat
import mo.db as db
-from mo.imports import ImportType, create_import
+from mo.imports import ImportType, Import, create_import
import mo.jobs.submit
from mo.rights import Right, Rights
import mo.util
@@ -1161,6 +1161,7 @@ class BatchPointsForm(FlaskForm):
choices=FileFormat.choices(), coerce=FileFormat.coerce,
default=FileFormat.cs_csv,
)
+ add_del_sols = wtforms.BooleanField('Zakládat / mazat řešení', description='Xyzzy')
submit = wtforms.SubmitField('Nahrát body')
get_template = wtforms.SubmitField('Stáhnout šablonu')
@@ -1172,7 +1173,7 @@ def generic_batch_points(round: db.Round, contest: Optional[db.Contest], task: d
errs = []
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)
+ 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)
if form.submit.data:
if form.file.data is not None:
file = form.file.data.stream
@@ -1182,7 +1183,7 @@ def generic_batch_points(round: db.Round, contest: Optional[db.Contest], task: d
if imp.cnt_rows == 0:
flash('Soubor neobsahoval žádné řádky s daty', 'danger')
else:
- flash(f'Importováno ({imp.cnt_rows} řádků, změněny body u {imp.cnt_set_points} řešení)', 'success')
+ flash(f'Importováno ({imp.cnt_rows} řádků, {imp.cnt_set_points} řešení přebodováno, {imp.cnt_add_sols} založeno a {imp.cnt_del_sols} smazáno)', 'success')
if contest is not None:
return redirect(url_for('org_contest', id=contest.contest_id))
else:
--
GitLab