From 537088771c77f1c2eadd06176d299cc312efeaa9 Mon Sep 17 00:00:00 2001 From: Martin Mares <mj@ucw.cz> Date: Thu, 1 Jul 2021 15:31:19 +0200 Subject: [PATCH] Protokoly: Job na sazbu --- mo/jobs/__init__.py | 1 + mo/jobs/protocols.py | 140 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 mo/jobs/protocols.py diff --git a/mo/jobs/__init__.py b/mo/jobs/__init__.py index d37becbc..77be8ca6 100644 --- a/mo/jobs/__init__.py +++ b/mo/jobs/__init__.py @@ -186,4 +186,5 @@ def job_handler(type: db.JobType): # Moduly implementující jednotlivé typy jobů +import mo.jobs.protocols import mo.jobs.submit diff --git a/mo/jobs/protocols.py b/mo/jobs/protocols.py new file mode 100644 index 00000000..2655f26b --- /dev/null +++ b/mo/jobs/protocols.py @@ -0,0 +1,140 @@ +# Implementace jobů na práci s protokoly + +import os +import re +from sqlalchemy.orm import joinedload +import subprocess +from typing import List, Optional + +import mo +import mo.db as db +from mo.jobs import TheJob, job_handler +from mo.util import logger, part_path +import mo.util_format + + +# +# Job create_protocols: Vygeneruje formuláře protokolů +# +# Vstupní JSON: +# { 'contest_id': ID contestu, +# 'site_id': ID soutěžního místa nebo none, +# 'task_ids': [task_id, ...], +# 'num_universal': počet papírů s univerzalní hlavičkou, +# 'num_blank': pocet pokračovacích papírů, +# } +# +# Výstupní JSON: +# null +# + + +def schedule_create_protocols(contest: db.Contest, site: Optional[db.Place], for_user: db.User, tasks: List[db.Task], num_universal: int, num_blank: int): + place = site or contest.place + + the_job = TheJob() + job = the_job.create(db.JobType.create_protocols, for_user) + job.description = f'Formuláře protokolů {contest.round.round_code_short()} {place.name}' + job.in_json = { + 'contest_id': contest.contest_id, + 'site_id': site.place_id if site else None, + 'task_ids': [t.task_id for t in tasks], + 'num_universal': num_universal, + 'num_blank': num_blank, + } + the_job.submit() + + +def tex_arg(s: str) -> str: + # Primitivní escapování do TeXu. Nesnaží se ani tak o věrnou intepretaci všech znaků, + # jako o zabránění pádu TeXu kvůli divným znakům. + s = re.sub(r'[\\{}#$%^~]', '?', s) + s = re.sub(r'([&_])', r'\\\1', s) + return '{' + s + '}' + + +@job_handler(db.JobType.create_protocols) +def handle_create_protocols(the_job: TheJob): + job = the_job.job + assert job.in_json is not None + contest_id = job.in_json['contest_id'] # type: ignore + site_id = job.in_json['site_id'] # type: ignore + task_ids = job.in_json['task_ids'] # type: ignore + num_universal = job.in_json['num_universal'] # type: ignore + num_blank = job.in_json['num_blank'] # type: ignore + + sess = db.get_session() + contest = sess.query(db.Contest).options(joinedload(db.Contest.round)).get(contest_id) + assert contest is not None + round = contest.round + + user_subq = sess.query(db.Participation.user_id).filter_by(contest=contest) + if site_id is not None: + user_subq = user_subq.filter_by(place_id=site_id) + user_subq = (user_subq + .filter(db.Participation.state.in_((db.PartState.invited, db.PartState.registered, db.PartState.present))) + .subquery()) + + pants = (sess.query(db.Participant) + .options(joinedload(db.Participant.user), joinedload(db.Participant.school_place)) + .filter(db.Participant.user_id.in_(user_subq)) + .all()) + pants.sort(key=lambda p: p.user.sort_key()) + + tasks = sess.query(db.Task).filter_by(round=round).filter(db.Task.task_id.in_(task_ids)).order_by(db.Task.code).all() + + pages = [] + for p in pants: + for t in tasks: + args = [ + ':'.join(['MO', round.round_code_short(), t.code, str(p.user_id)]), + p.user.full_name(), + p.grade, + p.school_place.name, + t.code, + ] + pages.append('\\proto' + "".join([tex_arg(x) for x in args])) + + for _ in range(num_universal): + pages.append('\\universal') + + for _ in range(num_blank): + pages.append('\\blank') + + if not pages: + the_job.error("Nebyly vyžádány žádné protokoly") + return + + temp_dir = job.dir_path() + logger.debug('Job: Vytvářím protokoly v %s (%s listů)', temp_dir, len(pages)) + + tex_src = os.path.join(temp_dir, 'protokoly.tex') + with open(tex_src, 'w') as f: + f.write('\\input protokol.tex\n\n') + kolo = f'{round.name} {round.year}. ročníku Matematické olympiády' + kat = f'Kategorie {round.category}' + if round.level > 0: + kat += ', ' + contest.place.name + f.write('\\def\\kolo' + tex_arg(kolo) + '\n\n') + f.write('\\def\\kat' + tex_arg(kat) + '\n\n') + + for p in pages: + f.write(p + '\n') + + f.write('\n\\bye\n') + + env = dict(os.environ) + env['TEXINPUTS'] = part_path('tex') + '//:' + + subprocess.run( + ['luatex', '--interaction=errorstopmode', 'protokoly.tex'], + check=True, + cwd=temp_dir, + env=env, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + job.out_file = 'protokoly.pdf' + job.result = 'Celkem ' + mo.util_format.inflect_number(len(pages), 'list', 'listy', 'listů') -- GitLab