diff --git a/mo/jobs/submit.py b/mo/jobs/submit.py index 7675c534cc6e3d5640bb4112fbb7f7cabb521464..e53beac142df65ebbc97dcea4e6f8405b09e5bd1 100644 --- a/mo/jobs/submit.py +++ b/mo/jobs/submit.py @@ -121,7 +121,16 @@ def parse_feedback_name(name: str) -> Optional[UploadFeedback]: ) # Formát jmen, pod kterými se ukládají jednotlivě stahovaná řešení - m = re.match(r'(?P<task>.+)-reseni-(?P<paper_id>\d+)\.', name) + m = re.match(r'(?P<task>[^_]+)_(reseni|opravene)_(?P<user_id>\d+)_', name) + if m: + return UploadFeedback( + file_name=name, + task_code=m['task'], + user_id=int(m['user_id']), + ) + + # Formát jmen, pod kterými se dříve ukládala jednotlivě stahovaná řešení + m = re.match(r'(?P<task>.+)-(reseni|opravene)-(?P<paper_id>\d+)\.', name) if m: paper = db.get_session().query(db.Paper).get(int(m['paper_id'])) if paper: diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index c135b139eba4cef91162e77b1f2b40892d7309da..ca0e026f455a57a119f5ec6d16e5b893e99d76e3 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -609,6 +609,7 @@ class SetFinalForm(FlaskForm): @app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/submit/<int:user_id>/<int:task_id>/', methods=('GET', 'POST')) def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Optional[int] = None): sc = get_solution_context(contest_id, user_id, task_id, site_id) + assert sc.user is not None sess = db.get_session() self_url = url_for('org_submit_list', contest_id=contest_id, user_id=user_id, task_id=task_id, site_id=site_id) @@ -733,13 +734,6 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option .order_by(db.PointsHistory.points_at.desc()) .all()) - def paper_link(paper: db.Paper) -> str: - return url_for('org_submit_paper', - contest_id=sc.contest.contest_id, - paper_id=paper.paper_id, - site_id=site_id, - filename=mo.web.util.task_paper_filename(paper)) - return render_template( 'org_submit_list.html', sc=sc, @@ -748,7 +742,7 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option fb_papers=fb_papers, points_history=points_history, for_site=(site_id is not None), - paper_link=paper_link, + paper_link=lambda p: mo.web.util.org_paper_link(sc.contest, sc.site, sc.user, p), form=form, set_final_form=set_final_form, ) @@ -763,7 +757,7 @@ def org_submit_paper(contest_id: int, paper_id: int, filename: str, site_id: Opt if not paper: raise werkzeug.exceptions.NotFound() - if filename != mo.web.util.task_paper_filename(paper): + if not filename.endswith('.pdf'): raise werkzeug.exceptions.NotFound() get_solution_context(contest_id, paper.for_user, paper.for_task, site_id) @@ -860,17 +854,10 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non ): paper_counts[(user_id, type.name)] = count - def paper_link(paper: db.Paper) -> str: - return url_for('org_submit_paper', - contest_id=sc.contest.contest_id, - paper_id=paper.paper_id, - site_id=site_id, - filename=mo.web.util.task_paper_filename(paper)) - return render_template( "org_contest_task.html", sc=sc, rows=rows, paper_counts=paper_counts, - paper_link=paper_link, + paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, sc.site, u, p), can_upload=sc.allow_upload_feedback, points_form=points_form, request_form=request.form, ) @@ -924,20 +911,13 @@ def org_contest_solutions(id: int, site_id: Optional[int] = None): for s in sols: task_sols[s.task_id][s.user_id] = s - def paper_link(paper: db.Paper) -> str: - return url_for('org_submit_paper', - contest_id=sc.contest.contest_id, - paper_id=paper.paper_id, - site_id=site_id, - filename=mo.web.util.task_paper_filename(paper)) - return render_template( 'org_contest_solutions.html', contest=sc.contest, site=sc.site, sc=sc, pions=pions, tasks=tasks, tasks_sols=task_sols, paper_counts=paper_counts, can_upload=sc.allow_upload_feedback, can_edit_points=sc.allow_edit_points, - paper_link=paper_link, + paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, sc.site, u, p), ) @@ -1120,14 +1100,9 @@ def org_contest_user(contest_id: int, user_id: int): ): paper_counts[(task_id, type.name)] = count - def paper_link(paper: db.Paper) -> str: - return url_for('org_submit_paper', - contest_id=sc.contest.contest_id, - paper_id=paper.paper_id, - filename=mo.web.util.task_paper_filename(paper)) - return render_template( 'org_contest_user.html', sc=sc, pant=pant, task_sols=task_sols, - paper_link=paper_link, paper_counts=paper_counts, + paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, None, u, p), + paper_counts=paper_counts, ) diff --git a/mo/web/org_score.py b/mo/web/org_score.py index 5ca3fd02332c3c815882c53b16b3152754146488..a9de01eecda04b76f4ee91b6d2989a54077eeaf7 100644 --- a/mo/web/org_score.py +++ b/mo/web/org_score.py @@ -39,10 +39,12 @@ class OrderCell(Cell): class SolPointsCell(Cell): contest_id: int + user: db.User sol: Optional[db.Solution] - def __init__(self, contest_id: int): + def __init__(self, contest_id: int, user: db.User): self.contest_id = contest_id + self.user = user self.sol = None def __str__(self) -> str: @@ -64,20 +66,10 @@ class SolPointsCell(Cell): points = str(self.sol.points) if self.sol.final_feedback_obj: - url = url_for( - 'org_submit_paper', - contest_id=self.contest_id, - paper_id=self.sol.final_feedback, - filename=mo.web.util.task_paper_filename(self.sol.final_feedback_obj) - ) + url = mo.web.util.org_paper_link(self.contest_id, None, self.user, self.sol.final_feedback_obj) return f'<td><a href="{url}" title="Zobrazit finální opravu">{points}</a>' elif self.sol.final_submit_obj: - url = url_for( - 'org_submit_paper', - contest_id=self.contest_id, - paper_id=self.sol.final_submit, - filename=mo.web.util.task_paper_filename(self.sol.final_submit_obj) - ) + url = mo.web.util.org_paper_link(self.contest_id, None, self.user, self.sol.final_submit_obj) return f'<td><a href="{url}" title="Zobrazit finální řešení od účastníka">{points}</a>' else: return f'<td>{points}' # no paper no link @@ -190,7 +182,7 @@ def org_score(round_id: Optional[int] = None, contest_id: Optional[int] = None): 'birth_year': pant.birth_year, }) for task in tasks: - row.keys[f'task_{task.task_id}'] = SolPointsCell(contest_id=pion.contest_id) + row.keys[f'task_{task.task_id}'] = SolPointsCell(contest_id=pion.contest_id, user=user) rows_map[user.user_id] = row for sol in sols: rows_map[sol.user_id].keys[f'task_{sol.task_id}'].set_sol(sol) diff --git a/mo/web/templates/org_contest_solutions.html b/mo/web/templates/org_contest_solutions.html index 5f2cda25c33ef0f5efe3a65d31229ac3fcd389ec..5606f4e6c1966a19b0b43315b419cd457716760e 100644 --- a/mo/web/templates/org_contest_solutions.html +++ b/mo/web/templates/org_contest_solutions.html @@ -52,7 +52,7 @@ konkrétní úlohu. Symbol <b>+</b> značí, že existuje více verzí dostupný {% set p = sol.final_submit_obj %} {% set late = p.check_deadline(round) %} <td class="sol {% if late %}sol-late{% endif %}"> - <a href="{{ paper_link(p) }}" title="{{ p.uploaded_at|timeformat }} - {{ p.pages|inflected('stránka', 'stránky', 'stránek') }}{% if late %} - {{ late }}{% endif %}">🖺</a> + <a href="{{ paper_link(u, p) }}" title="{{ p.uploaded_at|timeformat }} - {{ p.pages|inflected('stránka', 'stránky', 'stránek') }}{% if late %} - {{ late }}{% endif %}">🖺</a> {% set key = (u.user_id, task.task_id, "solution") %} {% if key in paper_counts and paper_counts[key] > 1 %} <b title="Celkem {{ paper_counts[key]|inflected('verze', 'verze', 'verzí') }}">+</b> @@ -63,7 +63,7 @@ konkrétní úlohu. Symbol <b>+</b> značí, že existuje více verzí dostupný <td class="sol"> {% if sol.final_feedback_obj %} {% set p = sol.final_feedback_obj %} - <a href="{{ paper_link(p) }}" title="{{ p.uploaded_at|timeformat }} - {{ p.pages|inflected('stránka', 'stránky', 'stránek') }}">🖺</a> + <a href="{{ paper_link(u, p) }}" title="{{ p.uploaded_at|timeformat }} - {{ p.pages|inflected('stránka', 'stránky', 'stránek') }}">🖺</a> {% set key = (u.user_id, task.task_id, "feedback") %} {% if key in paper_counts and paper_counts[key] > 1 %} <b title="Celkem {{ paper_counts[key]|inflected('verze', 'verze', 'verzí') }}">+</b> diff --git a/mo/web/templates/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html index 63387b8480e58c7a939d0eee2ff54663a87948ed..93d622e8d1e99aa631956006292bc7480753c0c0 100644 --- a/mo/web/templates/parts/org_solution_table.html +++ b/mo/web/templates/parts/org_solution_table.html @@ -35,7 +35,7 @@ finální (ve výchozím stavu poslední nahrané).{% endif %} {% set p = sol.final_submit_obj %} {% set late = p.check_deadline(round) %} {% if late %}<span class='sol-late' title="{{ late }}"><b>⚠</b></span> {% endif %} - <a href='{{ paper_link(p) }}'> + <a href='{{ paper_link(u, p) }}'> {{- p.uploaded_at|timeformat }} ({{ p.pages|inflected('strana', 'strany', 'stran') }}) </a> {% if p.uploaded_by_obj != u %} @@ -48,7 +48,7 @@ finální (ve výchozím stavu poslední nahrané).{% endif %} {% else %}–{% endif %} <td>{% if sol.final_feedback_obj %} {% set p = sol.final_feedback_obj %} - <a title="nahrál {{ p.uploaded_by_obj.full_name() }}" href='{{ paper_link(p) }}'> + <a title="nahrál {{ p.uploaded_by_obj.full_name() }}" href='{{ paper_link(u, p) }}'> {{ (p.uploaded_at if p else None)|timeformat }} ({{ p.pages|inflected('strana', 'strany', 'stran') }}) </a> {% set key = (obj.task_id if for_user else obj.user_id, "feedback") %} diff --git a/mo/web/util.py b/mo/web/util.py index f021d878e20400ca16c6a68628d396a4c2af3f6b..56dc2e9b494bbeb79f825932bde89c7c98d6f397 100644 --- a/mo/web/util.py +++ b/mo/web/util.py @@ -1,8 +1,9 @@ -from flask import Response, send_file +from flask import Response, send_file, url_for from flask_wtf import FlaskForm import os from sqlalchemy.orm.query import Query -from typing import Tuple +from typing import Tuple, Optional, Union +import unicodedata import werkzeug.exceptions import werkzeug.utils import wtforms @@ -49,19 +50,39 @@ def send_task_statement(round: db.Round) -> Response: raise werkzeug.exceptions.NotFound() -def task_paper_filename(paper: db.Paper) -> str: - """Doporučujeme preloadovat task.""" +def _task_paper_filename(user: db.User, paper: db.Paper) -> str: + # Tato jména parsuje dávkový upload - task = paper.task + full_name = user.full_name() + ascii_name = (unicodedata.normalize('NFD', full_name) + .encode('ascii', 'ignore') + .decode('utf-8')) - secure_task_code = werkzeug.utils.secure_filename(task.code) if paper.type == db.PaperType.solution: - return f'{secure_task_code}-reseni-{paper.paper_id}.pdf' + typ = 'reseni' elif paper.type == db.PaperType.feedback: - return f'{secure_task_code}-opravene-{paper.paper_id}.pdf' + typ = 'opravene' else: assert False + fn = f'{paper.task.code}_{typ}_{user.user_id}_{paper.paper_id}_{ascii_name}.pdf' + return werkzeug.utils.secure_filename(fn) + + +def org_paper_link(contest_or_id: Union[db.Contest, int], + site: Optional[db.Place], + user: db.User, + paper: db.Paper) -> str: + """Doporučujeme preloadovat paper.task.""" + if isinstance(contest_or_id, db.Contest): + contest_or_id = contest_or_id.contest_id + + return url_for('org_submit_paper', + contest_id=contest_or_id, + paper_id=paper.paper_id, + site_id=site.place_id if site else None, + filename=_task_paper_filename(user, paper)) + def send_task_paper(paper: db.Paper) -> Response: file = os.path.join(mo.util.data_dir('submits'), paper.file_name)