diff --git a/mo/jobs/protocols.py b/mo/jobs/protocols.py index fd9d08d5f8d18be9ee4187876f85053407150925..65b6b9e7d8e3970cfd63371be3697d9daf306d6d 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 60f7c92f80e72d1eb9ad6048f5f69035fce70b16..b76724c92a735585203fb2af482fc0699c71aeec 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 70a5731c4d4bddb3726a2c9f94267e49ba1fca4e..40fbb320cf04cd1ba6419578c545c8f2ce659155 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 20540ac1bb1abe80337275d558d713a6c638b756..03af33e3767c2553a215b3c54944205bad61850e 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">