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 (9)
......@@ -3,6 +3,7 @@
import argparse
import sys
import mo.config as config
import mo.db as db
import mo.users
import mo.util
......@@ -10,6 +11,7 @@ import mo.util
parser = argparse.ArgumentParser(description='Resetuje uživateli heslo a pošle mail')
parser.add_argument(dest='email', help='e-mailová adresa')
parser.add_argument('--new', default=False, action='store_true', help='pošle mail o založení účtu')
parser.add_argument('--mail-instead', metavar='EMAIL', default=None, help='pošle mail někomu jinému')
args = parser.parse_args()
......@@ -23,6 +25,9 @@ if user is None:
token = mo.users.ask_reset_password(user)
session.commit()
if args.mail_instead:
mo.config.MAIL_INSTEAD = args.mail_instead
if args.new:
mo.util.send_new_account_email(user, token)
else:
......
......@@ -240,6 +240,7 @@ class User(Base):
note = Column(Text, nullable=False, server_default=text("''::text"))
roles = relationship('UserRole', primaryjoin='UserRole.user_id == User.user_id')
participants = relationship('Participant', primaryjoin='Participant.user_id == User.user_id')
def full_name(self) -> str:
return self.first_name + ' ' + self.last_name
......@@ -532,7 +533,7 @@ def get_categories() -> List[str]:
return [cat for (cat,) in get_session().query(Round.category).distinct()]
def get_seqs() -> List[str]:
def get_seqs() -> List[int]:
return [seq for (seq,) in get_session().query(Round.seq).distinct()]
......
......@@ -76,6 +76,12 @@ def need_login():
def init_request():
path = request.path
if path.startswith('/static/'):
# Pro statické soubory v development nasazení nepotřebujeme nastavovat
# nic dalšího (v ostrém nasazení je servíruje uwsgi)
return
if 'uid' in session:
user = mo.users.user_by_uid(session['uid'])
if not user:
......@@ -91,7 +97,6 @@ def init_request():
# K některým podstromům je nutné mít speciální oprávnění
# XXX: Když celá aplikace běží v adresáři, request.path je relativní ke kořeni aplikace, ne celého webu
path = request.path
if path.startswith('/org/'):
if not user:
raise NeedLoginError()
......
......@@ -564,12 +564,20 @@ class SubmitForm(FlaskForm):
submit_fb = wtforms.SubmitField('Odevzdat opravené')
class SetFinalForm(FlaskForm):
type = wtforms.StringField()
paper_id = wtforms.IntegerField()
submit = wtforms.SubmitField("Prohlásit za finální")
@app.route('/org/contest/c/<int:contest_id>/submit/<int:user_id>/<int:task_id>/', methods=('GET', 'POST'))
@app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/submit/<int:user_id>/<int:task_id>/', methods=('GET', 'POST'))
def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Optional[int] = None):
sc = get_solution_context(contest_id, user_id, task_id, site_id)
sess = db.get_session()
self_url = url_for('org_submit_list', contest_id=contest_id, user_id=user_id, task_id=task_id, site_id=site_id)
form: Optional[SubmitForm] = None
if sc.allow_upload_solutions or sc.allow_upload_feedback:
form = SubmitForm()
......@@ -594,7 +602,6 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
assert sc.task is not None and sc.user is not None
paper = db.Paper(task=sc.task, for_user_obj=sc.user, uploaded_by_obj=g.user, type=type, note=form.note.data)
submitter = mo.submit.Submitter(instance_path=app.instance_path)
self_url = url_for('org_submit_list', contest_id=contest_id, user_id=user_id, task_id=task_id, site_id=site_id)
try:
submitter.submit_paper(paper, tmp_path)
......@@ -632,6 +639,53 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
.filter_by(user_id=user_id, task_id=task_id)
.one_or_none())
set_final_form: Optional[SetFinalForm] = None
if sol and sc.allow_upload_feedback:
set_final_form = SetFinalForm()
if set_final_form.validate_on_submit():
is_submit = set_final_form.type.data == "submit"
is_feedback = set_final_form.type.data == "feedback"
paper = sess.query(db.Paper).get(set_final_form.paper_id.data)
if not paper:
flash('Chyba: Papír s takovým ID neexistuje', 'danger')
elif paper.for_user != user_id or paper.for_task != task_id:
flash('Chyba: Papír nepatří k dané úloze a uživateli', 'danger')
elif (is_submit and sol.final_submit_obj == paper) or (is_feedback and sol.final_feedback_obj == paper):
flash('Žádná změna', 'warning')
elif is_submit:
app.logger.info(f"Finální submit úlohy {task_id} pro uživatele {user_id} změněn na {paper.paper_id}")
mo.util.log(
type=db.LogType.participant,
what=user_id,
details={
'action': 'task-final-submit-changed',
'task': task_id,
'old_paper': sol.final_submit,
'new_paper': paper.paper_id
},
)
sol.final_submit = paper.paper_id
sess.commit()
flash('Finální řešení změněno', 'success')
elif is_feedback:
app.logger.info(f"Finální feedback úlohy {task_id} pro uživatele {user_id} změněn na {paper.paper_id}")
mo.util.log(
type=db.LogType.participant,
what=user_id,
details={
'action': 'task-final-feedback-changed',
'task': task_id,
'old_paper': sol.final_feedback,
'new_paper': paper.paper_id
},
)
sol.final_feedback = paper.paper_id
sess.commit()
flash('Finální oprava změněna', 'success')
else:
flash('Chyba: Neplatná akce', 'danger')
return redirect(self_url)
papers = (sess.query(db.Paper)
.filter_by(for_user_obj=sc.user, task=sc.task)
.options(joinedload(db.Paper.uploaded_by_obj))
......@@ -664,6 +718,7 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
for_site=(site_id is not None),
paper_link=paper_link,
form=form,
set_final_form=set_final_form,
)
......
from typing import Optional
from flask import render_template, g, redirect, url_for, flash, request
from flask_wtf import FlaskForm
import werkzeug.exceptions
......@@ -19,6 +20,10 @@ from mo.web.util import PagerForm
class UsersFilterForm(PagerForm):
# user
search_name = wtforms.TextField("Jméno/příjmení")
search_email = wtforms.TextField("E-mail")
# participants
year = wtforms.IntegerField("Ročník")
school_code = wtforms.StringField("Škola")
......@@ -32,45 +37,79 @@ class UsersFilterForm(PagerForm):
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)
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/')
def org_users():
sess = db.get_session()
rr = g.gatekeeper.rights_generic()
q = sess.query(db.User).filter_by(is_admin=False, is_org=False)
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.validate()
filter_errors = []
if filter.f_search_name:
q = q.filter(or_(
db.User.first_name.ilike(filter.f_search_name),
db.User.last_name.ilike(filter.f_search_name)
))
if filter.f_search_email:
q = q.filter(db.User.email.ilike(filter.f_search_email))
if filter.f_year or filter.f_school:
participant_filter = sess.query(db.Participant.user_id)
participant_filter_apply = False
if filter.year.data:
participant_filter = participant_filter.filter_by(year=filter.year.data)
participant_filter_apply = True
if filter.school_code.data:
place = db.get_place_by_code(filter.school_code.data)
if place:
participant_filter = participant_filter.filter_by(school=place.place_id)
participant_filter_apply = True
else:
filter_errors.append("Neexistující kód školy")
if participant_filter_apply:
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)
q = q.filter(db.User.user_id.in_(participant_filter))
round_filter = sess.query(db.Round.round_id)
round_filter_apply = False
if filter.round_year.data:
round_filter = round_filter.filter_by(year=filter.round_year.data)
if filter.f_round_year:
round_filter = round_filter.filter_by(year=filter.f_round_year)
round_filter_apply = True
if filter.round_category.data and filter.round_category.data != "*":
round_filter = round_filter.filter_by(category=filter.round_category.data)
if filter.f_round_category:
round_filter = round_filter.filter_by(category=filter.f_round_category)
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)
......@@ -81,21 +120,17 @@ 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.contest_site_code.data:
place = db.get_place_by_code(filter.contest_site_code.data)
if place:
contest_filter = contest_filter.filter_by(place_id=place.place_id)
if filter.f_contest_site:
contest_filter = contest_filter.filter_by(place=filter.f_contest_site)
contest_filter_apply = True
else:
filter_errors.append("Neexistující kód soutěžního místa")
participation_filter = sess.query(db.Participation.user_id)
participation_filter_apply = False
if contest_filter_apply:
participation_filter = participation_filter.filter(db.Participation.contest_id.in_(contest_filter))
participation_filter_apply = True
if filter.participation_state.data and filter.participation_state.data != '*':
participation_filter = participation_filter.filter_by(state=filter.participation_state.data)
if filter.f_participation_state:
participation_filter = participation_filter.filter_by(state=filter.f_participation_state)
participation_filter_apply = True
if participation_filter_apply:
......@@ -107,16 +142,29 @@ def org_users():
return render_template(
'org_users.html', users=users, count=count,
filter=filter, filter_errors=filter_errors,
filter=filter,
can_edit=rr.have_right(Right.edit_users),
can_add=rr.have_right(Right.add_users),
)
class OrgsFilterForm(PagerForm):
# user
search_name = wtforms.TextField("Jméno/příjmení")
search_email = wtforms.TextField("E-mail")
# TODO: filtering by roles?
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
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
@app.route('/org/org/')
def org_orgs():
......@@ -127,27 +175,22 @@ def org_orgs():
subqueryload(db.User.roles).joinedload(db.UserRole.place)
)
filter = OrgsFilterForm(request.args)
# TODO: filtering by roles?
filter.validate()
count = db.get_count(q)
if filter.f_search_name:
q = q.filter(or_(
db.User.first_name.ilike(filter.f_search_name),
db.User.last_name.ilike(filter.f_search_name)
))
if filter.f_search_email:
q = q.filter(db.User.email.ilike(filter.f_search_email))
if not filter.offset.data:
filter.offset.data = 0
if not filter.limit.data:
filter.limit.data = 50
if filter.previous.data:
filter.offset.data = max(0, filter.offset.data - 50)
if filter.next.data:
filter.offset.data = min(count // 50 * 50, filter.offset.data + 50)
q = q.offset(filter.offset.data)
q = q.limit(filter.limit.data)
(count, q) = filter.apply_limits(q, pagesize=50)
users = q.all()
return render_template(
'org_orgs.html', users=users, count=count,
filter=filter, filter_errors=None,
filter=filter,
can_edit=rr.have_right(Right.edit_orgs),
can_add=rr.have_right(Right.add_orgs),
)
......@@ -248,7 +291,9 @@ def org_org(id: int):
@app.route('/org/user/<int:id>/', methods=('GET', 'POST'))
def org_user(id: int):
sess = db.get_session()
user = sess.query(db.User).get(id)
user = (sess.query(db.User)
.options(joinedload(db.User.roles).joinedload(db.User.roles.assigned_by_user))
.get(id))
if not user:
raise werkzeug.exceptions.NotFound()
if user.is_org or user.is_admin:
......
......@@ -3,14 +3,14 @@
<head>
<title>Odevzdávací systém MO</title>
<link rel=stylesheet href="{{ url_for('static', filename='bootstrap.min.css') }}" type='text/css' media=all>
<link rel=stylesheet href="{{ url_for('static', filename='mo.css') }}?v=6" type='text/css' media=all>
<link rel=stylesheet href="{{ url_for('static', filename='mo.css') }}?v=7" type='text/css' media=all>
{% block head %}{% endblock %}
</head>
<body>
<header class="flavor-{{web_flavor}}">
<div class="content">
<a href="http://www.matematickaolympiada.cz/" title="Na stránky MO">
<img src="{{ url_for('static', filename='img/mo.jpg') }}" alt="Matematická olympiáda">
<img src="{{ url_for('static', filename='img/mo.png') }}" alt="Matematická olympiáda">
</a>
<h1><b>O</b>devzdávací <b>S</b>ystém <b>M</b>atematické <b>O</b>lympiády</h1>
</div>
......
......@@ -29,9 +29,10 @@
{% include "parts/org_submit_warning.html" %}
<p><i>U každého účastníka je zobrazeno jeho poslední odevzdané řešení a přidělené body. Historii všech odevzdání, oprav a bodů
<p><i>U každého účastníka je zobrazeno jeho 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í.
{% if sc.allow_upload_feedback or sc.allow_edit_points %}Tamtéž můžete odevzdávat nové verze.{% endif %}
{% 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 %}
</i></p>
<p><i>Legenda k symbolům: <span class='sol-late'><b></b></span> odevzdané po termínu,
......@@ -42,8 +43,8 @@ naleznete v detailu řešení.
<thead>
<tr>
<th>Účastník
<th>Poslední řešení
<th>Poslední oprava
<th>Finální řešení
<th>Finální oprava
<th>Přidělené body
<th>Akce
</tr>
......
......@@ -51,16 +51,17 @@
<table class="data full">
<thead>
<tr>
<th>Role</th><th>Oblast</th><th>Ročník</th><th>Kategorie</th><th>Kolo</th><th>Akce</th>
<th>Role<th>Oblast<th>Ročník<th>Kategorie<th>Kolo<th>Přidělil<th>Akce
</tr>
</thead>
{% for role in user.roles %}
<tr>
<td>{{ roles_by_type[role.role].name }}</td>
<td><a href="{{ url_for('org_place', id=role.place_id) }}">{{ role.place.type_name() + ": " + role.place.name or '*' }}</a></td>
<td>{{ role.year or '*' }}</td>
<td>{{ role.category or '*' }}</td>
<td>{{ role.seq or '*' }}</td>
<td>{{ roles_by_type[role.role].name }}
<td><a href="{{ url_for('org_place', id=role.place_id) }}">{{ role.place.type_name() + ": " + role.place.name or '*' }}</a>
<td>{{ role.year or '*' }}
<td>{{ role.category or '*' }}
<td>{{ role.seq or '*' }}
<td>{% if role.assigned_by_user %}{{ role.assigned_by_user|user_link }}{% else %}správce{% endif %}
<td>
{% if can_assign_rights %}
<form action="" method="POST"{% if user == g.user -%}
......@@ -71,7 +72,6 @@
<button type="submit" class="btn btn-xs btn-danger">Odebrat</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
......
......@@ -7,15 +7,19 @@
<h2>Organizátoři</h2>
{% if filter_errors %}
<div class="alert alert-danger" role="alert">
{{ filter_errors|join("<br>") }}
</div>
{% endif %}
<form action="" method="GET" class="form" role="form">
<div class="form-frame">
<form action="" method="GET" role="form">
<div class="row">
<div class="form-group col-sm-6 btn-group">
<div class='col-sm-2'><strong>Filtr organizátorů</strong></div>
<div class="col-sm-3">
{{ wtf.form_field(filter.search_name, placeholder='Libovolná část jména') }}
</div>
<div class="col-sm-3">
{{ wtf.form_field(filter.search_email, placeholder='Libovolná část e-mailu') }}
</div>
</div>
<div class="btn-group">
{{ wtf.form_field(filter.submit, class='btn btn-primary') }}
{% if filter.offset.data > 0 %}
{{ wtf.form_field(filter.previous) }}
{% else %}
......@@ -26,15 +30,13 @@
{% else %}
<button class="btn" disabled>Další</button>
{% endif %}
{% set max = filter.offset.data + filter.limit.data if filter.offset.data + filter.limit.data < count else count %}
<br><br>Zobrazuji záznamy <b>{{filter.offset.data + 1}}</b><b>{{ max }}</b> z <b>{{count}} nalezených organizátorů</b>.
</div>
</div>
{% set max = filter.offset.data + filter.limit.data if filter.offset.data + filter.limit.data < count else count %}
Zobrazuji záznamy <b>{{filter.offset.data + 1}}</b><b>{{ max }}</b> z <b>{{count}} nalezených organizátorů</b>.
<input type="hidden" name="offset" value="{{filter.offset.data}}">
<input type="hidden" name="limit" value="{{filter.limit.data}}">
</form>
</div>
{% if users %}
<table class="data full">
......
......@@ -22,14 +22,16 @@
{% endfor %}
</table>
<h3>Vaše odvozená práva</h3>
{% if rights %}
<ul>
<!--
Odvozená práva:
{% if g.user.is_admin %}
admin
{% elif rights %}
{% for r in rights %}
<li>{{ r.name }}
{{ r.name }}
{% endfor %}
</ul>
{% else %}
<p>Žádná.
žádná
{% endif %}
-->
{% endblock %}
......@@ -25,6 +25,13 @@
<h3>Odevzdaná řešení</h3>
{% if sol_papers|length > 1 %}
<p><i>Existuje více než jedna verze řešení, finální je podbarvená.
{% if set_final_form %}Pomocí přepínačů u řešení můžete prohlásit za finální řešení i jiné, než naposledy odevzdané.{% endif %}
Účastník vždy vidí všechna svá řešení.
</i></p>
{% endif %}
{% if sol_papers %}
<table class=data>
<thead>
......@@ -49,7 +56,16 @@
<td>{{ p.bytes }}
<td>{{ p.uploaded_by_obj|user_link }}
<td>{% if late %}<span class='sol-late'>({{ late }})</span> {% endif %}{{ p.note }}
<td><a class='btn btn-xs btn-primary' href='{{ paper_link(p) }}'>Stáhnout</a>
<td><div class="btn-group">
<a class='btn btn-xs btn-primary' href='{{ paper_link(p) }}'>Stáhnout</a>
{% if p.paper_id != active_sol_id and set_final_form %}
<form class="btn-group" method="POST">
{{ set_final_form.csrf_token }}
<input type="hidden" name="type" value="submit"><input type="hidden" name="paper_id" value="{{ p.paper_id }}">
{{ wtf.form_field(set_final_form.submit, class="btn btn-xs btn-default") }}
</form>
{% endif %}
</div>
{% endfor %}
</table>
{% else %}
......@@ -58,6 +74,14 @@
<h3>Opravená řešení</h3>
{% if fb_papers|length > 1 %}
<p><i>Existuje více než jedna verze oprav, finální je podbarvená.
{% if set_final_form %}Pomocí přepínačů u oprav můžete prohlásit za finální opravu i jinou, než naposledy nahranou.{% endif %}
Účastník po uzavření kola uvidí
jen finální opravu a ani se nedozví, kolik různých verzí existovalo.
</i></p>
{% endif %}
{% if fb_papers %}
<table class=data>
<thead>
......@@ -81,7 +105,16 @@
<td>{{ p.bytes }}
<td>{{ p.uploaded_by_obj|user_link }}
<td>{{ p.note }}
<td><a class='btn btn-xs btn-primary' href='{{ paper_link(p) }}'>Stáhnout</a>
<td><div class="btn-group">
<a class='btn btn-xs btn-primary' href='{{ paper_link(p) }}'>Stáhnout</a>
{% if p.paper_id != active_fb_id and set_final_form %}
<form class="btn-group" method="POST">
{{ set_final_form.csrf_token }}
<input type="hidden" name="type" value="feedback"><input type="hidden" name="paper_id" value="{{ p.paper_id }}">
{{ wtf.form_field(set_final_form.submit, class="btn btn-xs btn-default") }}
</form>
{% endif %}
</div>
{% endfor %}
</table>
{% else %}
......
......@@ -7,40 +7,42 @@
<h2>Soutěžící</h2>
{% if filter_errors %}
<div class="alert alert-danger" role="alert">
{{ filter_errors|join("<br>") }}
</div>
{% endif %}
<form action="" method="GET" class="form" role="form">
<div class="form-frame">
<form action="" method="GET" role="form">
<div class="row">
<div class='col-sm-2'><strong>Účast v kole:</strong></div>
<div class="form-group col-sm-2">
<div class='col-sm-2'><strong>Filtr podle účasti v&nbsp;kole:</strong></div>
<div class="col-sm-2">
{{ wtf.form_field(filter.round_year) }}
</div>
<div class="form-group col-sm-2">
<div class="col-sm-2">
{{ wtf.form_field(filter.round_category) }}
</div>
<div class="form-group col-sm-2">
<div class="col-sm-2">
{{ wtf.form_field(filter.round_seq) }}
</div>
<div class="form-group col-sm-2">
<div class="col-sm-2">
{{ wtf.form_field(filter.contest_site_code, placeholder='Kód / #ID') }}
</div>
<div class="form-group col-sm-2">
<div class="col-sm-2">
{{ wtf.form_field(filter.participation_state) }}
</div>
</div>
<div class="row">
<div class='col-sm-2'><strong>Registrace:</strong></div>
<div class="form-group col-sm-2">
<div class='col-sm-2'><strong>Filtr podle přihlášky do ročníku:</strong></div>
<div class="col-sm-2">
{{ wtf.form_field(filter.year) }}
</div>
<div class="form-group col-sm-2">
<div class="col-sm-2">
{{ wtf.form_field(filter.school_code, placeholder='Kód / #ID') }}
</div>
<div class="form-group col-sm-6 btn-group">
<div class="col-sm-3">
{{ wtf.form_field(filter.search_name, placeholder='Libovolná část jména') }}
</div>
<div class="col-sm-3">
{{ wtf.form_field(filter.search_email, placeholder='Libovolná část e-mailu') }}
</div>
</div>
<div class="btn-group">
{{ wtf.form_field(filter.submit, class='btn btn-primary') }}
{% if filter.offset.data > 0 %}
{{ wtf.form_field(filter.previous) }}
......@@ -52,27 +54,32 @@
{% else %}
<button class="btn" disabled>Další</button>
{% endif %}
{% set max = filter.offset.data + filter.limit.data if filter.offset.data + filter.limit.data < count else count %}
<br><br>Zobrazuji záznamy <b>{{filter.offset.data + 1}}</b><b>{{ max }}</b> z <b>{{count}} nalezených soutěžících</b>.
</div>
</div>
{% set max = filter.offset.data + filter.limit.data if filter.offset.data + filter.limit.data < count else count %}
Zobrazuji záznamy <b>{{filter.offset.data + 1}}</b><b>{{ max }}</b> z <b>{{count}} nalezených soutěžících</b>.
<input type="hidden" name="offset" value="{{filter.offset.data}}">
<input type="hidden" name="limit" value="{{filter.limit.data}}">
</form>
</div>
{% if users %}
<table class="data full">
<thead>
<tr>
<th>Jméno</th><th>Příjmení</th><th>E-mail</th><th>Akce</th>
<th>Jméno</th><th>Příjmení</th><th>E-mail</th><th>Ročníky a školy účasti</th><th>Akce</th>
</tr>
</thead>
{% 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>{% if user.participants|count == 0 %}<i>v žádném ročníku</i>{% else %}
<ul>
{% for participant in user.participants %}
<li>{{ participant.year }}. ročník: <a href="{{ url_for('org_place', id=participant.school) }}">{{ participant.school_place.name }}</a></li>
{%- endfor %}
</ul>
{% endif %}</td>
<td><div class='btn-group'>
<a class="btn btn-xs btn-primary" href="{{ url_for('org_user', id=user.user_id) }}">Detail</a>
{% if can_edit %}
......
static/img/mo.jpg

28.5 KiB

static/img/mo.png

28.5 KiB

......@@ -150,6 +150,12 @@ nav#main-menu a.active {
color:red;
}
.form-frame {
padding: 10px;
border: 1px #ddd solid;
border-radius: 4px 4px;
}
/* Round states */
.rstate-preparing {
......