Skip to content
Snippets Groups Projects
Commit 575100a2 authored by Martin Mareš's avatar Martin Mareš
Browse files

Merge branch 'jk/issue-183' into 'devel'

Přdáno filtrování orgů podle rolí

See merge request !88
parents e90188aa 4aa88fa4
No related branches found
No related tags found
1 merge request!88Přdáno filtrování orgů podle rolí
...@@ -13,7 +13,7 @@ import re ...@@ -13,7 +13,7 @@ import re
import secrets import secrets
import subprocess import subprocess
import sys import sys
from typing import Any, Optional, NoReturn, Tuple from typing import Any, Optional, NoReturn, Tuple, List
import textwrap import textwrap
import urllib.parse import urllib.parse
...@@ -247,3 +247,24 @@ def check_points(points: decimal.Decimal, for_task: Optional[db.Task] = None, fo ...@@ -247,3 +247,24 @@ def check_points(points: decimal.Decimal, for_task: Optional[db.Task] = None, fo
else: else:
return f'Podle nastavení kola zadat body jen s krokem {points_step} (hodnota {points} je neplatná)' return f'Podle nastavení kola zadat body jen s krokem {points_step} (hodnota {points} je neplatná)'
return None return None
def parse_int_list(a: str, maxim: int = 200) -> List[int]:
"""Parsuje "1-3,5,7-9" na [1,2,3,5,7,9].
Aby nešlo generovat moc velká pole (obrana proti DDoSu),
existuje omezení na velikost."""
r: List[int] = []
for i in a.split(","):
b = i.split("-")
if len(b) > 2:
raise mo.CheckError("Nadměrný počet pomlček")
try:
c = list(map(int, b))
except ValueError:
raise mo.CheckError("Převod na číslo se nezdařil")
if any(x < 0 or x > maxim for x in c):
raise mo.CheckError("Překročen limit na velikost čísla")
if len(c) == 2 and c[0] > c[1]:
raise mo.CheckError("Větší číslo nemůže být před menším")
r += [c[0]] if len(c) == 1 else range(c[0], c[1] + 1)
return r
from typing import Optional from typing import Optional, List
from flask import render_template, g, redirect, url_for, flash, request from flask import render_template, g, redirect, url_for, flash, request
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
import werkzeug.exceptions import werkzeug.exceptions
import wtforms import wtforms
from sqlalchemy import or_ from sqlalchemy import or_
import flask_sqlalchemy
from sqlalchemy.orm import joinedload, subqueryload from sqlalchemy.orm import joinedload, subqueryload
from wtforms import validators from wtforms import validators
...@@ -147,50 +148,149 @@ def org_users(): ...@@ -147,50 +148,149 @@ def org_users():
can_add=rr.have_right(Right.add_users), can_add=rr.have_right(Right.add_users),
) )
class OrgsFilterForm(PagerForm): class OrgsFilterForm(PagerForm):
# user # user
search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True}) search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True})
search_email = wtforms.TextField("E-mail") search_email = wtforms.TextField("E-mail")
# TODO: filtering by roles? 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_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_category = wtforms.StringField("Kategorie", validators=[validators.Optional()])
search_seq = wtforms.StringField("Kolo", validators=[validators.Optional()])
submit = wtforms.SubmitField("Filtrovat") submit = wtforms.SubmitField("Filtrovat")
show_role_filter = wtforms.SubmitField("Zobrazit filtrování dle rolí")
hide_role_filter = wtforms.SubmitField("Skrýt filtrování dle rolí")
# Výstupní hodnoty filtru, None při nepoužitém filtru, prázdná db hodnota při is_role_filter = wtforms.HiddenField(default="") # "" -> skrýt. "yes" -> zobrazit
# nepovedené filtraci (neexistující místo a podobně)
# 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_name: Optional[str] = None
f_search_email: 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(self): def validate_search_category(self, field):
self.f_search_name = f"%{self.search_name.data}%" if self.search_name.data else None self.f_search_category = field.data.split(",")
self.f_search_email = f"%{self.search_email.data}%" if self.search_email.data else None
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"
if self.hide_role_filter.data:
self.is_role_filter.data = ""
if self.is_role_filter.data:
del self.show_role_filter
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_place_level
del self.search_year
del self.search_category
del self.search_seq
@app.route('/org/org/') @app.route('/org/org/', methods=('GET', 'POST'))
def org_orgs(): def org_orgs():
sess = db.get_session() sess = db.get_session()
rr = g.gatekeeper.rights_generic() rr = g.gatekeeper.rights_generic()
q = sess.query(db.User).filter(or_(db.User.is_admin, db.User.is_org)).options( q = sess.query(db.User).filter(or_(db.User.is_admin, db.User.is_org)).options(
subqueryload(db.User.roles).joinedload(db.UserRole.place) subqueryload(db.User.roles).joinedload(db.UserRole.place)
) )
filter = OrgsFilterForm(request.args) filter = OrgsFilterForm()
filter.validate() filter.validate_on_submit()
filter.prepare_role_filter()
if filter.f_search_name: if filter.f_search_name:
q = q.filter(or_( q = q.filter(or_(
db.User.first_name.ilike(filter.f_search_name), db.User.first_name.ilike(filter.f_search_name),
db.User.last_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)) 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)]))
# 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:
qr = qr.filter(db.UserRole.place_id.in_(
sess.query(db.Place.place_id).filter(db.Place.level.in_(filter.f_search_place_level))
))
return qr
if filter.is_role_filter.data:
qr = sess.query(db.UserRole.user_id)
qr = query_filter_role(qr)
q = q.filter(db.User.user_id.in_(qr))
q = q.order_by(db.User.user_id)
(count, q) = filter.apply_limits(q, pagesize=50) (count, q) = filter.apply_limits(q, pagesize=50)
users = q.all() users = q.all()
marked_roles_id: Set[int] = set()
if filter.is_role_filter.data:
qmr = sess.query(db.UserRole.user_role_id).filter(db.UserRole.user_id.in_([i.user_id for i in users]))
qmr = query_filter_role(qmr)
marked_roles_id = set([i[0] for i in qmr.all()])
return render_template( return render_template(
'org_orgs.html', users=users, count=count, 'org_orgs.html', users=users, count=count,
filter=filter, filter=filter,
marked_roles_id=marked_roles_id,
can_edit=rr.have_right(Right.edit_orgs), can_edit=rr.have_right(Right.edit_orgs),
can_add=rr.have_right(Right.add_orgs), can_add=rr.have_right(Right.add_orgs),
) )
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
{% endif %} {% endif %}
<div class="form-frame"> <div class="form-frame">
<form action="" method="GET" role="form"> <form action="" method="POST" role="form">
{{ filter.csrf_token }}
{{ filter.is_role_filter }}
<div class="row"> <div class="row">
<div class='col-sm-2'><strong>Filtr organizátorů</strong></div> <div class='col-sm-2'><strong>Filtr organizátorů</strong></div>
<div class="col-sm-3"> <div class="col-sm-3">
...@@ -17,8 +19,35 @@ ...@@ -17,8 +19,35 @@
{{ wtf.form_field(filter.search_email, placeholder='Libovolná část e-mailu') }} {{ wtf.form_field(filter.search_email, placeholder='Libovolná část e-mailu') }}
</div> </div>
</div> </div>
{% if filter.is_role_filter.data %}
<div class="row">
<div class='col-sm-2'><strong>Filtr podle rolí</strong><p>Hledá pouze organizátory, kteří mají přidělenu nějakou roli.</p></div>
<div class="col-sm-2">
{{ wtf.form_field(filter.search_role, size=filter.search_role.choices|length, class="form-control no-scroll") }}
</div>
<div class="col-sm-2">
{{ wtf.form_field(filter.search_year, placeholder='Např. 65-67,70') }}
{{ wtf.form_field(filter.search_category, placeholder='Např. A,P,Z9') }}
</div>
<div class="col-sm-2">
{{ 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>
</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" ) }}
</div>
</div>
{% endif %}
<div class="btn-group"> <div class="btn-group">
{{ wtf.form_field(filter.submit, class='btn btn-primary') }} {{ wtf.form_field(filter.submit, class='btn btn-primary') }}
{% if filter.is_role_filter.data %}
{{ wtf.form_field(filter.hide_role_filter) }}
{% else %}
{{ wtf.form_field(filter.show_role_filter) }}
{% endif %}
{% if filter.offset.data > 0 %} {% if filter.offset.data > 0 %}
{{ wtf.form_field(filter.previous) }} {{ wtf.form_field(filter.previous) }}
{% else %} {% else %}
...@@ -52,10 +81,15 @@ ...@@ -52,10 +81,15 @@
<tr> <tr>
<td>{{ user.first_name }}</td><td>{{ user.last_name }}</td> <td>{{ user.first_name }}</td><td>{{ user.last_name }}</td>
<td>{{ user.email|mailto }}{{ user|user_flags }}</td> <td>{{ user.email|mailto }}{{ user|user_flags }}</td>
<td>{% if user.is_admin %}správce{% elif user.roles|count == 0 %}<i>žádná role</i>{% else %} <td>{% if user.is_admin %}správce{% elif user.roles|count == 0 %}<i>žádná role</i>{% endif %}
{% if user.roles|count > 0 %}
<ul> <ul>
{% for role in user.roles %} {% for role in user.roles %}
{% if role.user_role_id in marked_roles_id %}
<li><b>{{ role }}</b></li>
{% else %}
<li>{{ role }}</li> <li>{{ role }}</li>
{% endif %}
{%- endfor %} {%- endfor %}
</ul> </ul>
{% endif %}</td> {% endif %}</td>
......
...@@ -179,6 +179,10 @@ nav#main-menu a.active { ...@@ -179,6 +179,10 @@ nav#main-menu a.active {
border-radius: 4px 4px; border-radius: 4px 4px;
} }
select.no-scroll::-webkit-scrollbar {
display: none;
}
.checked_toggle input.toggle:checked ~ .checked_hide { .checked_toggle input.toggle:checked ~ .checked_hide {
display: none; display: none;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment