diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5bcd4cef16..681a2baed8 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -18,6 +18,7 @@ Changelog * Add alt text validation rule in the accessibility checker (Albina Starykova) * Add a `deactivate()` method to `ProgressController` (Alex Morega) * Allow manually specifying credentials for CloudFront frontend cache backend (Jake Howard) + * Automatically register permissions for models registered with a `ModelViewSet` (Sage Abdullah) * Fix: Make `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` setting functional again (Rohit Sharma) * Fix: Enable `richtext` template tag to convert lazy translation values (Benjamin Bach) * Fix: Ensure permission labels on group permissions page are translated where available (Matt Westcott) diff --git a/docs/releases/6.2.md b/docs/releases/6.2.md index dcd276e62e..c7b0631d50 100644 --- a/docs/releases/6.2.md +++ b/docs/releases/6.2.md @@ -32,6 +32,7 @@ This feature was implemented by Albina Starykova, with support from the Wagtail * Implement a new design for locale labels in listings (Albina Starykova) * Add a `deactivate()` method to `ProgressController` (Alex Morega) * Allow manually specifying credentials for CloudFront frontend cache backend (Jake Howard) + * Automatically register permissions for models registered with a `ModelViewSet` (Sage Abdullah) ### Bug fixes @@ -110,11 +111,35 @@ WAGTAILFRONTENDCACHE = { } ``` -### Permissions for models in `ModelViewSet` are registered by default +### Changes to permissions registration for models with `ModelViewSet` and `SnippetViewSet` -Any models registered with a `ModelViewSet` will automatically have their permissions registered in the Groups administration area. Previously, you need to use the [`register_permissions`](register_permissions) hook to register them. +Models registered with a `ModelViewSet` will now automatically have their {class}`~django.contrib.auth.models.Permission` objects registered in the Groups administration area. Previously, you need to use the [`register_permissions`](register_permissions) hook to register them. -If you have a model registered with a `ModelViewSet` and you registered the model's permissions using the `register_permissions` hook, you can now safely remove the hook. If you wish to customize which permissions get registered for the model, you can override the {meth}`~wagtail.admin.viewsets.model.ModelViewSet.get_permissions_to_register` method. +If you have a model registered with a `ModelViewSet` and you registered the model's permissions using the `register_permissions` hook, you can now safely remove the hook. + +If the viewset has {attr}`~wagtail.admin.viewsets.model.ModelViewSet.inspect_view_enabled` set to `True`, all permissions for the model are registered. Otherwise, the "view" permission is excluded from the registration. + +To customize which permissions get registered for the model, you can override the {meth}`~wagtail.admin.viewsets.model.ModelViewSet.get_permissions_to_register` method. + +This behavior now applies to snippets as well. Previously, the "view" permission for snippets is always registered regardless of `inspect_view_enabled`. If you wish to register the "view" permission, you can enable the inspect view: + +```py +class FooViewSet(SnippetViewSet): + ... + inspect_view_enabled = True +``` + +Alternatively, if you wish to register the "view" permission without enabling the inspect view (i.e. the previous behavior), you can override `get_permissions_to_register` like the following: + +```py +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType + +class FooViewSet(SnippetViewSet): + def get_permissions_to_register(self): + content_type = ContentType.objects.get_for_model(self.model) + return Permission.objects.filter(content_type=content_type) +``` ## Upgrade considerations - deprecation of old functionality diff --git a/wagtail/admin/tests/viewsets/test_model_viewset.py b/wagtail/admin/tests/viewsets/test_model_viewset.py index 802fc13edd..f1c8df0de1 100644 --- a/wagtail/admin/tests/viewsets/test_model_viewset.py +++ b/wagtail/admin/tests/viewsets/test_model_viewset.py @@ -13,6 +13,7 @@ from django.utils.html import escape from django.utils.timezone import make_aware from openpyxl import load_workbook +from wagtail import hooks from wagtail.admin.admin_url_finder import AdminURLFinder from wagtail.log_actions import log from wagtail.models import ModelLogEntry @@ -1390,6 +1391,25 @@ class TestInspectView(WagtailTestUtils, TestCase): self.assertEqual(fields, expected_fields) self.assertEqual(values, expected_values) + def test_view_permission_registered(self): + content_type = ContentType.objects.get_for_model(FeatureCompleteToy) + qs = Permission.objects.none() + for fn in hooks.get_hooks("register_permissions"): + qs |= fn() + registered_user_permissions = qs.filter(content_type=content_type) + self.assertEqual( + set(registered_user_permissions.values_list("codename", flat=True)), + { + "add_featurecompletetoy", + "change_featurecompletetoy", + "delete_featurecompletetoy", + # The "view" permission should be registered if inspect view is enabled + "view_featurecompletetoy", + # Any custom permissions should be registered too + "can_set_release_date", + }, + ) + def test_disabled(self): # An alternate viewset for the same model without inspect_view_enabled = True with self.assertRaises(NoReverseMatch): diff --git a/wagtail/admin/viewsets/model.py b/wagtail/admin/viewsets/model.py index 86bfd454f3..4a1399df54 100644 --- a/wagtail/admin/viewsets/model.py +++ b/wagtail/admin/viewsets/model.py @@ -1,5 +1,6 @@ from warnings import warn +from django.contrib.auth import get_permission_codename from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured @@ -623,10 +624,18 @@ class ModelViewSet(ViewSet): """ Returns a queryset of :class:`~django.contrib.auth.models.Permission` objects to be registered with the :ref:`register_permissions` hook. By - default, it returns all permissions for the model. + default, it returns all permissions for the model if + :attr:`inspect_view_enabled` is set to ``True``. Otherwise, the "view" + permission is excluded. """ content_type = ContentType.objects.get_for_model(self.model) - return Permission.objects.filter(content_type=content_type) + permissions = Permission.objects.filter(content_type=content_type) + # Only register the "view" permission if the inspect view is enabled + if not self.inspect_view_enabled: + permissions = permissions.exclude( + codename=get_permission_codename("view", self.model_opts) + ) + return permissions def register_permissions(self): hooks.register("register_permissions", self.get_permissions_to_register) diff --git a/wagtail/locales/views.py b/wagtail/locales/views.py index ac3b06f269..9d6e455931 100644 --- a/wagtail/locales/views.py +++ b/wagtail/locales/views.py @@ -117,11 +117,3 @@ class LocaleViewSet(ModelViewSet): def get_form_class(self, for_update=False): return LocaleForm - - def get_permissions_to_register(self): - # Only register these permissions (and not others e.g. "view_locale") - return ( - super() - .get_permissions_to_register() - .filter(codename__in=["add_locale", "change_locale", "delete_locale"]) - ) diff --git a/wagtail/sites/views.py b/wagtail/sites/views.py index d6a16b752d..8ca1f6cdff 100644 --- a/wagtail/sites/views.py +++ b/wagtail/sites/views.py @@ -75,11 +75,3 @@ class SiteViewSet(ModelViewSet): def get_form_class(self, for_update=False): return SiteForm - - def get_permissions_to_register(self): - # Only register these permissions (and not others e.g. "view_site") - return ( - super() - .get_permissions_to_register() - .filter(codename__in=["add_site", "change_site", "delete_site"]) - ) diff --git a/wagtail/users/tests/test_admin_views.py b/wagtail/users/tests/test_admin_views.py index 0ae0322109..1a41b79d3d 100644 --- a/wagtail/users/tests/test_admin_views.py +++ b/wagtail/users/tests/test_admin_views.py @@ -2397,7 +2397,7 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase): def test_is_custom_permission_checked(self): # Add a permission from the 'custom permission' column to the user's group - custom_permission = Permission.objects.get(codename="view_fancysnippet") + custom_permission = Permission.objects.get(codename="view_fullfeaturedsnippet") self.test_group.permissions.add(custom_permission) response = self.get() diff --git a/wagtail/users/views/groups.py b/wagtail/users/views/groups.py index 2249f90b7e..6a7cf12372 100644 --- a/wagtail/users/views/groups.py +++ b/wagtail/users/views/groups.py @@ -190,11 +190,3 @@ class GroupViewSet(ModelViewSet): return super().get_urlpatterns() + [ re_path(r"(\d+)/users/$", self.users_view, name="users"), ] - - def get_permissions_to_register(self): - # Only register these permissions (and not others e.g. "view_group") - return ( - super() - .get_permissions_to_register() - .filter(codename__in=["add_group", "change_group", "delete_group"]) - ) diff --git a/wagtail/users/views/users.py b/wagtail/users/views/users.py index 75d6a94b04..ed9e97e4c0 100644 --- a/wagtail/users/views/users.py +++ b/wagtail/users/views/users.py @@ -3,7 +3,6 @@ from warnings import warn import django_filters from django.conf import settings from django.contrib.auth import ( - get_permission_codename, get_user_model, update_session_auth_hash, ) @@ -399,19 +398,3 @@ class UserViewSet(ModelViewSet): if for_update: return get_user_edit_form() return get_user_creation_form() - - def get_permissions_to_register(self): - # Only register these permissions (and not others e.g. "view_user") - # and use the model's meta to get the permission codenames in case the - # AUTH_USER_MODEL setting has been changed - return ( - super() - .get_permissions_to_register() - .filter( - codename__in=[ - get_permission_codename("add", self.model._meta), - get_permission_codename("change", self.model._meta), - get_permission_codename("delete", self.model._meta), - ] - ) - )