From cd6687261cad333da32f092ebf640124ea119fdd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ji=C5=99=C3=AD=20Setni=C4=8Dka?= <setnicka@seznam.cz>
Date: Sun, 28 Nov 2021 13:23:02 +0100
Subject: [PATCH] =?UTF-8?q?Skenov=C3=A1n=C3=AD=20odevzdan=C3=BDch=20=C5=99?=
=?UTF-8?q?e=C5=A1en=C3=AD=20i=20oprav?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Formulář pro upload skenů dovoluje vybrat typ, který se uloží do JSONu v
popisu jobu.
Issue #259
---
mo/jobs/protocols.py | 25 ++++++++-----
mo/web/org_contest.py | 36 ++++++++++++-------
mo/web/templates/org_contest_scans.html | 14 ++++++--
.../templates/org_contest_scans_process.html | 11 +++---
4 files changed, 60 insertions(+), 26 deletions(-)
diff --git a/mo/jobs/protocols.py b/mo/jobs/protocols.py
index fd9d08d5..65b6b9e7 100644
--- a/mo/jobs/protocols.py
+++ b/mo/jobs/protocols.py
@@ -178,12 +178,13 @@ def handle_create_protocols(the_job: TheJob):
#
-def schedule_process_scans(contest: db.Contest, site: Optional[db.Place], for_user: db.User, tasks: List[db.Task], in_file_names: List[str]) -> int:
+def schedule_process_scans(contest: db.Contest, site: Optional[db.Place], scans_type: str, for_user: db.User, tasks: List[db.Task], in_file_names: List[str]) -> int:
place = site or contest.place
+ scans_desc = "odevzdaných řešení" if scans_type == "solution" else "oprav"
the_job = TheJob()
job = the_job.create(db.JobType.process_scans, for_user)
- job.description = f'Zpracování skenů {contest.round.round_code_short()} {place.name}'
+ job.description = f'Zpracování skenů {scans_desc} {contest.round.round_code_short()} {place.name}'
in_files = []
num_files = 0
@@ -195,6 +196,7 @@ def schedule_process_scans(contest: db.Contest, site: Optional[db.Place], for_us
assert in_files
job.in_json = {
+ 'type': scans_type,
'contest_id': contest.contest_id,
'site_id': site.place_id if site else None,
'task_ids': [t.task_id for t in tasks],
@@ -396,11 +398,12 @@ def schedule_sort_scans(job_id: int, for_user: db.User) -> int:
contest = sess.query(db.Contest).options(joinedload(db.Contest.round)).get(job.in_json['contest_id'])
assert contest is not None
+ scans_desc = "oprav" if 'type' in job.in_json and job.in_json['type'] == "feedback" else "odevzdaných řešení"
job.type = db.JobType.sort_scans
job.created_at = mo.now
job.expires_at = None
job.user = for_user
- job.description = f'Rozdělení již roztříděných skenů {contest.round.round_code_short()}'
+ job.description = f'Rozdělení již roztříděných skenů {scans_desc} {contest.round.round_code_short()}'
the_job.submit()
return the_job.job_id
@@ -426,6 +429,7 @@ def handle_sort_scans(the_job: TheJob):
site_id = job.in_json['site_id'] # type: ignore
task_ids = job.in_json['task_ids'] # type: ignore
in_files: List[str] = job.in_json['in_files'] # type: ignore
+ paper_type = db.PaperType.feedback if 'type' in job.in_json and job.in_json['type'] == 'feedback' else db.PaperType.solution
sess = db.get_session()
contest = sess.query(db.Contest).options(joinedload(db.Contest.round)).get(contest_id)
@@ -473,7 +477,7 @@ def handle_sort_scans(the_job: TheJob):
task=task,
for_user_obj=user,
uploaded_by_obj=job.user,
- type=db.PaperType.solution,
+ type=paper_type,
note='Z hromadného skenování',
))
@@ -520,15 +524,20 @@ def handle_sort_scans(the_job: TheJob):
'task': sol.task.task_id,
},
)
+ sols_map[index] = sol
for index in papers:
paper = papers[index]
sess.add(paper.paper)
if index in sols_map:
- sols_map[index].final_submit_obj = paper.paper
- elif index in sols_to_create:
- sols_to_create[index].final_submit_obj = paper.paper
+ if paper_type == db.PaperType.solution:
+ sols_map[index].final_submit_obj = paper.paper
+ else:
+ sols_map[index].final_feedback_obj = paper.paper
sess.commit()
- job.result = 'Celkem ' + mo.util_format.inflect_number(len(papers), 'roztříděné řešení', 'roztříděná řešení', 'roztříděných řešení')
+ if paper_type == db.PaperType.solution:
+ job.result = 'Celkem ' + mo.util_format.inflect_number(len(papers), 'roztříděné řešení', 'roztříděná řešení', 'roztříděných řešení')
+ else:
+ job.result = 'Celkem ' + mo.util_format.inflect_number(len(papers), 'roztříděná oprava', 'roztříděné opravy', 'roztříděných oprav')
the_job.expires_in_minutes = config.JOB_EXPIRATION_LONG
diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 60f7c92f..b76724c9 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -1707,6 +1707,7 @@ def org_contest_protocols(ct_id: int, site_id: Optional[int] = None):
class ProcessScansForm(FlaskForm):
files = wtforms.MultipleFileField('Soubory PDF se skeny', validators=[validators.required()])
+ scans_type = wtforms.RadioField('Typ skenů', choices=[('solution', 'Odevzdaná řešení'), ('feedback', 'Opravená řešení')])
process_scans = wtforms.SubmitField('Nahrát skeny')
@@ -1737,7 +1738,7 @@ def org_contest_scans(ct_id: int, site_id: Optional[int] = None):
if proc_form.validate_on_submit() and proc_form.process_scans.data:
files = request.files.getlist(proc_form.files.name)
job_id = mo.jobs.protocols.schedule_process_scans(
- contest, site, g.user,
+ contest, site, proc_form.scans_type.data, g.user,
tasks=[t for t in tasks if getattr(proc_form, f'task_{t.task_id}').data],
in_file_names=[f.stream.name for f in files],
)
@@ -1747,27 +1748,28 @@ def org_contest_scans(ct_id: int, site_id: Optional[int] = None):
jobs_query = sess.query(db.Job).filter_by(type=db.JobType.process_scans)
if not g.user.is_admin:
jobs_query = jobs_query.filter_by(user=g.user)
- jobs = []
- job_tasks: Dict[int, List[db.Task]] = {}
+ jobs: List[Tuple[db.Job, str, List[db.Task]]] = []
for job in jobs_query.all():
if 'contest_id' not in job.in_json or job.in_json['contest_id'] != ct_id:
continue
if site_id is not None and ('site_id' not in job.in_json or job.in_json['site_id'] != site_id):
continue
- job_tasks[job.job_id] = []
+ tasks = []
for task_id in job.in_json['task_ids']:
if task_id in tasks_map:
- job_tasks[job.job_id].append(tasks_map[task_id])
+ tasks.append(tasks_map[task_id])
+ scans_type = job.in_json['type'] if 'type' in job.in_json else 'solution'
+ scans_type_names = {'solution': 'Odevzdaná řešení', 'feedback': 'Opravená řešení'}
- jobs.append(job)
+ jobs.append((job, scans_type_names[scans_type], tasks))
return render_template(
'org_contest_scans.html',
ctx=ctx,
proc_form=proc_form,
proc_task_fields=proc_task_fields,
- jobs=jobs, job_tasks=job_tasks,
+ jobs=jobs,
)
@@ -1784,9 +1786,6 @@ def org_contest_scans_process(ct_id: int, job_id: int, site_id: Optional[int] =
contest = ctx.contest
assert contest
- if not ctx.rights.can_upload_feedback():
- raise werkzeug.exceptions.Forbidden()
-
sess = db.get_session()
# Získáme job a zkontrolujeme, že je to správný job, máme na něj práva a už doběhl
@@ -1800,6 +1799,18 @@ def org_contest_scans_process(ct_id: int, job_id: int, site_id: Optional[int] =
flash('Dávka naskenovaných úloh nebyla dosud dokončena.')
return redirect(ctx.url_for('org_contest_scans'))
+ if 'type' in job.in_json and job.in_json['type'] == 'feedback':
+ scans_type = 'feedback'
+ scans_type_name = 'oprava'
+ else:
+ scans_type = 'solution'
+ scans_type_name = 'řešení'
+
+ if scans_type == 'solution' and not ctx.rights.can_upload_solutions():
+ raise werkzeug.exceptions.Forbidden()
+ if scans_type == 'feedback' and not ctx.rights.can_upload_feedback():
+ raise werkzeug.exceptions.Forbidden()
+
pages = sess.query(db.ScanPage).filter_by(job_id=job_id).order_by('file_nr', 'page_nr').all()
tasks = sess.query(db.Task).filter(db.Task.task_id.in_(job.in_json['task_ids'])).order_by('code').all()
pion_query = sess.query(db.Participation).filter(
@@ -1891,14 +1902,14 @@ def org_contest_scans_process(ct_id: int, job_id: int, site_id: Optional[int] =
for pion in pions:
index = (task.task_id, pion.user_id)
if index not in sol_map:
- warnings.append(f'Chybí řešení úlohy {task.code} {task.name} účastníka {pion.user.full_name()}')
+ warnings.append(f'Chybí {scans_type_name} úlohy {task.code} {task.name} účastníka {pion.user.full_name()}')
if process_form.validate_on_submit() and process_form.process_all.data:
if len(errors) > 0:
flash('Nelze zpracovat, dokud kontrola vrací chyby. Nejdříve je opravte.')
return redirect(self_url)
mo.jobs.protocols.schedule_sort_scans(job_id, for_user=g.user)
- flash('Skeny zařazeny ke zpracování, během několika chvil se řešení uloží k soutěžícím.', 'success')
+ flash('Skeny zařazeny ke zpracování, během několika chvil se uloží k soutěžícím.', 'success')
return redirect(url_for('org_jobs'))
def png_small(page: db.ScanPage) -> str:
@@ -1917,6 +1928,7 @@ def org_contest_scans_process(ct_id: int, job_id: int, site_id: Optional[int] =
'org_contest_scans_process.html',
ctx=ctx,
job=job,
+ scans_type=scans_type,
tasks=tasks,
pions=pions,
pages=pages,
diff --git a/mo/web/templates/org_contest_scans.html b/mo/web/templates/org_contest_scans.html
index 70a5731c..40fbb320 100644
--- a/mo/web/templates/org_contest_scans.html
+++ b/mo/web/templates/org_contest_scans.html
@@ -33,6 +33,14 @@ jejich přiřazení jednotlivým soutěžícím.
</div>
{% endif %}
{{ field(proc_form.files) }}
+ <div class='form-group required'>
+ <label class='control-label col-lg-3'>Typ skenů</label>
+ <div class='col-lg-7'>
+ {% for f in proc_form.scans_type %}
+ <div class="radio"><label for="{{ f.id }}">{{ f(required="required") }}{{ f.label.text }}</label></div>
+ {% endfor %}
+ </div>
+ </div>
{{ field(proc_form.process_scans) }}
</form>
@@ -43,16 +51,18 @@ jejich přiřazení jednotlivým soutěžícím.
<thead>
<tr>
<th>Datum
+ <th>Typ skenů
<th>Úlohy
<th>Stav
{% if g.user.is_admin %}<th>Vlastník{% endif %}
<th>Akce
</tr>
</thead>
- {% for job in jobs %}
+ {% for (job, type, tasks) in jobs %}
<tr class="job-{{ job.state.name }}">
<td>{{ job.created_at|timeformat }}
- <td>{% for task in job_tasks[job.job_id] %}{{ task.code }} {% endfor %}
+ <td>{{ type }}
+ <td>{{ tasks|map(attribute='code')|join(', ') }}
<td>
{% if job.state == JobState.done %}
Připraveno k roztřídění
diff --git a/mo/web/templates/org_contest_scans_process.html b/mo/web/templates/org_contest_scans_process.html
index 20540ac1..03af33e3 100644
--- a/mo/web/templates/org_contest_scans_process.html
+++ b/mo/web/templates/org_contest_scans_process.html
@@ -5,11 +5,14 @@
<script src="{{ asset_url('js/autocomplete.js') }}" type="text/javascript"></script>
{% endblock %}
+{% set for_solutions = scans_type == 'solution' %}
+
+{% set scans_title = 'odevzdaných řešení' if for_solutions else 'oprav' %}
{% block title %}
-Třídění skenů pro {{ ctx.round.name|lower }} kategorie {{ ctx.round.category }}
+Třídění skenů {{ scans_title }} pro {{ ctx.round.name|lower }} kategorie {{ ctx.round.category }}
{% endblock %}
{% block breadcrumbs %}
-{{ ctx.breadcrumbs(action="Třídění skenů") }}
+{{ ctx.breadcrumbs(action="Třídění skenů " + scans_title) }}
{% endblock %}
{% block body %}
@@ -25,8 +28,8 @@ setTimeout(function () { location.reload(1); }, 10_000);
<p>Napravo můžete klikáním vybírat jednotlivé naskenované stránky a pomocí vrchních políček je přiřazovat jednotlivým úlohám a soutěžícím. Pokud
je nějaké řešení přes více stránek, musí na sebe navazovat číslování stránek. Až bude vše správně zatříděné, můžete aktuální stav uložit
-tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracování pomocí <b>[Ukončit a zpracovat]</b> (řešení se uloží k soutěžícím,
-tuto akci nelze vzít zpět).
+tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracování pomocí <b>[Ukončit a zpracovat]</b>
+({{ 'řešení' if for_solutions else 'opravy' }} se uloží k soutěžícím, tuto akci nelze vzít zpět).
{% if errors or warnings %}
<div class="collapsible">
--
GitLab