diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index 8b0d5edd4a3e56b8ac1aed91cf37c9816a0d3cbb..98546219b4da0e50be3c43a289e41cec62b32001 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -353,6 +353,7 @@ def org_contest(id: int, site_id: Optional[int] = None): can_manage=rr.have_right(Right.manage_contest), can_upload=rr.can_upload_feedback(round), can_edit_points=rr.can_edit_points(round), + can_create_solutions=rr.can_upload_feedback(round) or rr.can_upload_solutions(round), can_view_statement=rr.can_view_statement(round), tasks=tasks, places_counts=places_counts, ) @@ -556,6 +557,7 @@ class SolutionContext: allow_view: bool allow_upload_solutions: bool allow_upload_feedback: bool + allow_create_solutions: bool allow_edit_points: bool @@ -608,6 +610,8 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio if not allow_view: raise werkzeug.exceptions.Forbidden() + allow_upload_solutions = rr.can_upload_solutions(round) + allow_upload_feedback = rr.can_upload_feedback(round) return SolutionContext( contest=contest, round=round, @@ -617,8 +621,9 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio 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), + allow_upload_solutions=allow_upload_solutions, + allow_upload_feedback=allow_upload_feedback, + allow_create_solutions=allow_upload_solutions or allow_upload_feedback, allow_edit_points=rr.can_edit_points(round), ) @@ -633,6 +638,7 @@ class SubmitForm(FlaskForm): file_note = wtforms.TextAreaField("Poznámka k souboru") submit_sol = wtforms.SubmitField('Uložit a nahrát soubor jako řešení') submit_fb = wtforms.SubmitField('Uložit a nahrát soubor jako opravu') + delete = wtforms.SubmitField('Smazat řešení') class SetFinalForm(FlaskForm): @@ -704,6 +710,24 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option form = SubmitForm(obj=sol) if form.validate_on_submit(): + if sol and form.delete.data: + if sol.final_submit or sol.final_feedback: + flash('Nelze smazat řešení, ke kterému již byl odevzdán soubor', 'danger') + else: + flash('Řešení smazáno', 'success') + sess.delete(sol) + mo.util.log( + type=db.LogType.participant, + what=sc.user.user_id, + details={ + 'action': 'solution-removed', + 'task': task_id, + }, + ) + sess.commit() + app.logger.info(f"Řešení úlohy {sc.task.code} od účastníka {sc.user.user_id} smazáno") + return redirect(self_url) + points = form.points.data # Checks if sol and sc.allow_edit_points and points and points < 0: @@ -713,6 +737,21 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option flash('Schází soubor k nahrání, žádné změny nebyly uloženy', 'danger') return redirect(self_url) + if not sol and (sc.allow_edit_points or sc.allow_upload_solutions or sc.allow_upload_feedback): + flash('Řešení založeno', 'success') + sol = db.Solution(task=sc.task, user=sc.user) + sess.add(sol) + mo.util.log( + type=db.LogType.participant, + what=sc.user.user_id, + details={ + 'action': 'solution-created', + 'task': task_id, + }, + ) + sess.commit() + app.logger.info(f"Řešení úlohy {sc.task.code} od účastníka {sc.user.user_id} založeno") + # Edit sol and points if sol and sc.allow_edit_points: # Sol edit @@ -768,16 +807,6 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option return redirect(self_url) sess.add(paper) - - # FIXME: Bylo by hezké použít INSERT ... ON CONFLICT UPDATE - # (SQLAlchemy to umí, ale ne přes ORM, jen core rozhraním) - sol = (sess.query(db.Solution) - .filter_by(user_id=user_id, task_id=task_id) - .with_for_update() - .one_or_none()) - if sol is None: - sol = db.Solution(task=sc.task, user=sc.user) - sess.add(sol) if type == db.PaperType.solution: sol.final_submit_obj = paper else: @@ -874,20 +903,24 @@ class TaskPointsForm(FlaskForm): submit = wtforms.SubmitField("Uložit body") +class TaskCreateForm(FlaskForm): + submit = wtforms.SubmitField("Založit označená řešení") + + @app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/') @app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/task/<int:task_id>/') @app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/points', methods=('GET', 'POST'), endpoint="org_contest_task_points") +@app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/create', methods=('GET', 'POST'), endpoint="org_contest_task_create") +@app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/task/<int:task_id>/create', methods=('GET', 'POST'), endpoint="org_contest_task_create") def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = None): sc = get_solution_context(contest_id, None, task_id, site_id) - edit_points = request.endpoint == "org_contest_task_points" - if edit_points: - assert site_id is None - if not sc.allow_edit_points: - raise werkzeug.exceptions.Forbidden() - if sc.round.state != db.RoundState.grading: - flash("Kolo není ve stavu opravování, nelze zadávat body", "warning") - return redirect(url_for('org_contest_task', contest_id=contest_id, task_id=task_id)) + action_create = request.endpoint == "org_contest_task_create" + action_points = request.endpoint == "org_contest_task_points" + if action_create and not sc.allow_create_solutions: + raise werkzeug.exceptions.Forbidden() + if action_points and not sc.allow_edit_points: + raise werkzeug.exceptions.Forbidden() sess = db.get_session() @@ -896,8 +929,41 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non rows.sort(key=lambda r: r[0].user.sort_key()) points_form: Optional[TaskPointsForm] = None + create_form: Optional[TaskCreateForm] = None + + if action_create: + create_form = TaskCreateForm() + if create_form.validate_on_submit(): + new_sol_count = 0 + for pion, sol in rows: + if sol: + continue # již existuje + if not request.form.get(f"create_sol_{pion.user_id}"): + continue # nikdo nežádá o vytvoření + + sol = db.Solution(task=sc.task, user=pion.user) + sess.add(sol) + mo.util.log( + type=db.LogType.participant, + what=pion.user_id, + details={ + 'action': 'solution-created', + 'task': task_id, + }, + ) + app.logger.info(f"Řešení úlohy {sc.task.code} od účastníka {pion.user_id} založeno") + new_sol_count += 1 + + if new_sol_count > 0: + sess.commit() + flash(inflect_by_number(new_sol_count, "Založeno", "Založena", "Založeno") + ' ' + + inflect_number(new_sol_count, "nové řešení", "nová řešení", "nových řešení"), + "success") + else: + flash("Žádné změny k uložení", "info") + return redirect(url_for('org_contest_task', contest_id=contest_id, task_id=task_id, site_id=site_id)) - if edit_points: + if action_points: points_form = TaskPointsForm() if points_form.validate_on_submit(): count = 0 @@ -906,10 +972,13 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non if sol is None: continue points = request.form.get(f"points_{sol.user_id}", type=int) - if points and points < 0: + if points is None: + continue + if points < 0: flash('Nelze zadat záporné body', 'danger') ok = False break + if points != sol.points: # Save points sol.points = points @@ -924,7 +993,7 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non if ok: if count > 0: sess.commit() - flash("Změněny body u " + inflect_number(count, "řešitele", "řešitelů", "řešitelů"), "success") + flash("Změněny body u " + inflect_number(count, "řešení", "řešení", "řešení"), "success") else: flash("Žádné změny k uložení", "info") return redirect(url_for('org_contest_task', contest_id=contest_id, task_id=task_id)) @@ -943,17 +1012,26 @@ def org_contest_task(contest_id: int, task_id: int, site_id: Optional[int] = Non "org_contest_task.html", sc=sc, rows=rows, paper_counts=paper_counts, 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, + points_form=points_form, create_form=create_form, request_form=request.form, ) +class ContestSolutionsEditForm(FlaskForm): + submit = wtforms.SubmitField("Založit označená řešení") + + @app.route('/org/contest/c/<int:id>/solutions', methods=('GET', 'POST')) @app.route('/org/contest/c/<int:id>/site/<int:site_id>/solutions', methods=('GET', 'POST')) +@app.route('/org/contest/c/<int:id>/solutions/edit', methods=('GET', 'POST'), endpoint="org_contest_solutions_edit") +@app.route('/org/contest/c/<int:id>/site/<int:site_id>/solutions/edit', methods=('GET', 'POST'), endpoint="org_contest_solutions_edit") def org_contest_solutions(id: int, site_id: Optional[int] = None): sc = get_solution_context(id, None, None, site_id) sess = db.get_session() + edit_action = request.endpoint == "org_contest_solutions_edit" + if edit_action and not sc.allow_create_solutions: + raise werkzeug.exceptions.Forbidden() + pions_subq = sess.query(db.Participation.user_id).filter_by(contest=sc.contest) if sc.site: pions_subq = pions_subq.filter_by(place=sc.site) @@ -996,13 +1074,46 @@ def org_contest_solutions(id: int, site_id: Optional[int] = None): for s in sols: task_sols[s.task_id][s.user_id] = s + edit_form: Optional[ContestSolutionsEditForm] = None + if edit_action: + edit_form = ContestSolutionsEditForm() + if edit_form.validate_on_submit(): + new_sol_count = 0 + for task in tasks: + for pion in pions: + if pion.user_id in task_sols[task.task_id]: + continue # již existuje + if not request.form.get(f"create_sol_{task.task_id}_{pion.user_id}"): + continue # nikdo nežádá o vytvoření + + sol = db.Solution(task=task, user=pion.user) + sess.add(sol) + mo.util.log( + type=db.LogType.participant, + what=pion.user_id, + details={ + 'action': 'solution-created', + 'task': task.task_id, + }, + ) + app.logger.info(f"Řešení úlohy {task.code} od účastníka {pion.user_id} založeno") + new_sol_count += 1 + + if new_sol_count > 0: + sess.commit() + flash(inflect_by_number(new_sol_count, "Založeno", "Založena", "Založeno") + ' ' + + inflect_number(new_sol_count, "nové řešení", "nová řešení", "nových řešení"), + "success") + else: + flash("Žádné změny k uložení", "info") + return redirect(url_for('org_contest_solutions', id=id, site_id=site_id)) + 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=lambda u, p: mo.web.util.org_paper_link(sc.contest, sc.site, u, p), + edit_form=edit_form, ) diff --git a/mo/web/templates/org_contest.html b/mo/web/templates/org_contest.html index ada65912f6a48801b89e8b4fd29d18141befc395..510d74c2ed9320cc8f008fdef6d69a0a8bfb27ee 100644 --- a/mo/web/templates/org_contest.html +++ b/mo/web/templates/org_contest.html @@ -84,7 +84,8 @@ <th>Kód <th>Název <th>Odevzdaná řešení - <th>Akce + <th>Jednotlivé akce + <th>Dávkové operace </tr> </thead> {% for task in tasks %} @@ -94,12 +95,19 @@ <td>{{ task.sol_count }} <td><div class="btn-group"> <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Odevzdaná řešení</a> - <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Stáhnout</a> + {% if not site and can_edit_points %} + <a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Zadat body</a> + {% endif %} + {% if can_create_solutions %} + <a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_create', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Založit řešení</a> + {% endif %} + </div> + <td><div class="btn-group"> + <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Stáhnout ZIP</a> {% if can_upload %} - <a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Nahrát</a> + <a class='btn btn-xs btn-default' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, task_id=task.task_id, site_id=site_id) }}">Nahrát ZIP</a> {% endif %} {% if not site and can_edit_points %} - <a class="btn btn-xs btn-primary" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Zadat body</a> <a class="btn btn-xs btn-default" href="{{ url_for('org_contest_task_batch_points', contest_id=contest.contest_id, task_id=task.task_id) }}">Nahrát body</a> {% endif %} </div> diff --git a/mo/web/templates/org_contest_solutions.html b/mo/web/templates/org_contest_solutions.html index 670a5472bd17e63d0190d69496123423fbff6614..dcb73a6f713c7b2386521b024525a0d7ee0ba0d6 100644 --- a/mo/web/templates/org_contest_solutions.html +++ b/mo/web/templates/org_contest_solutions.html @@ -4,10 +4,10 @@ {% set site_id = site.place_id if site else None %} {% block title %} -Tabulka řešení {% if site %}soutěžního místa {{ site.name }}{% else %}oblasti {{ contest.place.name }}{% endif %} +{{ "Založení řešení" if edit_form else "Tabulka řešení" }} {% if site %}soutěžního místa {{ site.name }}{% else %}oblasti {{ contest.place.name }}{% endif %} {% endblock %} {% block breadcrumbs %} -{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Tabulka řešení") }} +{{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Založení řešení" if edit_form else "Tabulka řešení") }} {% endblock %} {% block body %} @@ -20,10 +20,22 @@ Tabulka řešení {% if site %}soutěžního místa {{ site.name }}{% else %}obl {% include "parts/org_submit_warning.html" %} -<p>Všechna odevzdání od účastníka k úloze můžete vidět po kliknutí na ikonku <span class="icon">🔍</span>. +<p><i> +{% if edit_form %} +Zaškrtnutím políček u řešení, která dosud neexistují, a odesláním tlačítkem pod tabulkou tato řešení založíte. +To se hodí, pokud se nechystáte do systému nahrávat soubory řešení, ale jen chcete řešení vytvořit, aby jim +bylo možné vyplnit body. Pokud nějaké řešení založíte omylem, lze toto prázdné řešení smazat v jeho detailu. +{% else %} +Všechna odevzdání od účastníka k úloze můžete vidět po kliknutí na ikonku <span class="icon">🔍</span>. Odkazem v záhlaví se lze dostat na detailní výpis odevzdání všech uživatelů pro -konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje více verzí dostupných v detailu.</p> +konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje více verzí dostupných v detailu. +{% endif %} +</i></p> +{% if edit_form %} +<form class="form" method="POST"> +{{ edit_form.csrf_token }} +{% endif %} <table class="data full center"> <colgroup><col span="2"></colgroup> {% for task in tasks %} @@ -36,7 +48,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje <th rowspan=2>Stav účasti</th> {% for task in tasks %}<th colspan=4> <a href="{{ url_for('org_contest_task', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">{{ task.code }}</a> - {% if can_edit_points %} + {% if sc.allow_edit_points %} <a title="Editovat body" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}" class="icon pull-right">✎</a> {% endif %} {% endfor %} @@ -87,7 +99,13 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje {% endif %} <td class="sol"> {% else %} - <td colspan=3>– + <td colspan=3> + {% if edit_form %} + <label> + <input type="checkbox" name="create_sol_{{task.task_id}}_{{u.user_id}}"> + Založit + </label> + {% else %}–{% endif %} <td> {% endif %} <a class="btn btn-xs btn-link icon" title="Detail řešení" href="{{ url_for('org_submit_list', contest_id=contest.contest_id, user_id=u.user_id, task_id=task.task_id, site_id=site_id) }}">🔍</a> @@ -100,7 +118,7 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje {% for task in tasks %} <td colspan=4><div class='btn-group'> <a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_download', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">Stáhnout</a> - {% if can_upload %} + {% if sc.allow_upload_feedback %} <a class='btn btn-xs btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=contest.contest_id, site_id=site_id, task_id=task.task_id) }}">Nahrát</a> {% endif %} </div> @@ -108,5 +126,18 @@ konkrétní úlohu. Symbol <span class="icon">🗐</span> značí, že existuje <td> </tfoot> </table> +{% if edit_form %} + <div class='btn-group'> + {{ wtf.form_field(edit_form.submit, class="btn btn-primary") }} + <a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=contest.contest_id, site_id=site_id) }}">Zrušit</a> + </div> +</form> +{% else %} +<div class='btn-group'> + {% if sc.allow_create_solutions %} + <a class="btn btn-primary" href="{{ url_for('org_contest_solutions_edit', id=contest.contest_id, site_id=site_id) }}">Založit řešení</a> + {% endif %} +</div> +{% endif %} {% endblock %} diff --git a/mo/web/templates/org_contest_task.html b/mo/web/templates/org_contest_task.html index 224fd89eb4395db03ab1635dbcb9e16c11ec0ced..0e4719576a7c2b0a3c455ed23543592576a42099 100644 --- a/mo/web/templates/org_contest_task.html +++ b/mo/web/templates/org_contest_task.html @@ -7,9 +7,9 @@ {% set site_id = site.place_id if site else None %} {% set task = sc.task %} -{% block title %}{% if points_form %}Editace bodů{% else %}Odevzdaná řešení{% endif %} úlohy {{ task.code }} {{ task.name }}{% endblock %} +{% block title %}{{ "Zadávání bodů" if points_form else "Založení řešení" if create_form else "Odevzdaná řešení" }} úlohy {{ task.code }} {{ task.name }}{% endblock %} {% block breadcrumbs %} -{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Editace bodů" if points_form else None) }} +{{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Zadávání bodů" if points_form else "Založení řešení" if create_form else None) }} {% endblock %} {% block body %} @@ -23,25 +23,29 @@ {% include "parts/org_submit_warning.html" %} -{% if points_form %} +{% set form = points_form or create_form %} +{% if form %} <form class="form" method="POST"> -{{ points_form.csrf_token }} +{{ form.csrf_token }} {% endif %} {% with for_user=None, for_task=task, rows=rows %} {% include "parts/org_solution_table.html" %} {% endwith %} -{% if points_form %} +{% if form %} <div class='btn-group'> - {{ wtf.form_field(points_form.submit, class="btn btn-primary") }} - <a class="btn btn-default" href="{{ url_for('org_contest_task', contest_id=ct_id, task_id=task.task_id) }}">Zrušit editaci bodů</a> + {{ wtf.form_field(form.submit, class="btn btn-primary" ) }} + <a class="btn btn-default" href="{{ url_for('org_contest_task', contest_id=ct_id, task_id=task.task_id, site_id=site_id) }}">Zrušit</a> </div> </form> {% else %} <div class='btn-group'> <a class='btn btn-primary' href="{{ url_for('org_contest_task_download', contest_id=ct_id, site_id=site_id, task_id=task.task_id) }}">Stáhnout řešení</a> - {% if can_upload %} + {% if sc.allow_upload_feedback %} <a class='btn btn-primary' href="{{ url_for('org_contest_task_upload', contest_id=ct_id, site_id=site_id, task_id=task.task_id) }}">Nahrát opravená řešení</a> {% endif %} + {% if sc.allow_create_solutions %} + <a class="btn btn-primary" href="{{ url_for('org_contest_task_create', contest_id=ct_id, task_id=task.task_id, site_id=site_id) }}">Založit řešení</a> + {% endif %} {% if not site and sc.allow_edit_points %} <a class="btn btn-primary" href="{{ url_for('org_contest_task_points', contest_id=ct_id, task_id=task.task_id) }}">Zadat body</a> {% endif %} diff --git a/mo/web/templates/org_round.html b/mo/web/templates/org_round.html index 3296d22beeeb1486e96d26f526c4c4666c6e619b..9f6d685c9e13769ad89564da968fed8236a2dced 100644 --- a/mo/web/templates/org_round.html +++ b/mo/web/templates/org_round.html @@ -89,7 +89,8 @@ <th>Kód <th>Název <th>Odevzdaná řešení - {% if can_manage_round or can_handle_submits %}<th>Akce{% endif %} + {% if can_manage_round %}<th>Akce{% endif %} + {% if can_handle_submits or can_upload %}<th>Dávkové operace{% endif %} </tr> </thead> {% for task in tasks %} @@ -97,28 +98,32 @@ <td>{{ task.code }} <td>{{ task.name }} <td>{{ task.sol_count }} + {% if can_manage_round %} <td><div class="btn-group"> - {% if can_manage_round %} <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_edit', id=round.round_id, task_id=task.task_id) }}">Editovat</a> <form action="" method="POST" onsubmit="return confirm('Opravdu nenávratně smazat?')" class="btn-group"> {{ form_delete_task.csrf_token() }} <input type="hidden" name="delete_task_id" value="{{ task.task_id }}"> <button type="submit" class="btn btn-xs btn-danger">Smazat</button> </form> + {% if g.user.is_admin %} + <a class="btn btn-xs btn-default" href="{{ log_url('task', task.task_id) }}">Historie</a> {% endif %} + </div> + {% endif %} + {% if can_handle_submits or can_upload %} + <td><dic class="btn-group"> {% if can_handle_submits %} - <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_download', round_id=round.round_id, task_id=task.task_id) }}">Stáhnout</a> + <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_download', round_id=round.round_id, task_id=task.task_id) }}">Stáhnout ZIP</a> {% endif %} {% if can_upload %} - <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_upload', round_id=round.round_id, task_id=task.task_id) }}">Nahrát</a> + <a class="btn btn-xs btn-primary" href="{{ url_for('org_round_task_upload', round_id=round.round_id, task_id=task.task_id) }}">Nahrát ZIP</a> {% endif %} {% if can_upload %} <a class="btn btn-xs btn-default" href="{{ url_for('org_round_task_batch_points', round_id=round.round_id, task_id=task.task_id) }}">Nahrát body</a> {% endif %} - {% if g.user.is_admin %} - <a class="btn btn-xs btn-default" href="{{ log_url('task', task.task_id) }}">Historie</a> - {% endif %} </div> + {% endif %} </tr> {% endfor %} </table> diff --git a/mo/web/templates/org_submit_list.html b/mo/web/templates/org_submit_list.html index 37ad2fd414a30dde82d47066507f15307b7f525e..ffb9443ae94574a9eb049d4b57b5b3a75a6e9009 100644 --- a/mo/web/templates/org_submit_list.html +++ b/mo/web/templates/org_submit_list.html @@ -161,44 +161,64 @@ Existuje více než jedna verze oprav, finální je podbarvená. </div> {% else %} -<p>Žádné odevzdané řešení. {% if form %}Můžete ho přidat pomocí formuláře níže.{% endif %} +<p>Žádné odevzdané řešení. {% if form %}Můžete ho založit pomocí formuláře níže.{% endif %} {% endif %} {% if form %} - -<div class="form-frame"> <form method="post" class="form-horizontal" enctype="multipart/form-data"> +<div class="form-frame"> {{ form.csrf_token }} + {% set action = 'Uložit' if solution else 'Založit řešení' %} {% if sc.allow_edit_points %} - <h3 style="margin-top: 10px;">Hodnocení řešení</h3> + {% if solution %} + <h3 style="margin-top: 10px;">Hodnocení řešení</h3> + {% else %} + <h3>Založit řešení</h3> + <p><i>Můžete rovnou vyplnit i poznámky a přidělené body + {%- if sc.allow_upload_feedback or sc.allow_upload_solutions %}, případně rovnou nahrát i soubor řešení nebo opravy{% endif %}. + </i></p> + {% endif %} {{ wtf.form_field(form.note, form_type='horizontal', horizontal_columns=('sm', 2, 10), rows=4)}} {{ wtf.form_field(form.org_note, form_type='horizontal', horizontal_columns=('sm', 2, 10), rows=4 )}} {{ wtf.form_field(form.points, form_type='horizontal', horizontal_columns=('sm', 2, 10) )}} {{ wtf.form_field( form.submit, form_type='horizontal', class='btn btn-primary', horizontal_columns=('sm', 2, 10), - value='Uložit bez nahrání souboru' if sc.allow_upload_feedback or sc.allow_upload_solutions else 'Uložit' + value=action + (' bez nahrání souboru' if sc.allow_upload_feedback or sc.allow_upload_solutions else '') )}} {% endif %} {% if sc.allow_upload_feedback or sc.allow_upload_solutions %} - <h3>Nahrání souboru</h3> - {% if sc.allow_edit_points %} - <p><i>Lze najednou editovat řešení (například zadat body) i nahrát soubor, použijte tlačítka na spodku formuláře.</i></p> + {% if solution %} + <h3>Nahrání souboru</h3> + {% if sc.allow_edit_points %} + <p><i>Lze najednou editovat řešení (například zadat body) i nahrát soubor, použijte tlačítka na spodku formuláře.</i></p> + {% endif %} + {% else %} + <h3>Založit řešení a nahrát soubor</h3> {% endif %} {{ wtf.form_field(form.file, form_type='horizontal', horizontal_columns=('sm', 2, 10)) }} {{ wtf.form_field(form.file_note, form_type='horizontal', horizontal_columns=('sm', 2, 10)) }} <div class="form-group"> <div class="btn btn-group col-sm-offset-2"> {% if sc.allow_upload_solutions %} - {{ wtf.form_field(form.submit_sol, class='btn btn-primary' )}} + {{ wtf.form_field(form.submit_sol, class='btn btn-primary', value=action + ' a nahrát soubor jako řešení' )}} {% endif %} {% if sc.allow_upload_feedback %} - {{ wtf.form_field(form.submit_fb, class='btn btn-success' )}} + {{ wtf.form_field(form.submit_fb, class='btn btn-success', value=action + ' a nahrát soubor jako opravu' )}} {% endif %} </div> </div> {% endif %} -</form> </div> + +{% if solution and not solution.final_submit and not solution.final_feedback and sc.allow_create_solutions %} +<div class="form-frame"> + <h3 style="margin-top: 10px;">Smazání řešení</h3> + <p>Toto řešení zatím neobsahuje žádný soubor. Pokud bylo přidáno omylem, můžete ho smazat.</p> + {{ wtf.form_field(form.delete, class='btn btn-danger') }} +</div> +{% endif %} +</form> + {% endif %} {% endblock %} diff --git a/mo/web/templates/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html index 9eb4f7de3697d62e18835065a153beb71d86d69a..150d77ecb0f50d4af978c31b7e65e4557fa9c949 100644 --- a/mo/web/templates/parts/org_solution_table.html +++ b/mo/web/templates/parts/org_solution_table.html @@ -1,11 +1,18 @@ -<p><i>{% if for_user %}U každé úlohy je zobrazeno účastníkovo {% else %}U každého účastníka je zobrazeno jeho {% endif %} -finální řešení, finální oprava a přidělené body. Historii všech odevzdání, oprav a bodů naleznete v detailu řešení. +<p><i> +{% if create_form %} +Zaškrtnutím políček u řešení, která dosud neexistují, a odesláním tlačítkem pod tabulkou tato řešení založíte. +To se hodí, pokud se nechystáte do systému nahrávat soubory řešení, ale jen chcete řešení vytvořit, aby jim +bylo možné vyplnit body. Pokud nějaké řešení založíte omylem, lze toto prázdné řešení smazat v jeho detailu. +{% else %} +Historii všech odevzdání, oprav a bodů pro každé řešení naleznete v jeho detailu. {% if sc.allow_upload_feedback or sc.allow_edit_points %}Tamtéž můžete odevzdávat nové verze a změnit, které řešení/oprava je -finální (ve výchozím stavu poslední nahrané).{% endif %} +finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solutions %}Tamtéž můžete odevzdat nové řešení.{% endif %} +{% if for_task and sc.allow_create_solutions %} Hromadně založit řešení pro více řešitelů můžete pomocí tlačítek pod tabulkou.{% endif %} +{% endif %} </i></p> -<p><i>Legenda k symbolům: <span class='sol-warn icon'>⚠</span> odevzdané po termínu, -<span class="icon">🛈</span> nahráno někým jiným, než řešitelem, <span class="icon">🗐</span> existuje více verzí. Symboly po najetí myší zobrazí bližší informace. +<p><i>Legenda: <span class='sol-warn icon'>⚠</span> odevzdané po termínu, + <span class="icon">🛈</span> nahráno někým jiným, než řešitelem, <span class="icon">🗐</span> existuje více verzí. Symboly po najetí myší zobrazí bližší informace. </i></p> <table class="data full"> @@ -15,11 +22,11 @@ finální (ve výchozím stavu poslední nahrané).{% endif %} {% if for_task %}<th>Stav účasti{% endif %} <th>Finální řešení <th>Finální oprava + <th>Poznámky <th>Přidělené body {% if not for_user and not site and sc.allow_edit_points and not points_form %} <a title="Editovat body" href="{{ url_for('org_contest_task_points', contest_id=contest.contest_id, task_id=task.task_id) }}" class="icon pull-right">✎</a> {% endif %} - <th>Poznámky <th>Akce </tr> </thead> @@ -73,19 +80,25 @@ finální (ve výchozím stavu poslední nahrané).{% endif %} <span title="Celkem {{ paper_counts[key]|inflected('verze', 'verze', 'verzí') }}" class="icon">🗐</span> {% endif %} {% else %}–{% endif %} - <td>{% if points_form %} - <input type="number" class="form-control" name="points_{{sol.user_id}}" value="{{ request_form.get("points_{}".format(sol.user_id)) or sol.points }}" size="4"> - {% else %} - {% if sol.points is not none %}{{ sol.points}}{% else %}<span class="unknown">?</span>{% endif %} - {% endif %} - {% else %} - <td>–<td>–<td>– - {% endif %} <td style="text-align: center;"> - {% if sol and sol.note %}<span class="icon" title="Poznámka pro řešitele: {{ sol.note }}">🗩</span>{% endif %} - {% if sol and sol.org_note %} <span class="icon" title="Interní poznámka: {{ sol.org_note }}">🗩</span>{% endif %} + {% if sol.note %}<span class="icon" title="Poznámka pro řešitele: {{ sol.note }}">🗩</span>{% endif %} + {% if sol.org_note %} <span class="icon" title="Interní poznámka: {{ sol.org_note }}">🗩</span>{% endif %} <td> + {% if points_form %} + <input type="number" class="form-control" name="points_{{u.user_id}}" value="{{ request_form.get("points_{}".format(u.user_id)) or sol.points }}" size="4"> + {% else %} + {% if sol.points is not none %}{{ sol.points}}{% else %}<span class="unknown">?</span>{% endif %} + {% endif %} + {% else %} + <td colspan="4" class="text-center"> + {% if create_form %} + <input type="checkbox" name="create_sol_{{u.user_id}}" id="create_sol_{{u.user_id}}"{% if request_form.get("create_sol_{}".format(u.user_id)) %}checked{% endif %}> + <label for="create_sol_{{u.user_id}}">Založit řešení</label> + {% else %}–{% endif %} + {% endif %} + <td><div class="btn-group"> <a class="btn btn-xs btn-primary" href="{{ url_for('org_submit_list', contest_id=ct_id, user_id=u.user_id, task_id=task.task_id, site_id=site_id) }}">Detail</a> + </div> </tr> {% endfor %} </table> diff --git a/static/mo.css b/static/mo.css index 47c87eee9867cfcb525f85a7ed16a77acc1c4b83..8978f2cd51d677e19ab53875e9175b7793006476 100644 --- a/static/mo.css +++ b/static/mo.css @@ -172,6 +172,13 @@ nav#main-menu a.active { border-radius: 4px 4px; } +.checked_toggle input.toggle:checked ~ .checked_hide { + display: none; +} +.checked_toggle input.toggle:not(:checked) ~ .checked_show { + display: none; +} + /* Round states */ .rstate-preparing {