diff --git a/docs/reference/viewsets.md b/docs/reference/viewsets.md index 099bb270be..931bc340a2 100644 --- a/docs/reference/viewsets.md +++ b/docs/reference/viewsets.md @@ -83,6 +83,8 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit .. autoattribute:: list_display .. autoattribute:: admin_url_namespace .. autoattribute:: base_url_path + .. autoattribute:: chooser_admin_url_namespace + .. autoattribute:: chooser_base_url_path .. autoattribute:: filterset_class .. autoattribute:: index_view_class .. autoattribute:: add_view_class @@ -101,4 +103,6 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit .. autoattribute:: unlock_view_class .. automethod:: get_admin_url_namespace .. automethod:: get_admin_base_path + .. automethod:: get_chooser_admin_url_namespace + .. automethod:: get_chooser_admin_base_path ``` diff --git a/docs/topics/snippets.md b/docs/topics/snippets.md index 038485e73d..19c64eca64 100644 --- a/docs/topics/snippets.md +++ b/docs/topics/snippets.md @@ -571,6 +571,8 @@ You can define a {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.icon` at The {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.admin_url_namespace` attribute can be set to use a custom URL namespace for the URL patterns of the views. If unset, it defaults to `wagtailsnippets_{app_label}_{model_name}`. Meanwhile, setting {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.base_url_path` allows you to customise the base URL path relative to the Wagtail admin URL. If unset, it defaults to `snippets/app_label/model_name`. If you need further customisations, you can also override the {meth}`~wagtail.snippets.views.snippets.SnippetViewSet.get_admin_url_namespace` and {meth}`~wagtail.snippets.views.snippets.SnippetViewSet.get_admin_base_path` methods to override the namespace and base URL path, respectively. +Similar URL customisations are also possible for the snippet chooser views through {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.chooser_admin_url_namespace`, {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.chooser_base_url_path`, {meth}`~wagtail.snippets.views.snippets.SnippetViewSet.get_chooser_admin_url_namespace`, and {meth}`~wagtail.snippets.views.snippets.SnippetViewSet.get_chooser_admin_base_path`. + The {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.list_display` attribute can be set to specify the columns shown on the listing view. You can also add the ability to filter the listing view by defining a {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.filterset_class` attribute on a subclass of `SnippetViewSet`. For example: diff --git a/wagtail/snippets/tests/test_viewset.py b/wagtail/snippets/tests/test_viewset.py index 7a75a370ca..1e3183f410 100644 --- a/wagtail/snippets/tests/test_viewset.py +++ b/wagtail/snippets/tests/test_viewset.py @@ -139,9 +139,8 @@ class TestSnippetChooserPanelWithIcon(WagtailTestUtils, TestCase): self.assertNotIn("icon-snippet", field_html) def test_chooser_popup(self): - response = self.client.get( - reverse("wagtailsnippetchoosers_tests_fullfeaturedsnippet:choose") - ) + chooser_viewset = FullFeaturedSnippet.snippet_viewset.chooser_viewset + response = self.client.get(reverse(chooser_viewset.get_url_name("choose"))) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["header_icon"], "cog") self.assertContains(response, "icon icon-cog", count=1) @@ -175,12 +174,23 @@ class TestAdminURLs(WagtailTestUtils, TestCase): viewset.get_url_name("edit"), "wagtailsnippets_tests_advert:edit", ) + # Chooser namespace + self.assertEqual( + viewset.get_chooser_admin_url_namespace(), + "wagtailsnippetchoosers_tests_advert", + ) + # Get specific chooser URL name + self.assertEqual( + viewset.chooser_viewset.get_url_name("choose"), + "wagtailsnippetchoosers_tests_advert:choose", + ) def test_default_admin_base_path(self): snippet = Advert.objects.create(text="foo") viewset = snippet.snippet_viewset pk = quote(snippet.pk) expected_url = f"/admin/snippets/tests/advert/edit/{pk}/" + expected_choose_url = "/admin/snippets/choose/tests/advert/" # Accessed via the viewset self.assertEqual(viewset.get_admin_base_path(), "snippets/tests/advert") @@ -191,6 +201,16 @@ class TestAdminURLs(WagtailTestUtils, TestCase): # Ensure AdminURLFinder returns the correct URL url_finder = AdminURLFinder(self.user) self.assertEqual(url_finder.get_edit_url(snippet), expected_url) + # Chooser base path + self.assertEqual( + viewset.get_chooser_admin_base_path(), + "snippets/choose/tests/advert", + ) + # Get specific chooser URL + self.assertEqual( + reverse(viewset.chooser_viewset.get_url_name("choose")), + expected_choose_url, + ) def test_custom_url_namespace(self): snippet = FullFeaturedSnippet.objects.create(text="customised") @@ -201,12 +221,23 @@ class TestAdminURLs(WagtailTestUtils, TestCase): self.assertEqual(snippet.get_admin_url_namespace(), "some_namespace") # Get specific URL name self.assertEqual(viewset.get_url_name("edit"), "some_namespace:edit") + # Chooser namespace + self.assertEqual( + viewset.get_chooser_admin_url_namespace(), + "my_chooser_namespace", + ) + # Get specific chooser URL name + self.assertEqual( + viewset.chooser_viewset.get_url_name("choose"), + "my_chooser_namespace:choose", + ) def test_custom_admin_base_path(self): snippet = FullFeaturedSnippet.objects.create(text="customised") viewset = snippet.snippet_viewset pk = quote(snippet.pk) expected_url = f"/admin/deep/within/the/admin/edit/{pk}/" + expected_choose_url = "/admin/choose/wisely/" # Accessed via the viewset self.assertEqual(viewset.get_admin_base_path(), "deep/within/the/admin") # Accessed via the model @@ -216,3 +247,13 @@ class TestAdminURLs(WagtailTestUtils, TestCase): # Ensure AdminURLFinder returns the correct URL url_finder = AdminURLFinder(self.user) self.assertEqual(url_finder.get_edit_url(snippet), expected_url) + # Chooser base path + self.assertEqual( + viewset.get_chooser_admin_base_path(), + "choose/wisely", + ) + # Get specific chooser URL + self.assertEqual( + reverse(viewset.chooser_viewset.get_url_name("choose")), + expected_choose_url, + ) diff --git a/wagtail/snippets/views/snippets.py b/wagtail/snippets/views/snippets.py index 627e2cd4b5..ce0ed4c209 100644 --- a/wagtail/snippets/views/snippets.py +++ b/wagtail/snippets/views/snippets.py @@ -617,6 +617,14 @@ class SnippetViewSet(ViewSet): #: If left unset, ``snippets/{app_label}/{model_name}`` is used instead. base_url_path = None + #: The URL namespace to use for the chooser admin views. + #: If left unset, ``wagtailsnippetchoosers_{app_label}_{model_name}`` is used instead. + chooser_admin_url_namespace = None + + #: The base URL path to use for the chooser admin views. + #: If left unset, ``snippets/choose/{app_label}/{model_name}`` is used instead. + chooser_base_url_path = None + #: The view class to use for the index view; must be a subclass of ``wagtail.snippet.views.snippets.IndexView``. index_view_class = IndexView @@ -988,9 +996,9 @@ class SnippetViewSet(ViewSet): @property def chooser_viewset(self): return self.chooser_viewset_class( - f"wagtailsnippetchoosers_{self.app_label}_{self.model_name}", + self.get_chooser_admin_url_namespace(), model=self.model, - url_prefix=f"snippets/choose/{self.app_label}/{self.model_name}", + url_prefix=self.get_chooser_admin_base_path(), icon=self.icon, ) @@ -1009,6 +1017,21 @@ class SnippetViewSet(ViewSet): return self.base_url_path.strip().strip("/") return f"snippets/{self.app_label}/{self.model_name}" + def get_chooser_admin_url_namespace(self): + """Returns the URL namespace for the chooser admin URLs for this model.""" + if self.chooser_admin_url_namespace: + return self.chooser_admin_url_namespace + return f"wagtailsnippetchoosers_{self.app_label}_{self.model_name}" + + def get_chooser_admin_base_path(self): + """ + Returns the base path for the chooser admin URLs for this model. + The returned string must not begin or end with a slash. + """ + if self.chooser_base_url_path: + return self.chooser_base_url_path.strip().strip("/") + return f"snippets/choose/{self.app_label}/{self.model_name}" + @property def url_finder_class(self): return type( diff --git a/wagtail/snippets/widgets.py b/wagtail/snippets/widgets.py index 1e63065d42..9114853888 100644 --- a/wagtail/snippets/widgets.py +++ b/wagtail/snippets/widgets.py @@ -28,7 +28,7 @@ class AdminSnippetChooser(BaseChooser): def get_chooser_modal_url(self): try: return reverse( - f"wagtailsnippetchoosers_{self.model._meta.app_label}_{self.model._meta.model_name}:choose" + self.model.snippet_viewset.chooser_viewset.get_url_name("choose") ) except NoReverseMatch: # This most likely failed because the model is not registered as a snippet. diff --git a/wagtail/test/testapp/wagtail_hooks.py b/wagtail/test/testapp/wagtail_hooks.py index 41b214e784..65f46ae180 100644 --- a/wagtail/test/testapp/wagtail_hooks.py +++ b/wagtail/test/testapp/wagtail_hooks.py @@ -234,6 +234,8 @@ class FullFeaturedSnippetViewSet(SnippetViewSet): icon = "cog" admin_url_namespace = "some_namespace" base_url_path = "deep/within/the/admin" + chooser_admin_url_namespace = "my_chooser_namespace" + chooser_base_url_path = "choose/wisely" register_snippet(FullFeaturedSnippet, viewset=FullFeaturedSnippetViewSet)