diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index e9fd7c041128ccfddd56c7feed3da18e7e2502b5..839b34359fabf0ce71981dd16e2929b2555d74e2 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -615,6 +615,7 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio user=user, task=task, site=site, + # XXX: Potřebujeme tohle všechno? Nechceme spíš vracet rr a nechat každého, ať na něm volá metody? allow_view=allow_view, allow_upload_solutions=rr.can_upload_solutions(round), allow_upload_feedback=rr.can_upload_feedback(round), @@ -1153,6 +1154,69 @@ def org_contest_task_upload(contest_id: int, task_id: int, site_id: Optional[int can_upload_feedback=sc.allow_upload_feedback) +class BatchPointsForm(FlaskForm): + file = flask_wtf.file.FileField("Soubor") + fmt = wtforms.SelectField( + "Formát souboru", + choices=FileFormat.choices(), coerce=FileFormat.coerce, + default=FileFormat.cs_csv, + ) + submit = wtforms.SubmitField('Nahrát body') + get_template = wtforms.SubmitField('Stáhnout šablonu') + + +def generic_batch_points(round: db.Round, contest: Optional[db.Contest], task: db.Task): + """Společná funkce pro download/upload bodů do soutěží a kol.""" + + form = BatchPointsForm() + errs = [] + if form.validate_on_submit(): + fmt = form.fmt.data + imp = create_import(user=g.user, type=ImportType.points, fmt=fmt, round=round, contest=contest, task=task) + if form.submit.data: + if form.file.data is not None: + file = form.file.data.stream + import_tmp = mo.util.link_to_dir(file.name, mo.util.data_dir('imports'), suffix='.csv') + + if imp.run(import_tmp): + if imp.cnt_rows == 0: + flash('Soubor neobsahoval žádné řádky s daty', 'danger') + else: + flash(f'Importováno ({imp.cnt_rows} řádků, změněny body u {imp.cnt_set_points} řešení)', 'success') + if contest is not None: + return redirect(url_for('org_contest', id=contest.contest_id)) + else: + return redirect(url_for('org_round', id=round.round_id)) + else: + errs = imp.errors + else: + flash('Vyberte si prosím soubor', 'danger') + elif form.get_template.data: + out = imp.get_template() + resp = app.make_response(out) + resp.content_type = fmt.get_content_type() + resp.headers.add('Content-Disposition', 'attachment; filename=OSMO-' + imp.template_basename + '.' + fmt.get_extension()) + return resp + + return render_template( + 'org_generic_batch_points.html', + round=round, contest=contest, task=task, + form=form, + errs=errs, + ) + + +@app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/batch-points', methods=('GET', 'POST')) +def org_contest_task_batch_points(contest_id: int, task_id: int): + sc = get_solution_context(contest_id, None, task_id, None) + assert sc.task is not None + + if not sc.allow_edit_points: + raise werkzeug.exceptions.Forbidden() + + return generic_batch_points(round=sc.round, contest=sc.contest, task=sc.task) + + @app.route('/org/contest/c/<int:contest_id>/user/<int:user_id>') def org_contest_user(contest_id: int, user_id: int): sc = get_solution_context(contest_id, user_id, None, None) diff --git a/mo/web/org_round.py b/mo/web/org_round.py index 63b268b20c8f04609a3e533582e9563e334b24e0..7e86f508f75e4886a3b6b5b76889140678c98dec 100644 --- a/mo/web/org_round.py +++ b/mo/web/org_round.py @@ -1,4 +1,3 @@ -import datetime from flask import render_template, g, redirect, url_for, flash, request import locale from flask_wtf.form import FlaskForm @@ -17,7 +16,7 @@ from mo.rights import Right, Rights import mo.util from mo.web import app from mo.web.org_contest import ParticipantsActionForm, ParticipantsFilterForm, get_contestants_query, make_contestant_table, \ - generic_import, generic_batch_download, generic_batch_upload + generic_import, generic_batch_download, generic_batch_upload, generic_batch_points def get_round(id: int) -> db.Round: @@ -38,6 +37,13 @@ def get_round_rr(id: int, right_needed: Optional[Right], any_place: bool) -> Tup return round, rr +def get_task(round: db.Round, task_id: int) -> db.Task: + task = db.get_session().query(db.Task).get(task_id) + if not task or task.round_id != round.round_id: + raise werkzeug.exceptions.NotFound() + return task + + @app.route('/org/contest/') def org_rounds(): sess = db.get_session() @@ -271,30 +277,27 @@ def org_round_task_edit(id: int, task_id: int): @app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/download', methods=('GET', 'POST')) def org_round_task_download(round_id: int, task_id: int): - sess = db.get_session() round, rr = get_round_rr(round_id, Right.view_submits, False) - - task = sess.query(db.Task).get(task_id) - if not task or task.round_id != round_id: - raise werkzeug.exceptions.NotFound() - + task = get_task(round, task_id) return generic_batch_download(round=round, contest=None, site=None, task=task) @app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/upload', methods=('GET', 'POST')) def org_round_task_upload(round_id: int, task_id: int): - sess = db.get_session() round, rr = get_round_rr(round_id, Right.view_submits, False) - - task = sess.query(db.Task).get(task_id) - if not task or task.round_id != round_id: - raise werkzeug.exceptions.NotFound() - + task = get_task(round, task_id) return generic_batch_upload(round=round, contest=None, site=None, task=task, can_upload_solutions=rr.can_upload_solutions(round), can_upload_feedback=rr.can_upload_feedback(round)) +@app.route('/org/contest/r/<int:round_id>/task/<int:task_id>/batch-points', methods=('GET', 'POST')) +def org_round_task_batch_points(round_id: int, task_id: int): + round, rr = get_round_rr(round_id, Right.edit_points, True) + task = get_task(round, task_id) + return generic_batch_points(round=round, contest=None, task=task) + + @app.route('/org/contest/r/<int:id>/list', methods=('GET', 'POST')) def org_round_list(id: int): round, rr = get_round_rr(id, Right.manage_contest, True) diff --git a/mo/web/templates/org_generic_batch_points.html b/mo/web/templates/org_generic_batch_points.html new file mode 100644 index 0000000000000000000000000000000000000000..2624602807d49eba9846dcf98ace517078602c41 --- /dev/null +++ b/mo/web/templates/org_generic_batch_points.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% import "bootstrap/wtf.html" as wtf %} + +{% block title %}Dávkové bodování úlohy {{ task.code }} {{ task.name }}{% endblock %} +{% block breadcrumbs %} +{{ contest_breadcrumbs(round=round, contest=contest, task=task, action="Dávkové bodování") }} +{% endblock %} + +{% block body %} + +{% if errs %} +<h3>Chyby při importu</h3> + +<pre><div class="alert alert-danger" role="alert">{{ "" -}} +{% for e in errs %} +{{ e }} +{% endfor %} +</div></pre> +{% endif %} + +<p>Zde si můžete stáhnout bodovací formulář v zadaném formátu a pak ho nahrát zpět +s vyplněnými body. + +{{ wtf.quick_form(form, form_type='simple', button_map={'submit': 'primary'}) }} + +{% endblock %}