Table search implementation

This commit is contained in:
Éloi Rivard 2023-03-07 18:29:18 +01:00
parent fbf449edd6
commit 46a346a0d0
17 changed files with 173 additions and 6 deletions

View file

@ -47,7 +47,11 @@ class TableForm(FlaskForm):
def __init__(self, cls=None, page_size=25, filter=None, **kwargs): def __init__(self, cls=None, page_size=25, filter=None, **kwargs):
filter = filter or {} filter = filter or {}
super().__init__(**kwargs) super().__init__(**kwargs)
self.items = cls.query(**filter) if self.query.data:
self.items = cls.fuzzy(self.query.data, **filter)
else:
self.items = cls.query(**filter)
self.page_size = page_size self.page_size = page_size
self.nb_items = len(self.items) self.nb_items = len(self.items)
self.page_max = max(1, math.ceil(self.nb_items / self.page_size)) self.page_max = max(1, math.ceil(self.nb_items / self.page_size))
@ -56,6 +60,7 @@ class TableForm(FlaskForm):
self.items_slice = self.items[first_item:last_item] self.items_slice = self.items[first_item:last_item]
page = wtforms.IntegerField(default=1) page = wtforms.IntegerField(default=1)
query = wtforms.StringField(default="")
def validate_page(self, field): def validate_page(self, field):
if field.data < 1 or field.data > self.page_max: if field.data < 1 or field.data > self.page_max:

View file

@ -1,4 +1,5 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import "macro/table.html" as table %}
{% block content %} {% block content %}
<div class="ui segment"> <div class="ui segment">
@ -11,6 +12,9 @@
{% trans %}Groups{% endtrans %} {% trans %}Groups{% endtrans %}
</div> </div>
</h2> </h2>
<div class="ui attached segment">
{{ table.search(table_form) }}
</div>
{% include "partial/groups.html" %} {% include "partial/groups.html" %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -1,6 +1,20 @@
{% macro search(form) %}
<form id="search" action="{{ url_for(request.url_rule.endpoint, **request.view_args) }}" method="POST" class="ui form">
{{ form.hidden_tag() if form.hidden_tag }}
<input type="hidden" name="page" value="{{ form.page.data }}">
<div class="ui fluid action input">
<input type="search" placeholder="{{ _("Search") }}" name="{{ form.query.name }}" value="{{ form.query.data }}">
<button type="submit" class="ui icon button" title="{{ _("Search") }}">
<i class="search icon"></i>
</button>
</div>
</form>
{% endmacro %}
{% macro pagination(form) %} {% macro pagination(form) %}
<form id="pagination" action="{{ url_for(request.url_rule.endpoint, **request.view_args) }}" method="POST" class="ui form"> <form id="pagination" action="{{ url_for(request.url_rule.endpoint, **request.view_args) }}" method="POST" class="ui form">
{{ form.hidden_tag() if form.hidden_tag }} {{ form.hidden_tag() if form.hidden_tag }}
<input type="hidden" name="query" value="{{ form.query.data }}">
<div class="ui right floated stackable buttons"> <div class="ui right floated stackable buttons">
<span class="icon disabled ui button"> <span class="icon disabled ui button">
{% trans %}Page{% endtrans %} {% trans %}Page{% endtrans %}

View file

@ -1,9 +1,13 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import "macro/table.html" as table %}
{% block script %} {% block script %}
<script src="/static/js/users.js"></script> <script src="/static/js/users.js"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="ui attached segment">
{{ table.search(table_form) }}
</div>
{% include "partial/oidc/admin/authorization_list.html" %} {% include "partial/oidc/admin/authorization_list.html" %}
{% endblock %} {% endblock %}

View file

@ -1,4 +1,5 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import "macro/table.html" as table %}
{% block script %} {% block script %}
<script src="/static/js/users.js"></script> <script src="/static/js/users.js"></script>
@ -10,5 +11,8 @@
<a class="ui primary button" href="{{ url_for('oidc.clients.add') }}">{% trans %}Add client{% endtrans %}</a> <a class="ui primary button" href="{{ url_for('oidc.clients.add') }}">{% trans %}Add client{% endtrans %}</a>
</div> </div>
<div class="ui attached segment">
{{ table.search(table_form) }}
</div>
{% include "partial/oidc/admin/client_list.html" %} {% include "partial/oidc/admin/client_list.html" %}
{% endblock %} {% endblock %}

View file

@ -1,9 +1,13 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import "macro/table.html" as table %}
{% block script %} {% block script %}
<script src="/static/js/users.js"></script> <script src="/static/js/users.js"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="ui attached segment">
{{ table.search(table_form) }}
</div>
{% include "partial/oidc/admin/token_list.html" %} {% include "partial/oidc/admin/token_list.html" %}
{% endblock %} {% endblock %}

View file

@ -1,5 +1,5 @@
{% import "macro/table.html" as table %} {% import "macro/table.html" as table %}
<table class="ui table groups"> <table class="ui attached table groups">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>

View file

@ -1,5 +1,5 @@
{% import "macro/table.html" as table %} {% import "macro/table.html" as table %}
<table class="ui table codes"> <table class="ui attached table codes">
<thead> <thead>
<tr> <tr>
<th>{% trans %}Code{% endtrans %}</th> <th>{% trans %}Code{% endtrans %}</th>

View file

@ -1,5 +1,5 @@
{% import "macro/table.html" as table %} {% import "macro/table.html" as table %}
<table class="ui table clients"> <table class="ui attached table clients">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>

View file

@ -1,5 +1,5 @@
{% import "macro/table.html" as table %} {% import "macro/table.html" as table %}
<table class="ui table tokens"> <table class="ui attached table tokens">
<thead> <thead>
<tr> <tr>
<th>{% trans %}Token{% endtrans %}</th> <th>{% trans %}Token{% endtrans %}</th>

View file

@ -1,5 +1,5 @@
{% import "macro/table.html" as table %} {% import "macro/table.html" as table %}
<table class="ui table users"> <table class="ui attached table users">
<thead> <thead>
<tr> <tr>
{% if user.can_read("jpegPhoto") %} {% if user.can_read("jpegPhoto") %}

View file

@ -1,4 +1,5 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import "macro/table.html" as table %}
{% block script %} {% block script %}
<script src="/static/js/users.js"></script> <script src="/static/js/users.js"></script>
@ -13,5 +14,8 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="ui attached segment">
{{ table.search(table_form) }}
</div>
{% include "partial/users.html" %} {% include "partial/users.html" %}
{% endblock %} {% endblock %}

View file

@ -66,6 +66,21 @@ def test_client_list_bad_pages(testclient, logged_admin):
) )
def test_client_list_search(testclient, logged_admin, client, other_client):
res = testclient.get("/admin/client")
assert "2 items" in res
assert client.client_name in res
assert other_client.client_name in res
form = res.forms["search"]
form["query"] = "other"
res = form.submit()
assert "1 items" in res
assert other_client.client_name in res
assert client.client_name not in res
def test_client_add(testclient, logged_admin): def test_client_add(testclient, logged_admin):
res = testclient.get("/admin/client/add") res = testclient.get("/admin/client/add")
data = { data = {

View file

@ -64,6 +64,29 @@ def test_authorization_list_bad_pages(testclient, logged_admin):
) )
def test_authorization_list_search(testclient, logged_admin, client):
id1 = gen_salt(48)
auth1 = AuthorizationCode(authorization_code_id=id1, client=client, subject=client)
auth1.save()
id2 = gen_salt(48)
auth2 = AuthorizationCode(authorization_code_id=id2, client=client, subject=client)
auth2.save()
res = testclient.get("/admin/authorization")
assert "2 items" in res
assert id1 in res
assert id2 in res
form = res.forms["search"]
form["query"] = id1
res = form.submit()
assert "1 items" in res
assert id1 in res
assert id2 not in res
def test_authorizaton_view(testclient, authorization, logged_admin): def test_authorizaton_view(testclient, authorization, logged_admin):
res = testclient.get("/admin/authorization/" + authorization.authorization_code_id) res = testclient.get("/admin/authorization/" + authorization.authorization_code_id)
for attr in authorization.may() + authorization.must(): for attr in authorization.may() + authorization.must():

View file

@ -68,6 +68,46 @@ def test_token_list_bad_pages(testclient, logged_admin):
) )
def test_token_list_search(testclient, logged_admin, client):
token1 = Token(
token_id=gen_salt(48),
access_token="this-token-is-ok",
client=client,
subject=logged_admin,
type=None,
refresh_token=gen_salt(48),
scope="openid profile",
issue_date=(datetime.datetime.now().replace(microsecond=0)),
lifetime=3600,
)
token1.save()
token2 = Token(
token_id=gen_salt(48),
access_token="this-token-is-valid",
client=client,
subject=logged_admin,
type=None,
refresh_token=gen_salt(48),
scope="openid profile",
issue_date=(datetime.datetime.now().replace(microsecond=0)),
lifetime=3600,
)
token2.save()
res = testclient.get("/admin/token")
assert "2 items" in res
assert token1.token_id in res
assert token2.token_id in res
form = res.forms["search"]
form["query"] = "valid"
res = form.submit()
assert "1 items" in res
assert token2.token_id in res
assert token1.token_id not in res
def test_token_view(testclient, token, logged_admin): def test_token_view(testclient, token, logged_admin):
res = testclient.get("/admin/token/" + token.token_id) res = testclient.get("/admin/token/" + token.token_id)
assert token.access_token in res.text assert token.access_token in res.text

View file

@ -45,6 +45,21 @@ def test_group_list_bad_pages(testclient, logged_admin):
) )
def test_group_list_search(testclient, logged_admin, foo_group, bar_group):
res = testclient.get("/groups")
assert "2 items" in res
assert foo_group.name in res
assert bar_group.name in res
form = res.forms["search"]
form["query"] = "oo"
res = form.submit()
assert "1 items" in res, res.text
assert foo_group.name in res
assert bar_group.name not in res
def test_set_groups(app, user, foo_group, bar_group): def test_set_groups(app, user, foo_group, bar_group):
foo_dns = {m.dn for m in foo_group.get_members()} foo_dns = {m.dn for m in foo_group.get_members()}
assert user.dn in foo_dns assert user.dn in foo_dns
@ -227,3 +242,23 @@ def test_user_list_bad_pages(testclient, logged_admin, foo_group):
testclient.post( testclient.post(
"/groups/foo", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404 "/groups/foo", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404
) )
def test_user_list_search(testclient, logged_admin, foo_group, user, moderator):
foo_group.add_member(logged_admin)
foo_group.add_member(moderator)
foo_group.save()
res = testclient.get("/groups/foo")
assert "3 items" in res
assert user.name in res
assert moderator.name in res
form = res.forms["search"]
form["query"] = "ohn"
res = form.submit()
assert "1 items" in res
assert user.name in res
assert logged_admin.name not in res
assert moderator.name not in res

View file

@ -40,6 +40,21 @@ def test_user_list_bad_pages(testclient, logged_admin):
) )
def test_user_list_search(testclient, logged_admin, user, moderator):
res = testclient.get("/users")
assert "3 items" in res
assert moderator.name in res
assert user.name in res
form = res.forms["search"]
form["query"] = "Jack"
res = form.submit()
assert "1 items" in res, res.text
assert moderator.name in res
assert user.name not in res
def test_edition_permission( def test_edition_permission(
testclient, testclient,
slapd_server, slapd_server,