Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • devel
  • fo
  • fo-base
  • honza/add-contestant
  • honza/kolo-vs-soutez
  • honza/mr6
  • honza/mr7
  • honza/mra
  • honza/mrd
  • honza/mrf
  • honza/submit-images
  • jh-stress-test-wip
  • jirka/typing
  • jk/issue-196
  • jk/issue-96
  • master
  • mj/submit-images
  • shorten-schools
18 results

Target

Select target project
  • mj/mo-submit
1 result
Select Git revision
  • devel
  • fo
  • fo-base
  • honza/add-contestant
  • honza/kolo-vs-soutez
  • honza/mr6
  • honza/mr7
  • honza/mra
  • honza/mrd
  • honza/mrf
  • honza/submit-images
  • jh-stress-test-wip
  • jirka/typing
  • jk/issue-196
  • jk/issue-96
  • master
  • mj/submit-images
  • shorten-schools
18 results
Show changes
Commits on Source (22)
......@@ -187,3 +187,7 @@ def clean_up_token(token: str) -> str:
# Někteří klienti při kopírování adresy z mailu do prohlížeče
# přidávají divné Unicodové znaky (přepnutí směru psaní atd., viz issue #58).
return re.sub(r'[^!-~]', "", token)
def star_is_none(x):
return None if x == "*" else x
import decimal
from typing import Optional
import wtforms
from wtforms.fields.html5 import EmailField
from wtforms.widgets.html5 import NumberInput
import mo
......@@ -20,7 +22,45 @@ class OptionalInt(wtforms.IntegerField):
raise wtforms.ValidationError('Nejedná se o číslo.')
class Email(wtforms.StringField):
class Decimal(wtforms.DecimalField):
"""Upravený DecimalField, který formátuje číslo podle jeho skutečného počtu
desetinných míst a zadané `places` používá jen jako maximální počet desetinných míst."""
def _value(self):
if self.data is not None:
# Spočítání počtu desetinných míst, zbytek necháme na původní implementaci
max_places = self.places
self.places = 0
d = decimal.Decimal(1)
while self.data % d != 0 and self.places < max_places:
self.places += 1
d /= 10
return super(Decimal, self)._value()
class IntList(wtforms.StringField):
list = None
def __init__(self, label="", validators=None, **kwargs):
super().__init__(label, validators, **kwargs)
def pre_validate(field, form):
field.list = None
if field.data:
try:
field.list = mo.util.parse_int_list(field.data)
except mo.CheckError as e:
raise wtforms.ValidationError(str(e))
class Points(Decimal):
def __init__(self, label="Body", validators=None, **kwargs):
super().__init__(label, validators, **kwargs)
class Email(EmailField):
def __init__(self, label="E-mail", validators=None, **kwargs):
super().__init__(label, validators, **kwargs)
......@@ -87,6 +127,7 @@ class LastName(Name):
class Place(wtforms.StringField):
def __init__(self, label="Místo", validators=None, **kwargs):
super().__init__(label, validators, **kwargs)
self.render_kw = {"placeholder": "Kód"}
place_loaded: bool = False
place: Optional[db.Place] = None
......@@ -160,3 +201,18 @@ class RepeatPassword(wtforms.PasswordField):
def pre_validate(field, form):
if field.data != form.new_passwd.data:
raise wtforms.ValidationError('Hesla se neshodují.')
class DateTime(wtforms.DateTimeField):
def __init__(self, label, format='%Y-%m-%d %H:%M', description='Ve formátu 2000-01-01 12:34', **kwargs):
super().__init__(label, format=format, description=description, **kwargs)
def process_data(self, valuelist):
super().process_data(valuelist)
if self.data is not None:
self.data = self.data.astimezone()
def process_formdata(self, valuelist):
super().process_formdata(valuelist)
if self.data is not None:
self.data = self.data.astimezone()
......@@ -79,7 +79,7 @@ school_export_columns = (
@app.route('/org/export/schools')
def org_export_shools():
def org_export_schools():
sess = db.get_session()
format = request.args.get('format', 'en_csv')
......
......@@ -25,7 +25,7 @@ from mo.util_format import inflect_number, inflect_by_number
from mo.web import app
import mo.web.fields as mo_fields
import mo.web.util
from mo.web.util import MODecimalField, PagerForm
from mo.web.util import PagerForm
from mo.web.table import CellCheckbox, Table, Row, Column, cell_pion_link, cell_place_link, cell_email_link, cell_email_link_flags
import wtforms.validators as validators
from wtforms.widgets.html5 import NumberInput
......@@ -49,9 +49,9 @@ class ImportForm(FlaskForm):
class ParticipantsFilterForm(PagerForm):
school = wtforms.StringField("Škola")
participation_place = wtforms.StringField("Soutěžní místo", render_kw={'autofocus': True})
contest_place = wtforms.StringField("Soutěžní oblast", render_kw={'autofocus': True})
school = mo_fields.School()
participation_place = mo_fields.Place("Soutěžní místo", render_kw={'autofocus': True})
contest_place = mo_fields.Place("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')])
......@@ -59,33 +59,6 @@ class ParticipantsFilterForm(PagerForm):
download_csv = wtforms.SubmitField("↓ CSV")
download_tsv = wtforms.SubmitField("↓ TSV")
# Výstupní hodnoty filtru, None při nepoužitém filtru, prázdná db hodnota při
# nepovedené filtraci (neexistující místo a podobně)
f_school: Optional[db.Place] = None
f_participation_place: Optional[db.Place] = None
f_contest_place: Optional[db.Place] = None
f_participation_state: Optional[db.PartState] = None
def validate(self):
if self.school.data:
self.f_school = db.get_place_by_code(self.school.data)
if not self.f_school:
flash(f"Zadaná škola '{self.school.data}' neexistuje", "danger")
self.f_school = db.School()
if self.participation_place.data:
self.f_participation_place = db.get_place_by_code(self.participation_place.data)
if not self.f_participation_place:
flash(f"Zadané soutěžní místo '{self.participation_place.data}' neexistuje", "danger")
self.f_participation_place = db.Place()
if self.contest_place.data:
self.f_contest_place = db.get_place_by_code(self.contest_place.data)
if not self.f_contest_place:
flash(f"Zadaná soutěžní oblast '{self.contest_place.data}' neexistuje", "danger")
self.f_contest_place = db.Place()
self.f_participation_state = None if self.participation_state.data == '*' else self.participation_state.data
class ParticipantsActionForm(FlaskForm):
action_on = wtforms.RadioField(
"Provést akci na", validators=[validators.DataRequired()],
......@@ -97,12 +70,12 @@ class ParticipantsActionForm(FlaskForm):
participation_state = wtforms.SelectField('Stav účasti', choices=db.PartState.choices(), coerce=db.PartState.coerce)
set_participation_state = wtforms.SubmitField("Nastavit stav účasti")
participation_place = wtforms.StringField(
participation_place = mo_fields.Place(
'Soutěžní místo', description='Zadejte kód místa'
)
set_participation_place = wtforms.SubmitField("Nastavit soutěžní místo")
contest_place = wtforms.StringField(
contest_place = mo_fields.Place(
'Soutěžní oblast',
description='Musí existovat soutěž v dané oblasti pro stejné kolo. Oblast zadejte pomocí kódu.'
)
......@@ -123,15 +96,9 @@ class ParticipantsActionForm(FlaskForm):
if self.set_participation_state.data:
pass
elif self.set_participation_place.data:
participation_place = db.get_place_by_code(self.participation_place.data)
if not participation_place:
flash('Nenalezeno místo s daným kódem', 'danger')
return False
participation_place = self.participation_place.place
elif self.set_contest.data:
contest_place = db.get_place_by_code(self.contest_place.data)
if not contest_place:
flash("Nepovedlo se najít "+round.get_level().name_accusative("zadaný", "zadanou", "zadané"), 'danger')
return False
contest_place = self.contest_place.place
# Contest hledáme vždy v master kole, abychom náhodou nepřesunuli účastníky do soutěže v podkole
contest = sess.query(db.Contest).filter_by(round_id=round.master_round_id, place_id=contest_place.place_id).one_or_none()
if not contest:
......@@ -452,14 +419,16 @@ def org_contest_list(id: int, site_id: Optional[int] = None):
can_edit = rr.have_right(Right.manage_contest) and request.endpoint != 'org_contest_list_emails'
format = request.args.get('format', "")
filter = ParticipantsFilterForm(request.args)
filter = ParticipantsFilterForm(formdata=request.args)
if request.args:
filter.validate()
query = get_contestants_query(
round=master_contest.round, contest=master_contest, site=site,
school=filter.f_school,
# contest_place=filter.f_contest_place,
participation_place=filter.f_participation_place,
participation_state=filter.f_participation_state,
school=filter.school.place,
contest_place=filter.contest_place.place,
participation_place=filter.participation_place.place,
participation_state=mo.util.star_is_none(filter.participation_state.data),
)
action_form = None
......@@ -689,7 +658,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", render_kw={'autofocus': True})
org_note = wtforms.TextAreaField("Interní poznámka", description="Viditelná jen organizátorům")
points = MODecimalField('Body', description="Účastník po uzavření kola uvidí jen naposledy zadané body", validators=[validators.Optional()])
points = mo_fields.Points(description="Účastník po uzavření kola uvidí jen naposledy zadané body", validators=[validators.Optional()])
submit = wtforms.SubmitField('Uložit')
file = flask_wtf.file.FileField("Soubor")
......@@ -1182,7 +1151,7 @@ def org_contest_solutions(id: int, site_id: Optional[int] = None):
class DownloadSubmitsForm(FlaskForm):
min_points = wtforms.IntegerField(
min_points = mo_fields.Points(
'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()]
......@@ -1441,7 +1410,7 @@ def org_contest_user(contest_id: int, user_id: int):
class AdvanceForm(FlaskForm):
boundary = MODecimalField(
boundary = mo_fields.Points(
'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()]
......
......@@ -13,6 +13,7 @@ import mo.imports
import mo.rights
import mo.util
from mo.web import app
import mo.web.fields as mo_fields
import wtforms.validators as validators
......@@ -177,14 +178,14 @@ def org_place_edit(id: int):
class PlaceMoveForm(FlaskForm):
code = wtforms.StringField(validators=[validators.DataRequired()], render_kw={'autofocus': True})
new_parent = mo_fields.Place(validators=[validators.DataRequired()], render_kw={'autofocus': True})
submit = wtforms.SubmitField('Najít místo')
reset = wtforms.HiddenField()
move = wtforms.HiddenField()
class PlaceMoveConfirmForm(FlaskForm):
code = wtforms.HiddenField()
new_parent = mo_fields.Place(widget = wtforms.widgets.HiddenInput())
reset = wtforms.SubmitField('Zrušit')
move = wtforms.SubmitField('Přesunout')
......@@ -206,14 +207,14 @@ def org_place_move(id: int):
form = PlaceMoveForm()
form_confirm = None
if form.validate_on_submit():
if not form.validate_on_submit():
if form.new_parent.place_error:
search_error = form.new_parent.place_error
else:
if form.reset.data:
return redirect(url_for('org_place_move', id=id))
new_parent = db.get_place_by_code(form.code.data)
if not new_parent:
search_error = 'Místo s tímto kódem se nepovedlo nalézt'
else:
new_parent = form.new_parent.place
new_parents = reversed(g.gatekeeper.get_parents(new_parent))
(_, levels) = db.place_type_names_and_levels[place.type]
......@@ -241,7 +242,9 @@ def org_place_move(id: int):
else:
# OK but not confirmed yet, display the confirm form
form_confirm = PlaceMoveConfirmForm()
form_confirm.code.data = form.code.data
form_confirm.new_parent.data = form.new_parent.data
# tady se používá hnusný trik, že políčko new_parents z PlaceMoveConfirmForm se
# parsuje jako new_parents z PlaceMoveForm
return render_template(
'org_place_move.html',
......
......@@ -22,7 +22,7 @@ import mo.imports
from mo.rights import Right, RoundRights
import mo.util
from mo.web import app
from mo.web.util import MODecimalField
import mo.web.fields as mo_fields
from mo.web.org_contest import ParticipantsActionForm, ParticipantsFilterForm, get_contestant_emails, get_contestants_query, make_contestant_table, \
generic_import, generic_batch_download, generic_batch_upload, generic_batch_points
......@@ -104,7 +104,7 @@ def delete_task(round_id: int, form: TaskDeleteForm) -> bool:
class AddContestForm(FlaskForm):
place_code = wtforms.StringField('Nová soutěž v oblasti:', validators=[validators.Required()])
place = mo_fields.Place('Nová soutěž v oblasti:', validators=[validators.Required()])
create_contest = wtforms.SubmitField('Založit')
......@@ -112,10 +112,7 @@ def add_contest(round: db.Round, form: AddContestForm) -> bool:
if not (request.method == 'POST' and 'create_contest' in request.form and form.validate_on_submit()):
return False
place = db.get_place_by_code(form.place_code.data)
if place is None:
flash(f'Místo s kódem {form.place_code.data} neexistuje', 'danger')
return False
place: db.Place = form.place.place
if place.level != round.level:
flash(f'{place.type_name().title()} {place.name} není {round.get_level().name}', 'danger')
......@@ -229,7 +226,7 @@ def org_round(id: int):
return redirect(url_for('org_round', id=id))
form_add_contest = AddContestForm()
form_add_contest.place_code.label.text = "Nová soutěž " + round.get_level().in_name()
form_add_contest.place.label.text = "Nová soutěž " + round.get_level().in_name()
if add_contest(round, form_add_contest):
return redirect(url_for('org_round', id=id))
......@@ -259,7 +256,7 @@ class TaskEditForm(FlaskForm):
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 = MODecimalField(
max_points = mo_fields.Points(
'Maximum bodů', validators=[validators.Optional(), validators.NumberRange(min=0)],
description="Při nastavení maxima nelze udělit více bodů, pro zrušení uložte prázdnou hodnotu",
)
......@@ -372,14 +369,14 @@ def org_round_list(id: int):
can_edit = rr.have_right(Right.manage_round) and request.endpoint != 'org_round_list_emails'
format = request.args.get('format', "")
filter = ParticipantsFilterForm(request.args)
filter = ParticipantsFilterForm(formdata=request.args)
filter.validate()
query = get_contestants_query(
round=master_round,
school=filter.f_school,
contest_place=filter.f_contest_place,
participation_place=filter.f_participation_place,
participation_state=filter.f_participation_state,
school=filter.school.place,
contest_place=filter.contest_place.place,
participation_place=filter.participation_place.place,
participation_state=mo.util.star_is_none(filter.participation_state.data),
)
action_form = None
......@@ -419,22 +416,6 @@ def org_round_import(id: int):
return generic_import(round, master_round, None, None)
class MODateTimeField(wtforms.DateTimeField):
def __init__(self, label, format='%Y-%m-%d %H:%M', description='Ve formátu 2000-01-01 12:34', **kwargs):
super().__init__(label, format=format, description=description, **kwargs)
def process_data(self, valuelist):
super().process_data(valuelist)
if self.data is not None:
self.data = self.data.astimezone()
def process_formdata(self, valuelist):
super().process_formdata(valuelist)
if self.data is not None:
self.data = self.data.astimezone()
class RoundEditForm(FlaskForm):
_for_round: Optional[db.Round] = None
......@@ -445,16 +426,16 @@ class RoundEditForm(FlaskForm):
)
# Only the desktop Firefox does not support datetime-local field nowadays,
# other browsers does provide date and time picker UI :(
ct_tasks_start = MODateTimeField("Čas zveřejnění úloh pro účastníky", validators=[validators.Optional()])
pr_tasks_start = MODateTimeField("Čas zveřejnění úloh pro dozor", validators=[validators.Optional()])
ct_submit_end = MODateTimeField("Konec odevzdávání pro účastníky", validators=[validators.Optional()])
pr_submit_end = MODateTimeField("Konec odevzdávání pro dozor", validators=[validators.Optional()])
ct_tasks_start = mo_fields.DateTime("Čas zveřejnění úloh pro účastníky", validators=[validators.Optional()])
pr_tasks_start = mo_fields.DateTime("Čas zveřejnění úloh pro dozor", validators=[validators.Optional()])
ct_submit_end = mo_fields.DateTime("Konec odevzdávání pro účastníky", validators=[validators.Optional()])
pr_submit_end = mo_fields.DateTime("Konec odevzdávání pro dozor", validators=[validators.Optional()])
score_mode = wtforms.SelectField("Výsledková listina", choices=db.RoundScoreMode.choices(), coerce=db.RoundScoreMode.coerce)
score_winner_limit = MODecimalField(
score_winner_limit = mo_fields.Points(
"Hranice bodů pro vítěze", validators=[validators.Optional(), validators.NumberRange(min=0)],
description="Řešitelé s alespoň tolika body budou označeni za vítěze, prázdná hodnota = žádné neoznačovat",
)
score_successful_limit = MODecimalField(
score_successful_limit = mo_fields.Points(
"Hranice bodů pro úspěšné řešitele", validators=[validators.Optional(), validators.NumberRange(min=0)],
description="Řešitelé s alespoň tolika body budou označeni za úspěšné řešitele, prázdná hodnota = žádné neoznačovat",
)
......
......@@ -29,57 +29,25 @@ class UsersFilterForm(PagerForm):
search_email = wtforms.TextField("E-mail")
# participants
year = wtforms.IntegerField("Ročník")
school_code = wtforms.StringField("Škola")
year = mo_fields.OptionalInt("Ročník")
school = mo_fields.School()
# rounds->participations
round_year = wtforms.IntegerField("Ročník")
round_year = mo_fields.OptionalInt("Ročník")
round_category = wtforms.SelectField("Kategorie")
round_seq = wtforms.SelectField("Kolo")
contest_site_code = wtforms.StringField("Soutěžní oblast")
contest_site = mo_fields.Place("Soutěžní oblast")
participation_state = wtforms.SelectField('Účast', choices=[('*', '*')] + list(db.PartState.choices()))
submit = wtforms.SubmitField("Filtrovat")
# Výstupní hodnoty filtru, None při nepoužitém filtru, prázdná db hodnota při
# nepovedené filtraci (neexistující místo a podobně)
f_search_name: Optional[str] = None
f_search_email: Optional[str] = None
f_year: Optional[int] = None
f_school: Optional[db.Place] = None
f_round_category: Optional[str] = None
f_round_seq: Optional[int] = None
f_contest_site: Optional[db.Place] = None
f_participation_state: Optional[db.PartState] = None
def __init__(self, formdata, **kwargs):
super().__init__(formdata=formdata, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.round_category.choices = ['*'] + sorted(db.get_categories())
self.round_seq.choices = ['*'] + sorted(db.get_seqs())
def validate(self):
self.f_search_name = f"%{self.search_name.data}%" if self.search_name.data else None
self.f_search_email = f"%{self.search_email.data}%" if self.search_email.data else None
self.f_year = self.year.data
self.f_round_year = self.round_year.data
if self.school_code.data:
self.f_school = db.get_place_by_code(self.school_code.data)
if not self.f_school:
flash(f"Zadaná škola '{self.school_code.data}' neexistuje", "danger")
self.f_school = db.Place()
if self.contest_site_code.data:
self.f_contest_site = db.get_place_by_code(self.contest_site_code.data)
if not self.f_contest_site:
flash(f"Zadaná soutěžní oblast '{self.contest_site_code.data}' neexistuje", "danger")
self.f_contest_site = db.Place()
self.f_round_category = None if self.round_category.data == '*' else self.round_category.data
self.f_round_seq = None if self.round_seq.data == '*' else self.round_seq.data
self.f_participation_state = None if self.participation_state.data == '*' else self.participation_state.data
@app.route('/org/user/')
@app.route('/org/user/', methods=('GET', 'POST'))
def org_users():
sess = db.get_session()
rr = g.gatekeeper.rights_generic()
......@@ -87,32 +55,33 @@ def org_users():
q = sess.query(db.User).filter_by(is_admin=False, is_org=False).options(
subqueryload(db.User.participants).joinedload(db.Participant.school_place)
)
filter = UsersFilterForm(request.args)
filter = UsersFilterForm(formdata=request.args)
if request.args:
filter.validate()
if filter.f_search_name:
if filter.search_name.data:
q = q.filter(or_(
db.User.first_name.ilike(filter.f_search_name),
db.User.last_name.ilike(filter.f_search_name)
db.User.first_name.ilike(f"%{filter.search_name.data}%"),
db.User.last_name .ilike(f"%{filter.search_name.data}%")
))
if filter.f_search_email:
q = q.filter(db.User.email.ilike(filter.f_search_email))
if filter.search_email.data:
q = q.filter(db.User.email.ilike(f"%{filter.search_email.data}%"))
if filter.f_year or filter.f_school:
if filter.year.data or filter.school.place:
participant_filter = sess.query(db.Participant.user_id)
if filter.f_year:
participant_filter = participant_filter.filter_by(year=filter.f_year)
if filter.f_school:
participant_filter = participant_filter.filter_by(school=filter.f_school.place_id)
if filter.year.data:
participant_filter = participant_filter.filter_by(year=filter.year.data)
if filter.school.place:
participant_filter = participant_filter.filter_by(school=filter.school.place.place_id)
q = q.filter(db.User.user_id.in_(participant_filter))
round_filter = sess.query(db.Round.round_id)
round_filter_apply = False
if filter.f_round_year:
round_filter = round_filter.filter_by(year=filter.f_round_year)
if filter.round_year.data:
round_filter = round_filter.filter_by(year=filter.round_year.data)
round_filter_apply = True
if filter.f_round_category:
round_filter = round_filter.filter_by(category=filter.f_round_category)
if filter.round_category.data and filter.round_category.data != "*":
round_filter = round_filter.filter_by(category=filter.round_category.data)
round_filter_apply = True
if filter.round_seq.data and filter.round_seq.data != "*":
round_filter = round_filter.filter_by(seq=filter.round_seq.data)
......@@ -123,8 +92,8 @@ def org_users():
if round_filter_apply:
contest_filter = contest_filter.filter(db.Contest.round_id.in_(round_filter))
contest_filter_apply = True
if filter.f_contest_site:
contest_filter = contest_filter.filter_by(place_id=filter.f_contest_site.place_id)
if filter.contest_site.place:
contest_filter = contest_filter.filter_by(place_id=filter.contest_site.place.place_id)
contest_filter_apply = True
participation_filter = sess.query(db.Participation.user_id)
......@@ -132,8 +101,8 @@ def org_users():
if contest_filter_apply:
participation_filter = participation_filter.filter(db.Participation.contest_id.in_(contest_filter))
participation_filter_apply = True
if filter.f_participation_state:
participation_filter = participation_filter.filter_by(state=filter.f_participation_state)
if filter.participation_state.data and filter.participation_state.data != '*':
participation_filter = participation_filter.filter_by(state=filter.participation_state.data)
participation_filter_apply = True
if participation_filter_apply:
......@@ -150,18 +119,19 @@ def org_users():
can_add=rr.have_right(Right.add_users),
)
class OrgsFilterForm(PagerForm):
# user
search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True})
search_email = wtforms.TextField("E-mail")
search_role = wtforms.SelectMultipleField('Role', choices=db.RoleType.choices(), coerce=db.RoleType.coerce, validators=[validators.Optional()])
search_right_for_place_code = wtforms.StringField('Právo pro oblast', validators=[validators.Optional()])
search_in_place_code = wtforms.StringField('V oblasti', validators=[validators.Optional()])
search_right_for_place = mo_fields.Place('Právo pro oblast', validators=[validators.Optional()])
search_in_place = mo_fields.Place('V oblasti', validators=[validators.Optional()])
search_place_level = wtforms.SelectMultipleField("Úroveň oblasti", choices=[(i.level, i.name) for i in db.place_levels], validators=[validators.Optional()], coerce=int)
search_year = wtforms.StringField('Ročník', validators=[validators.Optional()])
search_year = mo_fields.IntList('Ročník', validators=[validators.Optional()])
search_category = wtforms.StringField("Kategorie", validators=[validators.Optional()])
search_seq = wtforms.StringField("Kolo", validators=[validators.Optional()])
search_seq = mo_fields.IntList("Kolo", validators=[validators.Optional()])
submit = wtforms.SubmitField("Filtrovat")
show_role_filter = wtforms.SubmitField("Zobrazit filtrování dle rolí")
......@@ -169,54 +139,6 @@ class OrgsFilterForm(PagerForm):
is_role_filter = wtforms.HiddenField(default="") # "" -> skrýt. "yes" -> zobrazit
# Výstupní hodnoty filtru, None při nepoužitém filtru nebo špatné hodnotě (takové filtry ignorujeme)
f_search_name: Optional[str] = None
f_search_email: Optional[str] = None
f_search_role: Optional[List[db.RoleType]] = None
f_search_right_for_place: Optional[db.Place] = None
f_search_in_place: Optional[db.Place] = None
f_search_year: Optional[List[int]] = None
f_search_category: Optional[List[str]] = None
f_search_place_level: Optional[List[int]] = None
f_search_seq: Optional[List[int]] = None
def validate_search_name(self, field):
self.f_search_name = f"%{field.data}%"
def validate_search_email(self, field):
self.f_search_email = f"%{field.data}%"
def validate_search_role(self, field):
self.f_search_role = field.data
def validate_search_right_for_place_code(self, field):
self.f_search_right_for_place = db.get_place_by_code(field.data)
if self.f_search_right_for_place is None:
raise wtforms.ValidationError("Chybné označení oblasti")
def validate_search_in_place_code(self, field):
self.f_search_in_place = db.get_place_by_code(field.data)
if self.f_search_in_place is None:
raise wtforms.ValidationError("Chybné označení oblasti")
def validate_search_year(self, field):
try:
self.f_search_year = mo.util.parse_int_list(field.data)
except mo.CheckError as e:
raise wtforms.ValidationError(str(e))
def validate_search_category(self, field):
self.f_search_category = field.data.split(",")
def validate_search_place_level(self, field):
self.f_search_place_level = field.data
def validate_search_seq(self, field):
try:
self.f_search_seq = mo.util.parse_int_list(field.data)
except mo.CheckError as e:
raise wtforms.ValidationError(str(e))
def prepare_role_filter(self):
if self.show_role_filter.data:
self.is_role_filter.data = "yes"
......@@ -227,8 +149,8 @@ class OrgsFilterForm(PagerForm):
else:
del self.hide_role_filter
del self.search_role
del self.search_right_for_place_code
del self.search_in_place_code
del self.search_right_for_place
del self.search_in_place
del self.search_place_level
del self.search_year
del self.search_category
......@@ -242,34 +164,39 @@ def org_orgs():
q = sess.query(db.User).filter(or_(db.User.is_admin, db.User.is_org)).options(
subqueryload(db.User.roles).joinedload(db.UserRole.place)
)
filter = OrgsFilterForm()
filter.validate_on_submit()
filter = OrgsFilterForm(formdata=request.args)
if request.args:
filter.validate()
filter.prepare_role_filter()
if filter.f_search_name:
if filter.search_name.data:
q = q.filter(or_(
db.User.first_name.ilike(filter.f_search_name),
db.User.last_name.ilike(filter.f_search_name)
db.User.first_name.ilike(f"%{filter.search_name.data}%"),
db.User.last_name .ilike(f"%{filter.search_name.data}%")
))
if filter.search_email.data:
q = q.filter(db.User.email.ilike(f"%{filter.search_email.data}%"))
def query_filter_role(qr: flask_sqlalchemy.BaseQuery) -> flask_sqlalchemy.BaseQuery:
if filter.f_search_role is not None:
qr = qr.filter(db.UserRole.role.in_(filter.f_search_role))
if filter.f_search_category is not None:
qr = qr.filter(or_(db.UserRole.category.in_(filter.f_search_category), db.UserRole.category == None))
if filter.f_search_seq is not None:
qr = qr.filter(or_(db.UserRole.seq.in_(filter.f_search_seq), db.UserRole.seq == None))
if filter.f_search_year is not None:
qr = qr.filter(or_(db.UserRole.year.in_(filter.f_search_year), db.UserRole.year == None))
if filter.f_search_in_place is not None:
qr = qr.filter(db.UserRole.place_id.in_(db.place_descendant_cte(filter.f_search_in_place)))
if filter.f_search_right_for_place is not None:
qr = qr.filter(db.UserRole.place_id.in_([x.place_id for x in db.get_place_parents(filter.f_search_right_for_place)]))
if filter.search_role.data:
qr = qr.filter(db.UserRole.role.in_(filter.search_role.data))
if filter.search_category.data:
qr = qr.filter(or_(db.UserRole.category.in_(filter.search_category.data.split(",")), db.UserRole.category == None))
if filter.search_seq.list:
qr = qr.filter(or_(db.UserRole.seq.in_(filter.search_seq.list), db.UserRole.seq == None))
if filter.search_year.list:
qr = qr.filter(or_(db.UserRole.year.in_(filter.search_year.list), db.UserRole.year == None))
pass
if filter.search_in_place.place is not None:
qr = qr.filter(db.UserRole.place_id.in_(db.place_descendant_cte(filter.search_in_place.place)))
if filter.search_right_for_place.place is not None:
qr = qr.filter(db.UserRole.place_id.in_([x.place_id for x in db.get_place_parents(filter.search_right_for_place.place)]))
# Po n>3 hodinách v mo.db jsem dospěl k závěru, že to hezčeji neumím (neumím vyrobit place_parents_cte)
if filter.f_search_place_level is not None:
if filter.search_place_level.data:
qr = qr.filter(db.UserRole.place_id.in_(
sess.query(db.Place.place_id).filter(db.Place.level.in_(filter.f_search_place_level))
sess.query(db.Place.place_id).filter(db.Place.level.in_(filter.search_place_level.data))
))
print(qr)
return qr
if filter.is_role_filter.data:
......@@ -300,7 +227,7 @@ def org_orgs():
class FormAddRole(FlaskForm):
role = wtforms.SelectField('Role', choices=db.RoleType.choices(), coerce=db.RoleType.coerce, render_kw={'autofocus': True})
place_code = wtforms.StringField('Oblast')
place = mo_fields.Place()
year = wtforms.IntegerField('Ročník', validators=[validators.Optional()])
category = wtforms.StringField("Kategorie", validators=[validators.Length(max=2)], filters=[lambda x: x or None])
seq = wtforms.IntegerField("Kolo", validators=[validators.Optional()])
......@@ -360,14 +287,7 @@ def org_org(id: int):
new_role.assigned_by = g.user.user_id
ok = True
place_code = form_add_role.place_code.data
if place_code:
place = db.get_place_by_code(place_code)
if not place:
role_errors.append("Nepovedlo se nalézt místo podle kódu")
ok = False
else:
new_role.place = place
new_role.place = form_add_role.place.place
if not g.gatekeeper.can_set_role(new_role):
role_errors.append(f'Roli "{new_role}" nelze přidělit, není podmnožinou žádné vaší role')
......@@ -454,20 +374,14 @@ def org_user(id: int):
class UserEditForm(FlaskForm):
first_name = wtforms.StringField("Jméno", validators=[Required()], render_kw={'autofocus': True})
last_name = wtforms.StringField("Příjmení", validators=[Required()])
email = wtforms.StringField("E-mail", validators=[Required()])
first_name = mo_fields.FirstName(validators=[Required()], render_kw={'autofocus': True})
last_name = mo_fields.LastName(validators=[Required()])
email = mo_fields.Email(validators=[Required()])
note = wtforms.TextAreaField("Poznámka")
is_test = wtforms.BooleanField("Testovací účet")
allow_duplicate_name = wtforms.BooleanField("Přidat účet s duplicitním jménem")
submit = wtforms.SubmitField("Uložit")
def validate_email(form, field):
try:
field.data = mo.users.normalize_email(field.data)
except mo.CheckError as e:
raise wtforms.ValidationError(str(e))
@app.route('/org/org/<int:id>/edit', methods=("GET", "POST"), endpoint="org_org_edit")
@app.route('/org/user/<int:id>/edit', methods=("GET", "POST"))
......
......@@ -16,9 +16,9 @@ Seznam účastníků {% if site %}v soutěžním místě {{ site.name }}{% else
<form action="" method="GET" class="form form-inline" role="form">
<div class="form-row">
{% if not site %}
{{ wtf.form_field(filter.participation_place, placeholder='Kód', size=8) }}
{{ wtf.form_field(filter.participation_place, size=8) }}
{% endif %}
{{ wtf.form_field(filter.school, placeholder='Kód', size=8) }}
{{ wtf.form_field(filter.school, size=8) }}
{{ wtf.form_field(filter.participation_state) }}
<div class="btn-group">
{{ wtf.form_field(filter.submit, class='btn btn-primary') }}
......
......@@ -54,7 +54,7 @@
<form action="" method="POST" class="form form-inline" role="form">
{{ form_add_role.csrf_token() }}
{{ wtf.form_field(form_add_role.role) }}
{{ wtf.form_field(form_add_role.place_code, placeholder='Kód', size=8) }}
{{ wtf.form_field(form_add_role.place, size=8) }}
{{ wtf.form_field(form_add_role.year, type='number', size=3, maxlength=2) }}
{{ wtf.form_field(form_add_role.category, size=2, maxlength=2) }}
{{ wtf.form_field(form_add_role.seq, type='number', size=3, maxlength=2) }}
......
......@@ -7,8 +7,7 @@
{% endif %}
<div class="form-frame">
<form action="" method="POST" role="form">
{{ filter.csrf_token }}
<form action="" method="GET" role="form">
{{ filter.is_role_filter }}
<div class="row">
<div class='col-sm-2'><strong>Filtr organizátorů</strong></div>
......@@ -33,8 +32,8 @@
{{ wtf.form_field(filter.search_seq, placeholder='Např. 1,3-4') }}
</div>
<div class="col-sm-2">
<span title="Omezí role na ty, které jsou přiděleny k dané oblasti a nebo její podoblasti.">{{ wtf.form_field(filter.search_in_place_code, placeholder='Kód oblasti') }}</span>
<span title="Omezí role na ty, které mají právo k dané oblasti. Tedy mohou být přiděleny i k nadřazené oblasti.">{{ wtf.form_field(filter.search_right_for_place_code, placeholder='Kód oblasti') }}</span>
<span title="Omezí role na ty, které jsou přiděleny k dané oblasti a nebo její podoblasti.">{{ wtf.form_field(filter.search_in_place) }}</span>
<span title="Omezí role na ty, které mají právo k dané oblasti. Tedy mohou být přiděleny i k nadřazené oblasti.">{{ wtf.form_field(filter.search_right_for_place) }}</span>
</div>
<div class="col-sm-2">
{{ wtf.form_field(filter.search_place_level, size=filter.search_place_level.choices|length, class="form-control no-scroll" ) }}
......
......@@ -122,7 +122,7 @@
{% if can_add_contest %}
<form action="" method="POST" class="form-inline">
{{ form_add_contest.csrf_token() }}
{{ wtf.form_field(form_add_contest.place_code) }}
{{ wtf.form_field(form_add_contest.place) }}
{{ wtf.form_field(form_add_contest.create_contest) }}
</form>
{% endif %}
......
......@@ -11,9 +11,9 @@
<div class="form-frame">
<form action="" method="GET" class="form form-inline" role="form">
<div class="form-row">
{{ wtf.form_field(filter.contest_place, placeholder='Kód', size=8) }}
{{ wtf.form_field(filter.participation_place, placeholder='Kód', size=8) }}
{{ wtf.form_field(filter.school, placeholder='Kód', size=8) }}
{{ wtf.form_field(filter.contest_place, size=8) }}
{{ wtf.form_field(filter.participation_place, size=8) }}
{{ wtf.form_field(filter.school, size=8) }}
{{ wtf.form_field(filter.participation_state) }}
</div>
<div class="form-row" style="margin-top: 5px;">
......
......@@ -20,7 +20,7 @@
{{ wtf.form_field(filter.round_seq) }}
</div>
<div class="col-sm-2">
{{ wtf.form_field(filter.contest_site_code, placeholder='Kód') }}
{{ wtf.form_field(filter.contest_site, placeholder='Kód') }}
</div>
<div class="col-sm-2">
{{ wtf.form_field(filter.participation_state) }}
......@@ -32,7 +32,7 @@
{{ wtf.form_field(filter.year) }}
</div>
<div class="col-sm-2">
{{ wtf.form_field(filter.school_code, placeholder='Kód') }}
{{ wtf.form_field(filter.school, placeholder='Kód') }}
</div>
<div class="col-sm-3">
{{ wtf.form_field(filter.search_name, placeholder='Libovolná část jména') }}
......
import decimal
from flask import Response, send_file, url_for
from flask_wtf import FlaskForm
import os
......@@ -131,22 +130,6 @@ def send_job_result(job: db.Job) -> Response:
raise werkzeug.exceptions.NotFound()
class MODecimalField(DecimalField):
"""Upravený DecimalField, který formátuje číslo podle jeho skutečného počtu
desetinných míst a zadané `places` používá jen jako maximální počet desetinných míst."""
def _value(self):
if self.data is not None:
# Spočítání počtu desetinných míst, zbytek necháme na původní implementaci
max_places = self.places
self.places = 0
d = decimal.Decimal(1)
while self.data % d != 0 and self.places < max_places:
self.places += 1
d /= 10
return super(MODecimalField, self)._value()
def user_html_flags(u: db.User) -> Markup:
r = []
if u.is_test:
......