Skip to content
Snippets Groups Projects
Commit 7fcaf0d7 authored by Martin Mareš's avatar Martin Mareš
Browse files

Jobs: Upload feedbacku

parent aa100d8b
No related branches found
No related tags found
3 merge requests!19Reforma vyhodnocování práv,!18Dávky okolo feedbacku,!17Výsledkovka pomocí mo.web.table
This commit is part of merge request !17. Comments created here will be created in the context of that merge request.
# Implementace jobů pracujících se submity # Implementace jobů pracujících se submity
from dataclasses import dataclass
import os import os
import re
import shutil
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import List from typing import List, Optional
import unicodedata import unicodedata
import werkzeug.utils import werkzeug.utils
import zipfile import zipfile
from mo.util import logger, data_dir
import mo.db as db import mo.db as db
from mo.jobs import TheJob, job_handler from mo.jobs import TheJob, job_handler, job_file_path
import mo.rights
from mo.submit import Submitter, SubmitException
from mo.util import logger, data_dir
from mo.util_format import inflect_number, inflect_by_number
def schedule_download_submits(paper_ids: List[int], description: str, for_user: db.User): def schedule_download_submits(paper_ids: List[int], description: str, for_user: db.User):
tj = TheJob() the_job = TheJob()
job = tj.create(db.JobType.download_submits, for_user) job = the_job.create(db.JobType.download_submits, for_user)
job.description = description job.description = description
job.in_json = {'papers': paper_ids} job.in_json = {'papers': paper_ids}
tj.submit() the_job.submit()
@job_handler(db.JobType.download_submits) @job_handler(db.JobType.download_submits)
def handle_download_submits(tj: TheJob): def handle_download_submits(the_job: TheJob):
"""Zazipuje zadané papíry. """Zazipuje zadané papíry.
Vstupní JSON: Vstupní JSON:
...@@ -32,10 +38,9 @@ def handle_download_submits(tj: TheJob): ...@@ -32,10 +38,9 @@ def handle_download_submits(tj: TheJob):
null null
""" """
job = tj.job job = the_job.job
assert job.in_json is not None assert job.in_json is not None
# FIXME: Typování JSONu... ids = job.in_json['papers'] # type: ignore
ids = job.in_json['papers']
sess = db.get_session() sess = db.get_session()
papers = (sess.query(db.Paper) papers = (sess.query(db.Paper)
...@@ -43,10 +48,10 @@ def handle_download_submits(tj: TheJob): ...@@ -43,10 +48,10 @@ def handle_download_submits(tj: TheJob):
.options(joinedload(db.Paper.for_user_obj), .options(joinedload(db.Paper.for_user_obj),
joinedload(db.Paper.task)) joinedload(db.Paper.task))
.all()) .all())
papers.sort(key=lambda p: (p.for_user_obj.sort_key(), p.task.code))
temp_file = NamedTemporaryFile(suffix='.zip', dir=data_dir('tmp'), mode='w+b') temp_file = NamedTemporaryFile(suffix='.zip', dir=data_dir('tmp'), mode='w+b')
logger.debug('Job: Vytvářím archiv %s', temp_file.name) logger.debug('Job: Vytvářím archiv %s', temp_file.name)
# FIXME: Setřídit soubory
cnt = 0 cnt = 0
with zipfile.ZipFile(temp_file, mode='w') as zip: with zipfile.ZipFile(temp_file, mode='w') as zip:
...@@ -62,10 +67,175 @@ def handle_download_submits(tj: TheJob): ...@@ -62,10 +67,175 @@ def handle_download_submits(tj: TheJob):
zip.write(filename=os.path.join(data_dir('submits'), p.file_name), zip.write(filename=os.path.join(data_dir('submits'), p.file_name),
arcname=fn) arcname=fn)
job.out_file = tj.attach_file(temp_file.name, '.zip') job.out_file = the_job.attach_file(temp_file.name, '.zip')
job.result = 'Celkem ' + inflect_number(cnt, 'soubor', 'soubory', 'souborů')
temp_file.close() temp_file.close()
def schedule_upload_feedback(round: db.Round, tmp_file: str, description: str, for_user: db.User):
the_job = TheJob()
job = the_job.create(db.JobType.upload_feedback, for_user)
job.description = description
job.in_json = {'round_id': round.round_id}
job.in_file = the_job.attach_file(tmp_file, '.zip')
the_job.submit()
@dataclass
class UploadFeedback:
file_name: str
task_code: str
user_id: int
task: Optional[db.Task] = None
user: Optional[db.User] = None
tmp_name: Optional[str] = None
def parse_feedback_name(name: str) -> Optional[UploadFeedback]:
name = os.path.basename(name)
m = re.match(r'(?P<task>[^_]+)_(?P<order>\d+)_(?P<user_id>\d+)_', name)
if m:
return UploadFeedback(
file_name=name,
task_code=m['task'],
user_id=int(m['user_id']),
)
else:
return None
@job_handler(db.JobType.upload_feedback) @job_handler(db.JobType.upload_feedback)
def handle_upload_feedback(tj: TheJob): def handle_upload_feedback(the_job: TheJob):
raise NotImplementedError() """Uploaduje opravená řešení.
Vstupní JSON:
{ 'round_id': <id> }
Výstupní JSON:
null
"""
job = the_job.job
assert job.in_json is not None
assert job.in_file is not None
round_id = job.in_json['round_id'] # type: ignore
sess = db.get_session()
round = sess.query(db.Round).get(round_id)
assert round is not None
files: List[UploadFeedback] = []
def parse_zip(in_path: str):
try:
with zipfile.ZipFile(in_path, mode='r') as zip:
contents = zip.infolist()
for item in contents:
if not item.is_dir():
fb = parse_feedback_name(item.filename)
if fb:
tmp_file = NamedTemporaryFile(dir=data_dir('tmp'), mode='w+b', delete=False)
logger.debug(f'Job: Extrahuji {item.filename} do {tmp_file.name}')
with zip.open(item) as item_file:
shutil.copyfileobj(item_file, tmp_file)
tmp_file.close()
fb.tmp_name = tmp_file.name
files.append(fb)
else:
the_job.error(f'Nerozpoznáno jméno souboru {item.filename}')
except zipfile.BadZipFile:
the_job.error('Chybný formát souboru. Je to opravdu ZIP?')
def resolve_tasks(files):
task_dict = {f.task_code: None for f in files}
tasks = sess.query(db.Task).filter_by(round=round).filter(db.Task.code.in_(task_dict.keys())).all()
for task in tasks:
task_dict[task.code] = task
for code, task in task_dict.items():
if task is None:
the_job.error(f'Neznámá úloha {code}')
for f in files:
f.task = task_dict[f.task_code]
if f.task is None:
the_job.error(f'{f.file_name}: Neznámá úloha {code}')
def resolve_users(files):
user_dict = {f.user_id: None for f in files}
rows = (sess.query(db.User, db.Participation, db.Contest)
.select_from(db.Participation)
.join(db.User, db.User.user_id == db.Participation.user_id)
.join(db.Contest, db.Contest.contest_id == db.Participation.contest_id)
.filter(db.Contest.round == round)
.filter(db.Participation.user_id.in_(user_dict.keys()))
.all())
rr = mo.rights.Rights(job.user)
rights_cache = {}
user_rights = {}
for user, pion, contest in rows:
user_dict[user.user_id] = user
if contest.contest_id not in rights_cache:
rr.get_for_contest(contest)
rights_cache[contest.contest_id] = (
rr.have_right(mo.rights.Right.upload_submits)
or (rr.have_right(mo.rights.Right.upload_feedback) and round.state == db.RoundState.grading))
user_rights[user.user_id] = rights_cache[contest.contest_id]
for f in files:
f.user = user_dict[f.user_id]
if not f.user:
the_job.error(f'{f.file_name}: Neznámý účastník #{f.user_id}')
elif not user_rights[f.user_id]:
the_job.error(f'{f.file_name}: K tomuto účastníkovi nemáte dostatečná oprávnění')
def process_file(fb: UploadFeedback) -> bool:
assert fb.user and fb.task
paper = db.Paper(
for_user_obj=fb.user,
task=fb.task,
type=db.PaperType.feedback,
uploaded_by_obj=job.user,
)
try:
assert fb.tmp_name
smtr = Submitter()
smtr.submit_paper(paper, fb.tmp_name)
fb.tmp_name = None # Soubor byl přesunut, není ho třeba mazat
sess.add(paper)
sol = (sess.query(db.Solution)
.filter_by(task=fb.task, user=fb.user)
.with_for_update()
.one())
sol.final_feedback_obj = paper
sess.commit()
return True
except SubmitException as e:
the_job.error(f'{fb.file_name}: {e}')
return False
cnt_good = 0
parse_zip(job_file_path(job.in_file))
if not the_job.errors:
resolve_tasks(files)
resolve_users(files)
if not the_job.errors:
for f in files:
if process_file(f):
cnt_good += 1
for f in files:
if f.tmp_name is not None:
os.unlink(f.tmp_name)
job.result = (inflect_by_number(cnt_good, 'Nahrán', 'Nahrány', 'Nahráno')
+ f' {cnt_good} z '
+ inflect_number(len(files), 'souboru', 'souborů', 'souborů'))
if the_job.errors:
job.result += (', '
+ inflect_by_number(len(the_job.errors), 'nastala', 'nastaly', 'nastalo')
+ ' '
+ inflect_number(len(the_job.errors), 'chyba', 'chyby', 'chyb'))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment