Skip to content
Snippets Groups Projects
Commit bb69eaeb authored by Jiří Setnička's avatar Jiří Setnička
Browse files

Vylepšení filtru seznamu účastníků a organizátorů

* Přepsání některých kontrol
* Při zadání nevalidního místa to již filtr neignoruje, ale správně
  nevypíše žádné výsledky
* Filtrování podle jména a emailu (SQL LIKE)
* Grafické přeskládání a zarámečkování filteru
parent 42d3036d
No related branches found
No related tags found
1 merge request!20Vylepšené vyhledávání
......@@ -533,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()]
......
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?
count = db.get_count(q)
filter.validate()
if not filter.offset.data:
filter.offset.data = 0
if not filter.limit.data:
filter.limit.data = 50
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.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),
)
......
......@@ -3,7 +3,7 @@
<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>
......
......@@ -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">
......
......@@ -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 %}
......
......@@ -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 {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment