Every list of items is paginated server-side.

This commit is contained in:
Éloi Rivard 2023-02-25 18:11:19 +01:00
parent 480b085db3
commit e5d968d4f5
23 changed files with 631 additions and 134 deletions

View file

@ -38,6 +38,7 @@ from .forms import LoginForm
from .forms import PasswordForm
from .forms import PasswordResetForm
from .forms import profile_form
from .forms import TableForm
from .mails import send_invitation_mail
from .mails import send_password_initialization_mail
from .mails import send_password_reset_mail
@ -179,11 +180,18 @@ def firstlogin(uid):
return render_template("firstlogin.html", form=form, uid=uid)
@bp.route("/users")
@bp.route("/users", methods=["GET", "POST"])
@permissions_needed("manage_users")
def users(user):
users = User.query()
return render_template("users.html", users=users, menuitem="users")
table_form = TableForm(User, formdata=request.form)
if request.form and not table_form.validate():
abort(404)
return render_template(
"users.html",
menuitem="users",
table_form=table_form,
)
@dataclass

View file

@ -1,3 +1,5 @@
import math
import wtforms.form
from flask import current_app
from flask import g
@ -41,6 +43,25 @@ def existing_login(form, field):
)
class TableForm(FlaskForm):
def __init__(self, cls=None, page_size=25, filter=None, **kwargs):
filter = filter or {}
super().__init__(**kwargs)
self.items = cls.query(**filter)
self.page_size = page_size
self.nb_items = len(self.items)
self.page_max = max(1, math.ceil(self.nb_items / self.page_size))
first_item = (self.page.data - 1) * self.page_size
last_item = min((self.page.data) * self.page_size, self.nb_items)
self.items_slice = self.items[first_item:last_item]
page = wtforms.IntegerField(default=1)
def validate_page(self, field):
if field.data < 1 or field.data > self.page_max:
raise wtforms.validators.ValidationError(_("The page number is not valid"))
class LoginForm(FlaskForm):
login = wtforms.StringField(
_("Login"),

View file

@ -10,16 +10,21 @@ from flask_themer import render_template
from .flaskutils import permissions_needed
from .forms import CreateGroupForm
from .forms import EditGroupForm
from .forms import TableForm
from .models import Group
from .models import User
bp = Blueprint("groups", __name__, url_prefix="/groups")
@bp.route("/")
@bp.route("/", methods=["GET", "POST"])
@permissions_needed("manage_groups")
def groups(user):
groups = Group.query()
return render_template("groups.html", groups=groups, menuitem="groups")
table_form = TableForm(Group, formdata=request.form)
if request.form and request.form.get("page") and not table_form.validate():
abort(404)
return render_template("groups.html", menuitem="groups", table_form=table_form)
@bp.route("/add", methods=("GET", "POST"))
@ -53,7 +58,11 @@ def group(user, groupname):
if not group:
abort(404)
if request.method == "GET" or request.form.get("action") == "edit":
if (
request.method == "GET"
or request.form.get("action") == "edit"
or request.form.get("page")
):
return edit_group(group)
if request.form.get("action") == "delete":
@ -63,6 +72,10 @@ def group(user, groupname):
def edit_group(group):
table_form = TableForm(User, filter={"memberOf": group}, formdata=request.form)
if request.form and request.form.get("page") and not table_form.validate():
abort(404)
form = EditGroupForm(
request.form or None,
data={
@ -71,7 +84,7 @@ def edit_group(group):
},
)
if request.form:
if request.form and not request.form.get("page"):
if form.validate():
group.description = [form.description.data]
group.save()
@ -84,7 +97,10 @@ def edit_group(group):
flash(_("Group edition failed."), "error")
return render_template(
"group.html", form=form, edited_group=group, members=group.get_members()
"group.html",
form=form,
edited_group=group,
table_form=table_form,
)

View file

@ -1,20 +1,26 @@
from canaille.flaskutils import permissions_needed
from canaille.forms import TableForm
from canaille.oidc.models import AuthorizationCode
from flask import abort
from flask import Blueprint
from flask import request
from flask_themer import render_template
bp = Blueprint("authorizations", __name__, url_prefix="/admin/authorization")
@bp.route("/")
@bp.route("/", methods=["GET", "POST"])
@permissions_needed("manage_oidc")
def index(user):
authorizations = AuthorizationCode.query()
table_form = TableForm(AuthorizationCode, formdata=request.form)
if request.form and request.form.get("page") and not table_form.validate():
abort(404)
return render_template(
"oidc/admin/authorization_list.html",
authorizations=authorizations,
menuitem="admin",
table_form=table_form,
)

View file

@ -1,6 +1,7 @@
import datetime
from canaille.flaskutils import permissions_needed
from canaille.forms import TableForm
from canaille.oidc.forms import ClientAdd
from canaille.oidc.models import Client
from flask import abort
@ -17,12 +18,15 @@ from werkzeug.security import gen_salt
bp = Blueprint("clients", __name__, url_prefix="/admin/client")
@bp.route("/")
@bp.route("/", methods=["GET", "POST"])
@permissions_needed("manage_oidc")
def index(user):
clients = Client.query()
table_form = TableForm(Client, formdata=request.form)
if request.form and request.form.get("page") and not table_form.validate():
abort(404)
return render_template(
"oidc/admin/client_list.html", clients=clients, menuitem="admin"
"oidc/admin/client_list.html", menuitem="admin", table_form=table_form
)

View file

@ -1,6 +1,7 @@
import datetime
from canaille.flaskutils import permissions_needed
from canaille.forms import TableForm
from canaille.models import User
from canaille.oidc.models import Client
from canaille.oidc.models import Token
@ -8,20 +9,23 @@ from flask import abort
from flask import Blueprint
from flask import flash
from flask import redirect
from flask import request
from flask import url_for
from flask_babel import gettext as _
from flask_themer import render_template
bp = Blueprint("tokens", __name__, url_prefix="/admin/token")
@bp.route("/")
@bp.route("/", methods=["GET", "POST"])
@permissions_needed("manage_oidc")
def index(user):
tokens = Token.query()
table_form = TableForm(Token, formdata=request.form)
if request.form and request.form.get("page") and not table_form.validate():
abort(404)
return render_template(
"oidc/admin/token_list.html", tokens=tokens, menuitem="admin"
"oidc/admin/token_list.html", menuitem="admin", table_form=table_form
)

View file

@ -119,7 +119,7 @@ i.massive.massive.massive.portrait.icon, i.massive.massive.massive.portrait.icon
.ui.link.menu .item:hover, .ui.menu .dropdown.item:hover, .ui.menu .link.item:hover, .ui.menu a.item:hover, .ui.menu a.item.active:hover {
color: rgba(255,255,255,.95);
}
.ui.attached.header, .ui.toggle.checkbox label::before, .ui.table > thead > tr > th, table tbody tr td .ui.label, .ui.card, .ui.cards > .card, .ui.message, .ui.info.message, .ui.success.message {
.ui.attached.header, .ui.toggle.checkbox label::before, .ui.table > thead > tr > th, .ui.table > tfoot > tr > th, table tbody tr td .ui.label, .ui.card, .ui.cards > .card, .ui.message, .ui.info.message, .ui.success.message {
background-color: #282828;
color: rgba(255,255,255,.87) !important;
}

View file

@ -87,9 +87,7 @@
</div>
</h2>
{% with users=members %}
{% include "partial/users.html" %}
{% endwith %}
{% include "partial/users.html" %}
</div>
{% endif %}

View file

@ -11,29 +11,6 @@
{% trans %}Groups{% endtrans %}
</div>
</h2>
<table class="ui table">
<thead>
<tr>
<th></th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Description{% endtrans %}</th>
<th>{% trans %}Number of members{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td>
<a href="{{ url_for('groups.group', groupname=group.name) }}">
<i class="users circular black inverted icon"></i>
</a>
</td>
<td><a href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a></td>
<td>{% if group.description %}{{ group.description[0] }}{% endif %}</td>
<td>{{ group.member|len }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "partial/groups.html" %}
</div>
{% endblock %}

View file

@ -0,0 +1,56 @@
{% macro pagination(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 }}
<div class="ui right floated stackable buttons">
<span class="icon disabled ui button">
{% trans %}Page{% endtrans %}
</span>
{% if form.page.data > 1 %}
<button name="page" type="submit" class="icon ui button" value="{{ form.page.data - 1 }}">
<i class="left chevron icon"></i>
</button>
{% else %}
<span class="icon disabled ui button">
<i class="left chevron icon"></i>
</span>
{% endif %}
{% if form.page.data > 1 %}
<button name="page" type="submit" class="ui button" value="1">
1
</button>
{% endif %}
{% if form.page.data > 2 %}
<span class="disabled ui button">
</span>
{% endif %}
<span class="ui button active">
{{ form.page.data }}
</span>
{% if form.page.data < form.page_max - 1 %}
<span class="disabled ui button">
</span>
{% endif %}
{% if form.page.data < form.page_max %}
<button name="page" type="submit" class="ui button" value="{{ form.page_max }}">
{{ form.page_max }}
</button>
{% endif %}
{% if form.page.data < form.page_max %}
<button name="page" type="submit" class="icon ui button" value="{{ form.page.data + 1 }}">
<i class="right chevron icon"></i>
</button>
{% else %}
<span class="icon disabled ui button">
<i class="right chevron icon"></i>
</span>
{% endif %}
</div>
<div class="ui left floated">
<span class="disabled ui button">
{{ _("%(nb_items)s items", nb_items=form.nb_items) }}
</span>
</div>
</form>
{% endmacro %}

View file

@ -5,26 +5,5 @@
{% endblock %}
{% block content %}
<table class="ui table">
<thead>
<tr>
<th>{% trans %}Code{% endtrans %}</th>
<th>{% trans %}Client{% endtrans %}</th>
<th>{% trans %}Subject{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for authorization in authorizations %}
<tr>
<td><a href="{{ url_for('oidc.authorizations.view', authorization_id=authorization.authorization_code_id) }}">{{ authorization.authorization_code_id }}</a></td>
<td><a href="{{ url_for('oidc.clients.edit', client_id=authorization.client_id) }}">{{ authorization.client_id }}</a></td>
<td>{{ authorization.subject }}</td>
<td>{{ authorization.issue_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "partial/oidc/admin/authorization_list.html" %}
{% endblock %}

View file

@ -10,33 +10,5 @@
<a class="ui primary button" href="{{ url_for('oidc.clients.add') }}">{% trans %}Add client{% endtrans %}</a>
</div>
<table class="ui table">
<thead>
<tr>
<th></th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}URL{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for client in clients %}
<tr>
<td>
<a href="{{ url_for('oidc.clients.edit', client_id=client.client_id) }}">
{% if client.logo_uri %}
<img class="ui avatar image" src="{{ client.logo_uri }}" alt="Client logo">
{% else %}
<i class="plug circular inverted black icon"></i>
{% endif %}
</a>
</td>
<td><a href="{{ url_for('oidc.clients.edit', client_id=client.client_id) }}">{{ client.client_name }}</a></td>
<td><a href="{{ client.client_uri }}">{{ client.client_uri }}</a></td>
<td>{% if client.client_id_issued_at %}{{ client.client_id_issued_at }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "partial/oidc/admin/client_list.html" %}
{% endblock %}

View file

@ -5,38 +5,5 @@
{% endblock %}
{% block content %}
<table class="ui table">
<thead>
<tr>
<th>{% trans %}Token{% endtrans %}</th>
<th>{% trans %}Client{% endtrans %}</th>
<th>{% trans %}Subject{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for token in tokens %}
<tr>
<td>
<a href="{{ url_for('oidc.tokens.view', token_id=token.token_id) }}">
{{ token.token_id }}
</a>
</td>
<td>
<a href="{{ url_for('oidc.clients.edit', client_id=token.client.client_id) }}">
{{ token.client.client_name }}
</a>
</td>
<td>
<a href="{{ url_for("account.profile_edition", username=token.subject.uid[0]) }}">
{{ token.subject.uid[0] }}
</a>
</td>
<td>{{ token.issue_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "partial/oidc/admin/token_list.html" %}
{% endblock %}

View file

@ -0,0 +1,52 @@
{% import "macro/table.html" as table %}
<table class="ui table groups">
<thead>
<tr>
<th></th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Description{% endtrans %}</th>
<th>{% trans %}Number of members{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for group in table_form.items_slice %}
<tr>
<td>
<a href="{{ url_for('groups.group', groupname=group.name) }}">
<i class="users circular black inverted icon"></i>
</a>
</td>
<td><a href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a></td>
<td>{% if group.description %}{{ group.description[0] }}{% endif %}</td>
<td>{{ group.member|len }}</td>
</tr>
{% else %}
<tr>
<td colspan="4">
<div class="ui icon message">
<i class="exclamation icon"></i>
<div class="content">
{% if request.headers.get("Hx-Request") %}
<div class="header">
{% trans %}No item matches your request{% endtrans %}
</div>
<p>{% trans %}Maybe try with different criterias?{% endtrans %}</p>
{% else %}
<div class="header">
{% trans %}There is nothing here{% endtrans %}
</div>
{% endif %}
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="4">
{{ table.pagination(table_form) }}
</th>
</tr>
</tfoot>
</table>

View file

@ -0,0 +1,48 @@
{% import "macro/table.html" as table %}
<table class="ui table codes">
<thead>
<tr>
<th>{% trans %}Code{% endtrans %}</th>
<th>{% trans %}Client{% endtrans %}</th>
<th>{% trans %}Subject{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for authorization in table_form.items_slice %}
<tr>
<td><a href="{{ url_for('oidc.authorizations.view', authorization_id=authorization.authorization_code_id) }}">{{ authorization.authorization_code_id }}</a></td>
<td><a href="{{ url_for('oidc.clients.edit', client_id=authorization.client_id) }}">{{ authorization.client_id }}</a></td>
<td>{{ authorization.subject }}</td>
<td>{{ authorization.issue_date }}</td>
</tr>
{% else %}
<tr>
<td colspan="4">
<div class="ui icon message">
<i class="exclamation icon"></i>
<div class="content">
{% if request.headers.get("Hx-Request") %}
<div class="header">
{% trans %}No item matches your request{% endtrans %}
</div>
<p>{% trans %}Maybe try with different criterias?{% endtrans %}</p>
{% else %}
<div class="header">
{% trans %}There is nothing here{% endtrans %}
</div>
{% endif %}
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="4">
{{ table.pagination(table_form) }}
</th>
</tr>
</tfoot>
</table>

View file

@ -0,0 +1,56 @@
{% import "macro/table.html" as table %}
<table class="ui table clients">
<thead>
<tr>
<th></th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}URL{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for client in table_form.items_slice %}
<tr>
<td>
<a href="{{ url_for('oidc.clients.edit', client_id=client.client_id) }}">
{% if client.logo_uri %}
<img class="ui avatar image" src="{{ client.logo_uri }}" alt="Client logo">
{% else %}
<i class="plug circular inverted black icon"></i>
{% endif %}
</a>
</td>
<td><a href="{{ url_for('oidc.clients.edit', client_id=client.client_id) }}">{{ client.client_name }}</a></td>
<td><a href="{{ client.client_uri }}">{{ client.client_uri }}</a></td>
<td>{% if client.client_id_issued_at %}{{ client.client_id_issued_at }}{% endif %}</td>
</tr>
{% else %}
<tr>
<td colspan="4">
<div class="ui icon message">
<i class="exclamation icon"></i>
<div class="content">
{% if request.headers.get("Hx-Request") %}
<div class="header">
{% trans %}No item matches your request{% endtrans %}
</div>
<p>{% trans %}Maybe try with different criterias?{% endtrans %}</p>
{% else %}
<div class="header">
{% trans %}There is nothing here{% endtrans %}
</div>
{% endif %}
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="4">
{{ table.pagination(table_form) }}
</th>
</tr>
</tfoot>
</table>

View file

@ -0,0 +1,60 @@
{% import "macro/table.html" as table %}
<table class="ui table tokens">
<thead>
<tr>
<th>{% trans %}Token{% endtrans %}</th>
<th>{% trans %}Client{% endtrans %}</th>
<th>{% trans %}Subject{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for token in table_form.items_slice %}
<tr>
<td>
<a href="{{ url_for('oidc.tokens.view', token_id=token.token_id) }}">
{{ token.token_id }}
</a>
</td>
<td>
<a href="{{ url_for('oidc.clients.edit', client_id=token.client.client_id) }}">
{{ token.client.client_name }}
</a>
</td>
<td>
<a href="{{ url_for("account.profile_edition", username=token.subject.uid[0]) }}">
{{ token.subject.uid[0] }}
</a>
</td>
<td>{{ token.issue_date }}</td>
</tr>
{% else %}
<tr>
<td colspan="4">
<div class="ui icon message">
<i class="exclamation icon"></i>
<div class="content">
{% if request.headers.get("Hx-Request") %}
<div class="header">
{% trans %}No item matches your request{% endtrans %}
</div>
<p>{% trans %}Maybe try with different criterias?{% endtrans %}</p>
{% else %}
<div class="header">
{% trans %}There is nothing here{% endtrans %}
</div>
{% endif %}
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="4">
{{ table.pagination(table_form) }}
</th>
</tr>
</tfoot>
</table>

View file

@ -1,4 +1,5 @@
<table class="ui table">
{% import "macro/table.html" as table %}
<table class="ui table users">
<thead>
<tr>
{% if user.can_read("jpegPhoto") %}
@ -19,7 +20,7 @@
</tr>
</thead>
<tbody>
{% for watched_user in users %}
{% for watched_user in table_form.items_slice %}
<tr>
{% if user.can_read("jpegPhoto") %}
<td>
@ -73,4 +74,11 @@
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="5">
{{ table.pagination(table_form) }}
</th>
</tr>
</tfoot>
</table>

View file

@ -4,6 +4,7 @@ from canaille.oidc.models import AuthorizationCode
from canaille.oidc.models import Client
from canaille.oidc.models import Consent
from canaille.oidc.models import Token
from werkzeug.security import gen_salt
def test_no_logged_no_access(testclient):
@ -23,6 +24,48 @@ def test_client_list(testclient, client, logged_admin):
assert client.client_name in res.text
def test_client_list_pagination(testclient, logged_admin, client, other_client):
res = testclient.get("/admin/client")
assert "2 items" in res
clients = []
for _ in range(25):
client = Client(client_id=gen_salt(48), client_name=gen_salt(48))
client.save()
clients.append(client)
res = testclient.get("/admin/client")
assert "27 items" in res, res.text
client_name = res.pyquery(
".clients tbody tr:nth-of-type(1) td:nth-of-type(2) a"
).text()
assert client_name
form = res.forms["pagination"]
res = form.submit(name="page", value="2")
assert (
client_name not in res.pyquery(".clients tbody tr td:nth-of-type(2) a").text()
)
for client in clients:
client.delete()
res = testclient.get("/admin/client")
assert "2 items" in res
def test_client_list_bad_pages(testclient, logged_admin):
res = testclient.get("/admin/client")
form = res.forms["pagination"]
testclient.post(
"/admin/client", {"csrf_token": form["csrf_token"], "page": "2"}, status=404
)
res = testclient.get("/admin/client")
form = res.forms["pagination"]
testclient.post(
"/admin/client", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404
)
def test_client_add(testclient, logged_admin):
res = testclient.get("/admin/client/add")
data = {

View file

@ -1,3 +1,7 @@
from canaille.oidc.models import AuthorizationCode
from werkzeug.security import gen_salt
def test_no_logged_no_access(testclient):
testclient.get("/admin/authorization", status=403)
@ -11,6 +15,55 @@ def test_authorizaton_list(testclient, authorization, logged_admin):
assert authorization.authorization_code_id in res.text
def test_authorization_list_pagination(testclient, logged_admin, client):
res = testclient.get("/admin/authorization")
assert "0 items" in res
authorizations = []
for _ in range(26):
code = AuthorizationCode(
authorization_code_id=gen_salt(48), client=client, subject=client
)
code.save()
authorizations.append(code)
res = testclient.get("/admin/authorization")
assert "26 items" in res, res.text
authorization_code_id = res.pyquery(
".codes tbody tr:nth-of-type(1) td:nth-of-type(1) a"
).text()
assert authorization_code_id
form = res.forms["pagination"]
res = form.submit(name="page", value="2")
assert (
authorization_code_id
not in res.pyquery(".codes tbody tr td:nth-of-type(1) a").text()
)
for authorization in authorizations:
authorization.delete()
res = testclient.get("/admin/authorization")
assert "0 items" in res
def test_authorization_list_bad_pages(testclient, logged_admin):
res = testclient.get("/admin/authorization")
form = res.forms["pagination"]
testclient.post(
"/admin/authorization",
{"csrf_token": form["csrf_token"], "page": "2"},
status=404,
)
res = testclient.get("/admin/authorization")
form = res.forms["pagination"]
testclient.post(
"/admin/authorization",
{"csrf_token": form["csrf_token"], "page": "-1"},
status=404,
)
def test_authorizaton_view(testclient, authorization, logged_admin):
res = testclient.get("/admin/authorization/" + authorization.authorization_code_id)
for attr in authorization.may() + authorization.must():

View file

@ -1,3 +1,9 @@
import datetime
from canaille.oidc.models import Token
from werkzeug.security import gen_salt
def test_no_logged_no_access(testclient):
testclient.get("/admin/token", status=403)
@ -11,6 +17,57 @@ def test_token_list(testclient, token, logged_admin):
assert token.token_id in res.text
def test_token_list_pagination(testclient, logged_admin, client):
res = testclient.get("/admin/token")
assert "0 items" in res
tokens = []
for _ in range(26):
token = Token(
token_id=gen_salt(48),
access_token="my-valid-token",
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,
)
token.save()
tokens.append(token)
res = testclient.get("/admin/token")
assert "26 items" in res, res.text
token_id = res.pyquery(".tokens tbody tr td:nth-of-type(1) a").text()
assert token_id
form = res.forms["pagination"]
res = form.submit(name="page", value="2")
assert (
token_id
not in res.pyquery(".tokens tbody tr:nth-of-type(1) td:nth-of-type(1) a").text()
)
for token in tokens:
token.delete()
res = testclient.get("/admin/token")
assert "0 items" in res
def test_token_list_bad_pages(testclient, logged_admin):
res = testclient.get("/admin/token")
form = res.forms["pagination"]
testclient.post(
"/admin/token", {"csrf_token": form["csrf_token"], "page": "2"}, status=404
)
res = testclient.get("/admin/token")
form = res.forms["pagination"]
testclient.post(
"/admin/token", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404
)
def test_token_view(testclient, token, logged_admin):
res = testclient.get("/admin/token/" + token.token_id)
assert token.access_token in res.text

View file

@ -1,5 +1,6 @@
from canaille.models import Group
from canaille.models import User
from canaille.populate import fake_groups
from canaille.populate import fake_users
@ -7,6 +8,43 @@ def test_no_group(app, slapd_connection):
assert Group.query() == []
def test_group_list_pagination(testclient, logged_admin, foo_group):
res = testclient.get("/groups")
assert "1 items" in res
groups = fake_groups(25)
res = testclient.get("/groups")
assert "26 items" in res, res.text
group_name = res.pyquery(
".groups tbody tr:nth-of-type(1) td:nth-of-type(2) a"
).text()
assert group_name
form = res.forms["pagination"]
res = form.submit(name="page", value="2")
assert group_name not in res.pyquery(".groups tbody tr td:nth-of-type(2) a").text()
for group in groups:
group.delete()
res = testclient.get("/groups")
assert "1 items" in res
def test_group_list_bad_pages(testclient, logged_admin):
res = testclient.get("/groups")
form = res.forms["pagination"]
testclient.post(
"/groups", {"csrf_token": form["csrf_token"], "page": "2"}, status=404
)
res = testclient.get("/groups")
form = res.forms["pagination"]
testclient.post(
"/groups", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404
)
def test_set_groups(app, user, foo_group, bar_group):
foo_dns = {m.dn for m in foo_group.get_members()}
assert user.dn in foo_dns
@ -151,3 +189,41 @@ def test_edition_failed(testclient, logged_moderator, foo_group):
assert "Group edition failed." in res
foo_group = Group.get(foo_group.dn)
assert foo_group.name == "foo"
def test_user_list_pagination(testclient, logged_admin, foo_group):
res = testclient.get("/groups/foo")
assert "1 items" in res
users = fake_users(25)
for user in users:
foo_group.add_member(user)
foo_group.save()
res = testclient.get("/groups/foo")
assert "26 items" in res, res.text
user_name = res.pyquery(".users tbody tr:nth-of-type(1) td:nth-of-type(2) a").text()
assert user_name
form = res.forms["pagination"]
res = form.submit(name="page", value="2")
assert user_name not in res.pyquery(".users tr td:nth-of-type(2) a").text()
for user in users:
user.delete()
res = testclient.get("/groups/foo")
assert "1 items" in res
def test_user_list_bad_pages(testclient, logged_admin, foo_group):
res = testclient.get("/groups/foo")
form = res.forms["pagination"]
testclient.post(
"/groups/foo", {"csrf_token": form["csrf_token"], "page": "2"}, status=404
)
res = testclient.get("/groups/foo")
form = res.forms["pagination"]
testclient.post(
"/groups/foo", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404
)

View file

@ -1,9 +1,45 @@
from unittest import mock
from canaille.models import User
from canaille.populate import fake_users
from webtest import Upload
def test_user_list_pagination(testclient, logged_admin):
res = testclient.get("/users")
assert "1 items" in res
users = fake_users(25)
res = testclient.get("/users")
assert "26 items" in res, res.text
user_name = res.pyquery(".users tbody tr:nth-of-type(1) td:nth-of-type(2) a").text()
assert user_name
form = res.forms["pagination"]
res = form.submit(name="page", value="2")
assert user_name not in res.pyquery(".users tbody tr td:nth-of-type(2) a").text()
for user in users:
user.delete()
res = testclient.get("/users")
assert "1 items" in res
def test_user_list_bad_pages(testclient, logged_admin):
res = testclient.get("/users")
form = res.forms["pagination"]
testclient.post(
"/users", {"csrf_token": form["csrf_token"], "page": "2"}, status=404
)
res = testclient.get("/users")
form = res.forms["pagination"]
testclient.post(
"/users", {"csrf_token": form["csrf_token"], "page": "-1"}, status=404
)
def test_edition_permission(
testclient,
slapd_server,