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