diff --git a/mo/web/jinja.py b/mo/web/jinja.py index 893ac8ca01a617687a00fc49f20498b8843d7c73..f16c2e5c4efec31f9bfc7c637e1655456936775e 100644 --- a/mo/web/jinja.py +++ b/mo/web/jinja.py @@ -2,8 +2,10 @@ from flask import url_for import json +from html import escape from markupsafe import Markup from typing import Any +import urllib.parse import mo.config as config import mo.db as db @@ -67,6 +69,12 @@ def pion_link(u: db.User, contest_id: int) -> Markup: return Markup('<a href="{url}">{name}{test}</a>').format(url=url, name=u.full_name(), test=" (test)" if u.is_test else "") +@app.template_filter() +def mailto(email: str) -> Markup: + safe_email = urllib.parse.quote(email, safe='@') + return Markup(f'<a href="mailto:{escape(safe_email)}">{escape(email)}</a>') + + @app.template_filter() def or_dash(s: Any) -> str: return str(s) if s else '–' diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index d788d9a0cc9a192325b18362f7a6c87ff769ddce..4e9ecf45452087c64547ca84faed46d62c4ebf61 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -30,7 +30,7 @@ from wtforms.widgets.html5 import NumberInput class ImportForm(FlaskForm): - file = flask_wtf.file.FileField("Soubor") + file = flask_wtf.file.FileField("Soubor", render_kw={'autofocus': True}) typ = wtforms.SelectField( "Typ dat", choices=[(x.name, x.friendly_name()) for x in (ImportType.participants, ImportType.proctors, ImportType.judges)], @@ -48,8 +48,8 @@ class ImportForm(FlaskForm): class ParticipantsFilterForm(PagerForm): school = wtforms.StringField("Škola") - participation_place = wtforms.StringField("Soutěžní místo") - contest_place = wtforms.StringField("Soutěžní oblast") + participation_place = wtforms.StringField("Soutěžní místo", render_kw={'autofocus': True}) + contest_place = wtforms.StringField("Soutěžní oblast", render_kw={'autofocus': True}) participation_state = wtforms.SelectField('Stav účasti', choices=[('*', '*')] + list(db.PartState.choices()), default='*') # format = wtforms.RadioField(choices=[('', 'Zobrazit'), ('csv', 'Stáhnout vše v CSV'), ('tsv', 'Stáhnout vše v TSV')]) @@ -656,7 +656,7 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio class SubmitForm(FlaskForm): - note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola") + note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola", render_kw={'autofocus': True}) org_note = wtforms.TextAreaField("Interní poznámka", description="Viditelná jen organizátorům") # Validátory k points budou přidány podle počtu maximálních bodů úlohy v org_submit_list points = IntegerField('Body', description="Účastník po uzavření kola uvidí jen naposledy zadané body") @@ -887,7 +887,7 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option class SubmitEditForm(FlaskForm): - note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola", render_kw={"rows": 8}) + note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola", render_kw={"rows": 8, 'autofocus': True}) org_note = wtforms.TextAreaField("Interní poznámka", description="Viditelná jen organizátorům", render_kw={"rows": 8}) submit = wtforms.SubmitField("Uložit") @@ -1150,7 +1150,11 @@ def org_contest_solutions(id: int, site_id: Optional[int] = None): class DownloadSubmitsForm(FlaskForm): - min_points = wtforms.IntegerField('Minimální počet bodů v kole', description='Je-li uveden, řešení účastníků, kteří ve výsledcích celého kola dostali méně bodů, se nestahují.', validators=[validators.Optional()]) + min_points = wtforms.IntegerField( + 'Minimální počet bodů v kole', render_kw={'autofocus': True}, + description='Je-li uveden, řešení účastníků, kteří ve výsledcích celého kola dostali méně bodů, se nestahují.', + validators=[validators.Optional()] + ) download_sol = wtforms.SubmitField('Stáhnout všechna účastnická řešení') download_fb = wtforms.SubmitField('Stáhnout všechna opravená řešení') download_sol_mix = wtforms.SubmitField('Stáhnout účastnická/opravená') @@ -1249,7 +1253,7 @@ def generic_batch_download(round: db.Round, contest: Optional[db.Contest], site: class UploadSubmitsForm(FlaskForm): - file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()]) + file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()], render_kw={'autofocus': True}) submit = wtforms.SubmitField('Odeslat') @@ -1298,7 +1302,7 @@ def org_contest_task_upload(contest_id: int, task_id: int, site_id: Optional[int class BatchPointsForm(FlaskForm): - file = flask_wtf.file.FileField("Soubor") + file = flask_wtf.file.FileField("Soubor", render_kw={'autofocus': True}) fmt = wtforms.SelectField( "Formát souboru", choices=FileFormat.choices(), coerce=FileFormat.coerce, @@ -1404,7 +1408,11 @@ def org_contest_user(contest_id: int, user_id: int): class AdvanceForm(FlaskForm): - boundary = IntegerField('Bodová hranice', description="Postoupí všichni účastníci, kteří v minulém kole získali aspoň tolik bodů.", validators=[validators.InputRequired()]) + boundary = IntegerField( + 'Bodová hranice', render_kw={'autofocus': True}, + description="Postoupí všichni účastníci, kteří v minulém kole získali aspoň tolik bodů.", + validators=[validators.InputRequired()] + ) status = wtforms.HiddenField() preview = wtforms.SubmitField('Zobrazit návrh') execute = wtforms.SubmitField('Provést') diff --git a/mo/web/org_place.py b/mo/web/org_place.py index 322706b6b01e70630e98175ed4d6125bbea38d20..34fb5895887672b79a7583f4b71d6084103fa333 100644 --- a/mo/web/org_place.py +++ b/mo/web/org_place.py @@ -42,7 +42,7 @@ def org_place(id: int): class PlaceEditForm(FlaskForm): name = wtforms.StringField( - 'Název', + 'Název', render_kw={'autofocus': True}, validators=[validators.DataRequired()] ) code = wtforms.StringField( @@ -169,7 +169,7 @@ def org_place_edit(id: int): class PlaceMoveForm(FlaskForm): - code = wtforms.StringField(validators=[validators.DataRequired()]) + code = wtforms.StringField(validators=[validators.DataRequired()], render_kw={'autofocus': True}) submit = wtforms.SubmitField('Najít místo') reset = wtforms.HiddenField() move = wtforms.HiddenField() diff --git a/mo/web/org_round.py b/mo/web/org_round.py index f14705d3b4aa9d138d7079279e0592147457743a..7dbc1c1abc04504d513cdc58ac3457f8660c0f31 100644 --- a/mo/web/org_round.py +++ b/mo/web/org_round.py @@ -225,9 +225,8 @@ def org_round(id: int): class TaskEditForm(FlaskForm): code = wtforms.StringField('Kód úlohy', validators=[ validators.Required(), - # trik: nelze použít \w protože obsahuje _, \W je negace \w, takže [^\W-] je \w bez _ - validators.Regexp(r'^([^\W_]|-)+$', message="Kód úlohy smí obsahovat jen písmena, čísla a znak -"), - ]) + validators.Regexp(r'^[A-Za-z0-9-]+$', message="Kód úlohy smí obsahovat jen nediakritická písmena, čísla a znak -"), + ], render_kw={'autofocus': True}) name = wtforms.StringField('Název úlohy') max_points = IntegerField( 'Maximum bodů', validators=[validators.Optional()], @@ -390,7 +389,7 @@ class MODateTimeField(wtforms.DateTimeField): class RoundEditForm(FlaskForm): - name = wtforms.StringField("Název") + name = wtforms.StringField("Název", render_kw={'autofocus': True}) state = wtforms.SelectField("Stav kola", choices=db.RoundState.choices(), coerce=db.RoundState.coerce) # Only the desktop Firefox does not support datetime-local field nowadays, # other browsers does provide date and time picker UI :( diff --git a/mo/web/org_users.py b/mo/web/org_users.py index 5937abc2cd8c1c17e5b9e2ca9d89d8e582db3642..88354ecde8af6b7aa76613696cdf13fd052ad264 100644 --- a/mo/web/org_users.py +++ b/mo/web/org_users.py @@ -21,7 +21,7 @@ from mo.web.util import PagerForm class UsersFilterForm(PagerForm): # user - search_name = wtforms.TextField("Jméno/příjmení") + search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True}) search_email = wtforms.TextField("E-mail") # participants @@ -149,7 +149,7 @@ def org_users(): class OrgsFilterForm(PagerForm): # user - search_name = wtforms.TextField("Jméno/příjmení") + search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True}) search_email = wtforms.TextField("E-mail") # TODO: filtering by roles? @@ -196,7 +196,7 @@ def org_orgs(): class FormAddRole(FlaskForm): - role = wtforms.SelectField('Role', choices=db.RoleType.choices(), coerce=db.RoleType.coerce) + role = wtforms.SelectField('Role', choices=db.RoleType.choices(), coerce=db.RoleType.coerce, render_kw={'autofocus': True}) place_code = wtforms.StringField('Oblast') year = wtforms.IntegerField('Ročník', validators=[validators.Optional()]) category = wtforms.StringField("Kategorie", validators=[validators.Length(max=2)], filters=[lambda x: x or None]) @@ -320,7 +320,7 @@ def org_user(id: int): class UserEditForm(FlaskForm): - first_name = wtforms.StringField("Jméno", validators=[Required()]) + first_name = wtforms.StringField("Jméno", validators=[Required()], render_kw={'autofocus': True}) last_name = wtforms.StringField("Příjmení", validators=[Required()]) note = wtforms.TextAreaField("Poznámka") submit = wtforms.SubmitField("Uložit") diff --git a/mo/web/templates/base.html b/mo/web/templates/base.html index 361c62f7f1b11baa946e9fbc51d6261b18418a15..a493fa9b5fd6fe5b1d02e62963f2a46e16043211 100644 --- a/mo/web/templates/base.html +++ b/mo/web/templates/base.html @@ -30,6 +30,7 @@ </div> <main> {% if self.breadcrumbs() %}<ol class="breadcrumb">{% block breadcrumbs %}{% endblock %}</ol>{% endif %} +{% block pretitle %}{% endblock %} <h2>{{ self.title() }}</h2> {% with messages = get_flashed_messages(with_categories=true) %} @@ -50,7 +51,7 @@ <footer> <hr> <div class="content"> - <p>V případě problémů napište správci na adresu <a href='mailto:osmo@mo.mff.cuni.cz'>osmo@mo.mff.cuni.cz</a>. + <p>V případě problémů napište správci na adresu {{ 'osmo@mo.mff.cuni.cz'|mailto }}. Více viz <a href='{{ url_for('doc_about') }}'>o aplikaci</a> a <a href='{{ url_for('doc_gdpr') }}'>zpracování osobních údajů</a>. </div> diff --git a/mo/web/templates/doc_about.html b/mo/web/templates/doc_about.html index 225a8edbaa920b0230ce03126b6f7fdf217e7a3a..f737e46822f32c67a37ca1372c07ad17159dbf6b 100644 --- a/mo/web/templates/doc_about.html +++ b/mo/web/templates/doc_about.html @@ -13,6 +13,6 @@ MFF UK také děkujeme za poskytnutí serveru, kde systém běží. <p>Veškeré připomínky k chodu systému a nápady na další rozvoj -prosím posílejte e-mailem na <a href='mailto:osmo@mo.mff.cuni.cz'>osmo@mo.mff.cuni.cz</a>. +prosím posílejte e-mailem na {{ 'osmo@mo.mff.cuni.cz'|mailto }}. {% endblock %} diff --git a/mo/web/templates/doc_gdpr.html b/mo/web/templates/doc_gdpr.html index f4e0ae1f171cbaf57d452ab42ee8d2500201b877..f305feb49dc89b85286ea06cabea7ff19fd5cf09 100644 --- a/mo/web/templates/doc_gdpr.html +++ b/mo/web/templates/doc_gdpr.html @@ -19,7 +19,7 @@ Data nebudou předávána třetím stranám a budou uchovávána maximálně 2 r Tento odstavec se netýká zveřejněných výsledkových listin. <p>Souhlas se zpracováním osobních údajů můžete kdykoliv odvolat zasláním e-mailu na adresu -<a href='mailto:osmo@mo.mff.cuni.cz'>osmo@mo.mff.cuni.cz</a>. Údaje z již zveřejněných výsledkových +{{ 'osmo@mo.mff.cuni.cz'|mailto }}. Údaje z již zveřejněných výsledkových listin nicméně není možné zpětně odstranit. <p>Dále máte právo: @@ -33,6 +33,6 @@ listin nicméně není možné zpětně odstranit. </ul> <p>V případě jakéhokoliv dotazu nebo uplatnění svých práv můžete kontaktovat univerzitního -pověřence pro ochranu osobních údajů na e-mailové adrese <a href='mailto:gdpr@cuni.cz'>gdpr@cuni.cz</a>. +pověřence pro ochranu osobních údajů na e-mailové adrese {{ 'gdpr@cuni.cz'|mailto }}. {% endblock %} diff --git a/mo/web/templates/org_contest_solutions.html b/mo/web/templates/org_contest_solutions.html index 623a82bc69c9b95e6d4ea27a5996c4bed16cf1da..0d4fe73a4a1eb1df77b75ffd36a6f9e063144b1b 100644 --- a/mo/web/templates/org_contest_solutions.html +++ b/mo/web/templates/org_contest_solutions.html @@ -10,14 +10,15 @@ {{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Založení řešení" if edit_form else "Tabulka řešení") }} {% endblock %} -{% block body %} +{% block pretitle %} {% if round.state in [RoundState.grading, RoundState.closed] %} <div class="btn-group pull-right"> <a class="btn btn-default" href="{{ url_for('org_score', contest_id=contest.contest_id) }}">Výsledky oblasti</a> <a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a> </div> {% endif %} - +{% endblock %} +{% block body %} {% include "parts/org_submit_warning.html" %} <p><i> diff --git a/mo/web/templates/org_contest_task.html b/mo/web/templates/org_contest_task.html index 0e4719576a7c2b0a3c455ed23543592576a42099..d327cf1275f8e35df504e021fea770f229d2c7ce 100644 --- a/mo/web/templates/org_contest_task.html +++ b/mo/web/templates/org_contest_task.html @@ -12,7 +12,7 @@ {{ 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 %} +{% block pretitle %} <div class="btn-group pull-right"> <a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=ct_id, site_id=site_id) }}">Všechny úlohy</a> {% if round.state in [RoundState.grading, RoundState.closed] %} @@ -20,7 +20,8 @@ <a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a> {% endif %} </div> - +{% endblock %} +{% block body %} {% include "parts/org_submit_warning.html" %} {% set form = points_form or create_form %} diff --git a/mo/web/templates/org_contest_user.html b/mo/web/templates/org_contest_user.html index 89da5f3134a9c4cba7c6f5f4ad89f9d7c13248c6..f4a4944e46447e869d5fd3357d29cea9fa167447 100644 --- a/mo/web/templates/org_contest_user.html +++ b/mo/web/templates/org_contest_user.html @@ -32,7 +32,7 @@ </thead> <tr><td>Jméno:<td>{{ user.first_name }} <tr><td>Příjmení:<td>{{ user.last_name }} - <tr><td>E-mail:<td><a href="mailto:{{ user.email }}">{{ user.email }}</a> + <tr><td>E-mail:<td>{{ user.email|mailto }} <tr><td>Škola:<td><a href='{{ url_for('org_place', id=pant.school) }}'>{{ pant.school_place.name }}</a> <tr><td>Třída:<td>{{ pant.grade }} <tr><td>Rok narození:<td>{{ pant.birth_year }} diff --git a/mo/web/templates/org_org.html b/mo/web/templates/org_org.html index beff61bc87e3c9475036c304d5718fedee8d4f07..3385df81f546e2978b649ac66d22f1b14563f241 100644 --- a/mo/web/templates/org_org.html +++ b/mo/web/templates/org_org.html @@ -6,7 +6,7 @@ <table class=data> <tr><td>Jméno:<td>{{ user.first_name }} <tr><td>Příjmení:<td>{{ user.last_name }} -<tr><td>E-mail:<td><a href="mailto:{{ user.email }}">{{ user.email }}</a> +<tr><td>E-mail:<td>{{ user.email|mailto }} {% if user.is_admin %}<tr><td>Správce:<td>ano{% endif %} {% if user.is_org %}<tr><td>Organizátor:<td>ano{% endif %} <tr><td>Účet založen:<td>{{ user.created_at|timeformat }} diff --git a/mo/web/templates/org_orgs.html b/mo/web/templates/org_orgs.html index 25366f76117f8b47ea837cf3265703cfad48cb16..cfff4e6e3473c3c565ed4b60f22fc2ffcd377b6b 100644 --- a/mo/web/templates/org_orgs.html +++ b/mo/web/templates/org_orgs.html @@ -47,7 +47,7 @@ {% for user in users %} <tr> <td>{{ user.first_name }}</td><td>{{ user.last_name }}</td> - <td><a href="mailto:{{ user.email }}">{{ user.email }}</a>{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td> + <td>{{ user.email|mailto }}{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td> <td>{% if user.is_admin %}správce{% elif user.roles|count == 0 %}<i>žádná role</i>{% else %} <ul> {% for role in user.roles %} diff --git a/mo/web/templates/org_score.html b/mo/web/templates/org_score.html index 8c1872548411d8d3387e6c4f88546f91da0a4372..6cf67c92b7e0d605b74b02d3f7b9816d232aa9f8 100644 --- a/mo/web/templates/org_score.html +++ b/mo/web/templates/org_score.html @@ -7,13 +7,15 @@ {{ contest_breadcrumbs(round=round, contest=contest, action="Výsledky oblasti" if contest else "Výsledky kola") }} {% endblock %} -{% block body %} +{% block pretitle %} <div class="btn-group pull-right"> {% if contest %} <a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=contest.contest_id) }}">Odevzdaná řešení</a> <a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a> {% endif %} </div> +{% endblock %} +{% block body %} {% if messages %} <div class="collapsible"> diff --git a/mo/web/templates/org_user.html b/mo/web/templates/org_user.html index 83423526c2ea907f22466e2945117910aabb51e6..c068b09e789706551722ab0bbb42074c24621639 100644 --- a/mo/web/templates/org_user.html +++ b/mo/web/templates/org_user.html @@ -6,7 +6,7 @@ <table class=data> <tr><td>Jméno:<td>{{ user.first_name }} <tr><td>Příjmení:<td>{{ user.last_name }} -<tr><td>E-mail:<td><a href="mailto:{{ user.email }}">{{ user.email }}</a> +<tr><td>E-mail:<td>{{ user.email|mailto }} {% if user.is_admin %}<tr><td>Správce:<td>ano{% endif %} {% if user.is_org %}<tr><td>Organizátor:<td>ano{% endif %} <tr><td>Účet založen:<td>{{ user.created_at|timeformat }} diff --git a/mo/web/templates/org_user_edit.html b/mo/web/templates/org_user_edit.html index e4841fdb023f5d36450d594b5a203e3ce30ef430..db299584495544a3fbcf001bf98841ca5b60b54a 100644 --- a/mo/web/templates/org_user_edit.html +++ b/mo/web/templates/org_user_edit.html @@ -6,7 +6,7 @@ <table class=data> <tr><td>Jméno:</td><td>{{ user.first_name }}</td></tr> <tr><td>Příjmení:</td><td>{{ user.last_name }}</td></tr> -<tr><td>E-mail:</td><td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td></tr> +<tr><td>E-mail:</td><td>{{ user.email|mailto }}</td></tr> {% if user.is_admin %}<tr><td>Správce:</td><td>ano</td></tr>{% endif %} {% if user.is_org %}<tr><td>Organizátor:</td><td>ano</td></tr>{% endif %} <tr><td>Poznámka:</td><td style="white-space: pre;">{{ user.note }}</td></tr> diff --git a/mo/web/templates/org_users.html b/mo/web/templates/org_users.html index 776293f556d6bbb3f026e208111c642a46e966bc..ad5100ef49d4de39a604787a04e2538d32b58603 100644 --- a/mo/web/templates/org_users.html +++ b/mo/web/templates/org_users.html @@ -71,7 +71,7 @@ {% for user in users %} <tr{% if user.is_test %} class="testuser" title="Testovací uživatel"{% endif %}> <td>{{ user.first_name }}</td><td>{{ user.last_name }}</td> - <td><a href="mailto:{{ user.email }}">{{ user.email }}</a>{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td> + <td>{{ user.email|mailto }}{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td> <td>{% if user.participants|count == 0 %}<i>v žádném ročníku</i>{% else %} <ul> {% for participant in user.participants %} diff --git a/mo/web/templates/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html index 3b9c625c98bce7448602b47de839301eebf8a19a..7e164d06200a77d582d1fe548df93bbadb5cd307 100644 --- a/mo/web/templates/parts/org_solution_table.html +++ b/mo/web/templates/parts/org_solution_table.html @@ -30,6 +30,7 @@ finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solut <th>Akce </tr> </thead> +{% set tabindex = namespace(value=1) %} {% for obj, sol in rows %} {% set u = for_user or obj.user %} {% set task = for_task or obj %} @@ -85,14 +86,16 @@ finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solut {% if sol.org_note %} <span class="icon" title="Interní poznámka: {{ sol.org_note }}">🗩</span>{% endif %} <td> {% if points_form %} - <input type="number" min=0 {% if task.max_points is not none %}max={{ task.max_points }}{% endif %} class="form-control" name="points_{{u.user_id}}" value="{{ request_form.get("points_{}".format(u.user_id)) or sol.points }}" size="4"> + <input type="number" min=0 {% if task.max_points is not none %}max={{ task.max_points }}{% endif %} class="form-control" name="points_{{u.user_id}}" value="{{ request_form.get("points_{}".format(u.user_id)) or sol.points }}" size="4" tabindex={{ tabindex.value }} autofocus> + {% set tabindex.value = tabindex.value + 1%} {% 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 %}> + <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 %} tabindex={{ tabindex.value }} autofocus> + {% set tabindex.value = tabindex.value + 1%} <label for="create_sol_{{u.user_id}}">Založit řešení</label> {% else %}–{% endif %} {% endif %} diff --git a/mo/web/user.py b/mo/web/user.py index 5ebc54f2f02a4d563747a16c48f07014112da557..e0b56ac0c64df8eb140932bfaa8ccb6a3f7b94e4 100644 --- a/mo/web/user.py +++ b/mo/web/user.py @@ -97,7 +97,7 @@ def user_task_statement(id: int): class SubmitForm(FlaskForm): - file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()]) + file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()], render_kw={'autofocus': True}) note = wtforms.TextAreaField("Poznámka", description="Zde můžete něco vzkázat organizátorům soutěže.") submit = wtforms.SubmitField('Odevzdat')