Don't duplicate snippets in "Snippets" menu

This change alters the behavior of the sidebar "Snippets" menu such that
the snippet index view doesn't include snippet models that have been
configured with their own menu item.

Currently, the "Snippets" menu doesn't appear if all snippet models have
their own menu items; however, if some models do and some don't, the
snippets index view lists all of them, causing some duplication in the
UI which could be confusing for editors.

This commit changes this behavior so that "Snippets" is only used to
edit those snippet models that aren't editable elsewhere.

A new setting (WAGTAILSNIPPETS_MENU_SHOW_ALL) has been added to always
show the snippets menu and all snippets models. This setting will allow
for continuation of the current behavior for those users that desire it.

Closes issue 11340.
pull/13031/head
Andy Chosak 2023-12-29 13:51:23 -05:00 zatwierdzone przez Sage Abdullah
rodzic 0506449cbb
commit b376a7b65d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: EB1A33CC51CC0217
8 zmienionych plików z 84 dodań i 14 usunięć

Wyświetl plik

@ -918,3 +918,19 @@ WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH = True
This determines whether publishing a page with an ongoing workflow will cancel the workflow (if true) or leave the workflow unaffected (false).
Disabling this could be useful if your site has long, multi-step workflows, and you want to be able to publish urgent page updates while the workflow continues to provide less urgent feedback.
## Snippets
(wagtailsnippets_menu_show_all)=
### `WAGTAILSNIPPETS_MENU_SHOW_ALL`
```python
WAGTAILSNIPPETS_MENU_SHOW_ALL = False
```
The sidebar "Snippets" menu item is only shown if any snippet models exist
[without their own menu items](wagtailsnippets_menu_item)
and by default its view only contains those models.
This setting can be set to `True` to always show the "Snippets" menu item
and to have its view include all snippet models for which the user has permission to add, view, or change.

Wyświetl plik

@ -181,6 +181,14 @@ This option is enabled as standard for the `title` field of page models. It is a
In previous releases, the `save()` method on page models called the `full_clean` method to apply [model-level validation rules](inv:django#validating-objects), regardless of whether the page was in a draft or live state, unless this was explicitly disabled by passing `clean=False`. As of this release, saving a page in a draft state (`live=False`) will only perform the minimum validation necessary to ensure data integrity: the title must be non-empty, and the slug must be unique within the parent page. Saving a page with `live=True` will apply full validation as before. If you have user code that creates draft pages and requires them to be validated, you must now call `full_clean` explicitly.
### "Snippets" menu now only includes snippet models without menu items
The "Snippets" sidebar menu item appears if there are snippet models [without their own menu items](wagtailsnippets_menu_item).
Previously, the "Snippets" menu item pointed to a snippets index view that listed all snippet models whether they'd been configured with their own menu items or not.
This behaviour has been changed and the snippets index view will now only include snippet models that haven't been configured that way.
The new [](wagtailsnippets_menu_show_all) setting can be used to always show a top-level "Snippets" menu item in the sidebar pointing to an index view that includes all snippet models.
## Upgrade considerations - deprecation of old functionality
### `TAG_LIMIT` and `TAG_SPACES_ALLOWED` settings renamed to `WAGTAIL_TAG_LIMIT` and `WAGTAIL_TAG_SPACES_ALLOWED`

Wyświetl plik

@ -108,6 +108,8 @@ The inspect view is disabled by default, as it's not often useful for most model
Template customizations work the same way as for `ModelViewSet`, except that the {attr}`~.ModelViewSet.template_prefix` defaults to `wagtailsnippets/snippets/`. Refer to [the template customizations for `ModelViewSet`](modelviewset_templates) for more details.
(wagtailsnippets_menu_item)=
## Menu item
By default, registering a snippet model will add a "Snippets" menu item to the sidebar menu. However, you can configure a snippet model to have its own top-level menu item in the sidebar menu by setting {attr}`~.ViewSet.add_to_admin_menu` to `True`. Refer to [the menu customizations for `ModelViewSet`](modelviewset_menu) for more details.
@ -160,6 +162,8 @@ class MarketingViewSetGroup(SnippetViewSetGroup):
register_snippet(MarketingViewSetGroup)
```
By default, the sidebar "Snippets" menu item will only show snippet models that haven't been configured with their own menu items.
If all snippet models have their own menu items, the "Snippets" menu item will not be shown.
This behaviour can be changed using the [](wagtailsnippets_menu_show_all) setting.
Various additional attributes are available to customize the viewset - see {class}`~SnippetViewSet`.

Wyświetl plik

@ -19,14 +19,16 @@ def user_can_edit_snippet_type(user, model):
return False
def user_can_access_snippets(user):
def user_can_access_snippets(user, models=None):
"""
true if user has 'add', 'change', 'delete', or 'view' permission
on any model registered as a snippet type
on any model registered as a snippet type - or if a `models` list
is passed, any of those models
"""
snippet_models = get_snippet_models()
if models is None:
models = get_snippet_models()
for model in snippet_models:
for model in models:
if model.snippet_viewset.permission_policy.user_has_any_permission(
user, {"add", "change", "delete", "view"}
):

Wyświetl plik

@ -14,7 +14,7 @@ from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpRequest, HttpResponse
from django.test import RequestFactory, TestCase, TransactionTestCase
from django.test import RequestFactory, SimpleTestCase, TestCase, TransactionTestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.timezone import make_aware, now
@ -38,6 +38,7 @@ from wagtail.snippets.action_menu import (
)
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.snippets.models import SNIPPET_MODELS, register_snippet
from wagtail.snippets.views.snippets import get_snippet_models_for_index_view
from wagtail.snippets.widgets import (
AdminSnippetChooser,
SnippetChooserAdapter,
@ -80,6 +81,22 @@ from wagtail.test.utils.timestamps import submittable_timestamp
from wagtail.utils.timestamps import render_timestamp
class TestGetSnippetModelsForIndexView(SimpleTestCase):
def test_default_lists_all_snippets_without_menu_items(self):
self.assertEqual(
get_snippet_models_for_index_view(),
[
model
for model in SNIPPET_MODELS
if not model.snippet_viewset.get_menu_item_is_registered()
],
)
@override_settings(WAGTAILSNIPPETS_MENU_SHOW_ALL=True)
def test_setting_allows_listing_of_all_snippet_models(self):
self.assertEqual(get_snippet_models_for_index_view(), SNIPPET_MODELS)
class TestSnippetIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()

Wyświetl plik

@ -1204,7 +1204,7 @@ class TestMenuItemRegistration(BaseSnippetViewSetTests):
self.assertEqual(item.url, reverse("wagtailsnippets:index"))
# Clear cached property
del item._all_have_menu_items
del item._snippets_in_index_view
with mock.patch(
"wagtail.snippets.views.snippets.SnippetViewSet.get_menu_item_is_registered"
@ -1214,6 +1214,19 @@ class TestMenuItemRegistration(BaseSnippetViewSetTests):
snippets = [item for item in menu_items if item.name == "snippets"]
self.assertEqual(len(snippets), 0)
def test_snippets_menu_item_hidden_when_user_lacks_permissions_for_snippets(self):
self.user.is_superuser = False
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
)
self.user.save()
menu_items = admin_menu.render_component(self.request)
snippets = [item for item in menu_items if item.name == "snippets"]
self.assertEqual(len(snippets), 0)
class TestCustomFormClass(BaseSnippetViewSetTests):
model = DraftStateModel

Wyświetl plik

@ -1,4 +1,5 @@
from django.apps import apps
from django.conf import settings
from django.contrib.admin.utils import quote
from django.core import checks
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
@ -68,6 +69,19 @@ def get_snippet_model_from_url_params(app_name, model_name):
# == Views ==
def get_snippet_models_for_index_view():
models = get_snippet_models()
if getattr(settings, "WAGTAILSNIPPETS_MENU_SHOW_ALL", False):
return models
return [
model
for model in models
if not model.snippet_viewset.get_menu_item_is_registered()
]
class ModelIndexView(generic.BaseListingView):
page_title = gettext_lazy("Snippets")
header_icon = "snippet"
@ -86,7 +100,7 @@ class ModelIndexView(generic.BaseListingView):
"model": model,
"url": url,
}
for model in get_snippet_models()
for model in get_snippet_models_for_index_view()
if (url := self.get_list_url(model))
]

Wyświetl plik

@ -5,7 +5,6 @@ from django.utils.translation import gettext_lazy as _
from wagtail import hooks
from wagtail.admin.menu import MenuItem
from wagtail.snippets.bulk_actions.delete import DeleteBulkAction
from wagtail.snippets.models import get_snippet_models
from wagtail.snippets.permissions import user_can_access_snippets
from wagtail.snippets.views import snippets as snippet_views
@ -26,14 +25,11 @@ def register_admin_urls():
class SnippetsMenuItem(MenuItem):
@cached_property
def _all_have_menu_items(self):
return all(
model.snippet_viewset.get_menu_item_is_registered()
for model in get_snippet_models()
)
def _snippets_in_index_view(self):
return snippet_views.get_snippet_models_for_index_view()
def is_shown(self, request):
return not self._all_have_menu_items and user_can_access_snippets(request.user)
return user_can_access_snippets(request.user, self._snippets_in_index_view)
@hooks.register("register_admin_menu_item")