Pass the request user object instead of context to register_user_listing_buttons hook

pull/11823/head
Sage Abdullah 2024-04-05 03:53:28 +07:00
rodzic cf1c0683e8
commit d0fa6abae9
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: EB1A33CC51CC0217
5 zmienionych plików z 221 dodań i 14 usunięć

Wyświetl plik

@ -407,18 +407,29 @@ Return a QuerySet of `Permission` objects to be shown in the Groups administrati
### `register_user_listing_buttons`
Add buttons to the user list. This example will add a simple button to the listing:
Add buttons to the user list.
This hook takes two parameters:
- `user`: The user object to generate the button for
- `request_user`: The currently logged-in user
This example will add a simple button to the listing if the currently logged-in user is a superuser:
```python
from wagtail.users.widgets import UserListingButton
@hooks.register("register_user_listing_buttons")
def user_listing_external_profile(context, user):
yield UserListingButton(
"Show profile",
f"/goes/to/a/url/{user.pk}",
priority=30,
)
def user_listing_external_profile(user, request_user):
if request_user.is_superuser:
yield UserListingButton(
"Show profile",
f"/goes/to/a/url/{user.pk}",
priority=30,
)
```
```{versionchanged} 6.2
The hook function was updated to accept a `request_user` argument instead of `context`.
```
(filter_form_submissions_for_user)=

Wyświetl plik

@ -108,8 +108,17 @@ As part of the Universal Listings project, the `SubmissionsListView` for listing
- The `ordering` attribute has been renamed to `default_ordering`.
- The template used to render the view has been significantly refactored to use the new universal listings UI.
### `register_user_listing_buttons` hook signature changed
The function signature for the [`register_user_listing_buttons`](register_user_listing_buttons) hook was updated to accept a `request_user` argument instead of `context`. If you use this hook, make sure to update your function signature to match the new one. The old signature with `context` will continue to work for now, but the context only contains the request object. Support for the old signature will be removed in a future release.
## Upgrade considerations - changes to undocumented internals
### Deprecation of `user_listing_buttons` template tag
The undocumented `user_listing_buttons` template tag has been deprecated and will be removed in a future release.
### Deprecation of `window.chooserUrls` within Draftail choosers
The undocumented usage of the JavaScript `window.chooserUrls` within Draftail choosers will be removed in a future release.

Wyświetl plik

@ -1,5 +1,5 @@
import itertools
from collections import defaultdict
from warnings import warn
from django import template
from django.contrib.auth import get_permission_codename
@ -9,7 +9,9 @@ from django.utils.text import camel_case_to_spaces
from wagtail import hooks
from wagtail.admin.models import Admin
from wagtail.coreutils import accepts_kwarg
from wagtail.users.permission_order import CONTENT_TYPE_ORDER
from wagtail.utils.deprecation import RemovedInWagtail70Warning
register = template.Library()
@ -185,8 +187,24 @@ def format_permissions(permission_bound_field):
@register.inclusion_tag("wagtailadmin/shared/buttons.html", takes_context=True)
def user_listing_buttons(context, user):
button_hooks = hooks.get_hooks("register_user_listing_buttons")
buttons = sorted(
itertools.chain.from_iterable(hook(context, user) for hook in button_hooks)
warn(
"`user_listing_buttons` template tag is deprecated.",
category=RemovedInWagtail70Warning,
)
return {"user": user, "buttons": buttons}
buttons = []
for hook in hooks.get_hooks("register_user_listing_buttons"):
if accepts_kwarg(hook, "request_user"):
buttons.extend(hook(user=user, request_user=context.get("request").user))
else:
# old-style hook that accepts a context argument instead of request_user
buttons.extend(hook(context, user))
warn(
"`register_user_listing_buttons` hook functions should accept a "
"`request_user` argument instead of `context` - "
f"{hook.__module__}.{hook.__name__} needs to be updated",
category=RemovedInWagtail70Warning,
)
return {"user": user, "buttons": sorted(buttons)}

Wyświetl plik

@ -10,6 +10,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models import Q
from django.http import HttpRequest, HttpResponse
from django.template import RequestContext, Template
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils.text import capfirst
@ -18,7 +19,9 @@ from wagtail import hooks
from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.admin.models import Admin
from wagtail.admin.staticfiles import versioned_static
from wagtail.admin.widgets.button import ButtonWithDropdown
from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
from wagtail.coreutils import get_dummy_request
from wagtail.log_actions import log
from wagtail.models import (
Collection,
@ -34,6 +37,7 @@ from wagtail.users.permission_order import register as register_permission_order
from wagtail.users.views.groups import GroupViewSet
from wagtail.users.views.users import get_user_creation_form, get_user_edit_form
from wagtail.users.wagtail_hooks import get_group_viewset_cls
from wagtail.users.widgets import UserListingButton
from wagtail.utils.deprecation import RemovedInWagtail70Warning
delete_user_perm_codename = f"delete_{AUTH_USER_MODEL_NAME.lower()}"
@ -197,7 +201,7 @@ class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
first_name="First Name",
last_name="Last Name",
)
self.login()
self.user = self.login()
def get(self, params={}):
return self.client.get(reverse("wagtailusers_users:index"), params)
@ -273,6 +277,60 @@ class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
with self.assertNumQueries(num_queries):
self.get()
def test_buttons_hook(self):
def hook(user, request_user):
self.assertEqual(request_user, self.user)
yield UserListingButton(
"Show profile",
f"/goes/to/a/url/{user.pk}",
priority=30,
)
yield ButtonWithDropdown(
label="Moar pls!",
buttons=[UserListingButton("Alrighty", "/cheers", priority=10)],
)
with self.register_hook("register_user_listing_buttons", hook):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/shared/buttons.html")
soup = self.get_soup(response.content)
row = soup.select_one(f"tbody tr:has([data-object-id='{self.test_user.pk}'])")
self.assertIsNotNone(row)
profile_url = f"/goes/to/a/url/{self.test_user.pk}"
actions = row.select_one("td ul.actions")
top_level_custom_button = actions.select_one(f"li > a[href='{profile_url}']")
self.assertIsNone(top_level_custom_button)
custom_button = actions.select_one(
f"li [data-controller='w-dropdown'] a[href='{profile_url}']"
)
self.assertIsNotNone(custom_button)
self.assertEqual(
custom_button.text.strip(),
"Show profile",
)
nested_dropdown = actions.select_one(
"li [data-controller='w-dropdown'] [data-controller='w-dropdown']"
)
self.assertIsNone(nested_dropdown)
dropdown_buttons = actions.select("li > [data-controller='w-dropdown']")
# Default "More" button and the custom "Moar pls!" button
self.assertEqual(len(dropdown_buttons), 2)
custom_dropdown = None
for button in dropdown_buttons:
if "Moar pls!" in button.text.strip():
custom_dropdown = button
self.assertIsNotNone(custom_dropdown)
self.assertEqual(custom_dropdown.select_one("button").text.strip(), "Moar pls!")
# Should contain the custom button inside the custom dropdown
custom_button = custom_dropdown.find("a", attrs={"href": "/cheers"})
self.assertIsNotNone(custom_button)
self.assertEqual(custom_button.text.strip(), "Alrighty")
class TestUserIndexResultsView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
def setUp(self):
@ -2637,3 +2695,100 @@ class TestAuthorisationDeleteView(WagtailTestUtils, TestCase):
self.assertRedirects(response, reverse("wagtailusers_users:index"))
user = get_user_model().objects.filter(email="test_user@email.com")
self.assertFalse(user.exists())
class TestTemplateTags(WagtailTestUtils, TestCase):
@classmethod
def setUpTestData(cls):
cls.user = cls.create_superuser("admin")
cls.request = get_dummy_request()
cls.request.user = cls.user
cls.test_user = cls.create_user(
username="testuser",
email="testuser@email.com",
password="password",
)
def test_user_listing_buttons(self):
template = """
{% load wagtailusers_tags %}
{% for user in users %}
<ul class="actions">
{% user_listing_buttons user %}
</ul>
{% endfor %}
"""
def hook(user, request_user):
self.assertEqual(user, self.test_user)
self.assertEqual(request_user, self.user)
yield UserListingButton(
"Show profile",
f"/goes/to/a/url/{user.pk}",
priority=30,
)
with self.register_hook("register_user_listing_buttons", hook):
with self.assertWarnsMessage(
RemovedInWagtail70Warning,
"`user_listing_buttons` template tag is deprecated.",
):
html = Template(template).render(
RequestContext(self.request, {"users": [self.test_user]})
)
soup = self.get_soup(html)
profile_url = f"/goes/to/a/url/{self.test_user.pk}"
top_level_custom_button = soup.select_one(f"li > a[href='{profile_url}']")
self.assertIsNotNone(top_level_custom_button)
self.assertEqual(
top_level_custom_button.text.strip(),
"Show profile",
)
def test_user_listing_buttons_with_deprecated_hook(self):
template = """
{% load wagtailusers_tags %}
{% for user in users %}
<ul class="actions">
{% user_listing_buttons user %}
</ul>
{% endfor %}
"""
def deprecated_hook(context, user):
self.assertEqual(user, self.test_user)
self.assertEqual(context.request.user, self.user)
yield UserListingButton(
"Show profile",
f"/goes/to/a/url/{user.pk}",
priority=30,
)
with self.register_hook("register_user_listing_buttons", deprecated_hook):
with self.assertWarns(RemovedInWagtail70Warning) as warning_manager:
html = Template(template).render(
RequestContext(self.request, {"users": [self.test_user]})
)
self.assertEqual(
[str(w.message) for w in warning_manager.warnings],
[
# Deprecation of the template tag
"`user_listing_buttons` template tag is deprecated.",
# Deprecation of the hook signature
"`register_user_listing_buttons` hook functions should accept a "
"`request_user` argument instead of `context` - "
"wagtail.users.tests.test_admin_views.deprecated_hook needs to be updated",
],
)
soup = self.get_soup(html)
profile_url = f"/goes/to/a/url/{self.test_user.pk}"
top_level_custom_button = soup.select_one(f"li > a[href='{profile_url}']")
self.assertIsNotNone(top_level_custom_button)
self.assertEqual(
top_level_custom_button.text.strip(),
"Show profile",
)

Wyświetl plik

@ -1,3 +1,5 @@
from warnings import warn
from django.conf import settings
from django.contrib.auth import get_user_model, update_session_auth_hash
from django.contrib.auth.models import Group
@ -26,9 +28,11 @@ from wagtail.admin.widgets.button import (
ButtonWithDropdown,
)
from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
from wagtail.coreutils import accepts_kwarg
from wagtail.permission_policies import ModelPermissionPolicy
from wagtail.users.forms import UserCreationForm, UserEditForm
from wagtail.users.utils import user_can_delete_user
from wagtail.utils.deprecation import RemovedInWagtail70Warning
from wagtail.utils.loading import get_custom_form
User = get_user_model()
@ -172,7 +176,17 @@ class Index(IndexView):
list_buttons = []
for hook in hooks.get_hooks("register_user_listing_buttons"):
hook_buttons = hook(RequestContext(self.request), instance)
if accepts_kwarg(hook, "request_user"):
hook_buttons = hook(user=instance, request_user=self.request.user)
else:
# old-style hook that accepts a context argument instead of request_user
hook_buttons = hook(RequestContext(self.request), instance)
warn(
"`register_user_listing_buttons` hook functions should accept a `request_user` argument instead of `context` -"
f" {hook.__module__}.{hook.__name__} needs to be updated",
category=RemovedInWagtail70Warning,
)
for button in hook_buttons:
if isinstance(button, BaseDropdownMenuButton):
# If the button is a dropdown menu, add it to the top-level