Add docs and tests for customising snippet icons

pull/10255/head
Daniel Kirkham 2022-12-17 22:21:58 +11:00 zatwierdzone przez Matt Westcott
rodzic c2d4f89692
commit 198388bf55
8 zmienionych plików z 212 dodań i 7 usunięć

Wyświetl plik

@ -79,6 +79,7 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit
```{eval-rst}
.. autoclass:: wagtail.snippets.views.snippets.SnippetViewSet
.. autoattribute:: icon
.. autoattribute:: list_display
.. autoattribute:: filterset_class
.. autoattribute:: index_view_class

Wyświetl plik

@ -567,7 +567,11 @@ class MemberFilterSet(WagtailFilterSet):
fields = ["shirt_size"]
```
You can define a {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.list_display` attribute 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:
You can define a {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.icon` attribute to specify the icon that is used across the admin for this snippet type. The `icon` needs to be [registered in the Wagtail icon library](../../advanced_topics/icons). If `icon` is not set, the default `"snippet"` icon is used.
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:
```python
# views.py
@ -578,6 +582,7 @@ from myapp.models import MemberFilterSet
class MemberViewSet(SnippetViewSet):
icon = "user"
list_display = ["name", "shirt_size", "get_shirt_size_display", UpdatedAtColumn()]
filterset_class = MemberFilterSet
```

Wyświetl plik

@ -4,13 +4,13 @@ from django.test import TestCase
from django.urls import reverse
from wagtail.snippets.bulk_actions.delete import DeleteBulkAction
from wagtail.test.snippets.models import StandardSnippet
from wagtail.test.testapp.models import FullFeaturedSnippet
from wagtail.test.utils import WagtailTestUtils
class TestSnippetDeleteView(WagtailTestUtils, TestCase):
def setUp(self):
self.snippet_model = StandardSnippet
self.snippet_model = FullFeaturedSnippet
# create a set of test snippets
self.test_snippets = [
@ -41,6 +41,9 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
self.assertTemplateUsed(
response, "wagtailsnippets/bulk_actions/confirm_bulk_delete.html"
)
self.assertTemplateUsed(response, "wagtailadmin/shared/header.html")
self.assertEqual(response.context["header_icon"], "cog")
self.assertContains(response, "icon icon-cog", count=1)
def test_bulk_delete(self):
response = self.client.post(self.url)
@ -66,7 +69,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to delete these standard snippets</p>",
"<p>You don't have permission to delete these full-featured snippets</p>",
html,
)

Wyświetl plik

@ -111,6 +111,7 @@ class TestSnippetListView(WagtailTestUtils, TestCase):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/type_index.html")
self.assertEqual(response.context["header_icon"], "snippet")
def get_with_limited_permissions(self):
self.user.is_superuser = False
@ -3783,6 +3784,7 @@ class TestSnippetChooserPanel(WagtailTestUtils, TestCase):
self.assertIn(self.advert_text, field_html)
self.assertIn("Choose advert", field_html)
self.assertIn("Choose another advert", field_html)
self.assertIn("icon icon-snippet icon", field_html)
def test_render_as_empty_field(self):
test_snippet = SnippetChooserModel()
@ -4683,6 +4685,7 @@ class TestAddOnlyPermissions(WagtailTestUtils, TestCase):
response = self.client.get(reverse("wagtailsnippets_tests_advert:add"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/create.html")
self.assertEqual(response.context["header_icon"], "snippet")
def test_get_edit(self):
response = self.client.get(
@ -4746,6 +4749,7 @@ class TestEditOnlyPermissions(WagtailTestUtils, TestCase):
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/edit.html")
self.assertEqual(response.context["header_icon"], "snippet")
def test_get_delete(self):
response = self.client.get(
@ -4807,6 +4811,7 @@ class TestDeleteOnlyPermissions(WagtailTestUtils, TestCase):
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/confirm_delete.html")
self.assertEqual(response.context["header_icon"], "snippet")
class TestSnippetEditHandlers(WagtailTestUtils, TestCase):
@ -5276,6 +5281,8 @@ class TestSnippetChooseWithCustomPrimaryKey(WagtailTestUtils, TestCase):
def test_simple(self):
response = self.get()
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
self.assertEqual(response.context["header_icon"], "snippet")
self.assertEqual(response.context["icon"], "snippet")
def test_ordering(self):
"""

Wyświetl plik

@ -0,0 +1,155 @@
from django.contrib.admin.utils import quote
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.urls import reverse
from wagtail.admin.panels import get_edit_handler
from wagtail.coreutils import get_dummy_request
from wagtail.models import Workflow, WorkflowContentType
from wagtail.test.testapp.models import Advert, FullFeaturedSnippet, SnippetChooserModel
from wagtail.test.utils import WagtailTestUtils
class TestCustomIcon(WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()
self.object = FullFeaturedSnippet.objects.create(
text="test snippet with custom icon"
)
self.revision_1 = self.object.save_revision()
self.revision_1.publish()
self.object.text = "test snippet with custom icon (updated)"
self.revision_2 = self.object.save_revision()
def get_url(self, url_name, args=()):
app_label = self.object._meta.app_label
model_name = self.object._meta.model_name
view_name = f"wagtailsnippets_{app_label}_{model_name}:{url_name}"
return reverse(view_name, args=args)
def test_get_views(self):
pk = quote(self.object.pk)
views = [
("list", []),
("add", []),
("edit", [pk]),
("delete", [pk]),
("usage", [pk]),
("unpublish", [pk]),
("workflow_history", [pk]),
("revisions_revert", [pk, self.revision_1.id]),
("revisions_compare", [pk, self.revision_1.id, self.revision_2.id]),
("revisions_unschedule", [pk, self.revision_2.id]),
]
for view_name, args in views:
with self.subTest(view_name=view_name):
response = self.client.get(self.get_url(view_name, args))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["header_icon"], "cog")
self.assertContains(response, "icon icon-cog", count=1)
# TODO: Make the list view use the shared header template
if view_name != "list":
self.assertTemplateUsed(response, "wagtailadmin/shared/header.html")
def test_get_history(self):
response = self.client.get(self.get_url("history", [quote(self.object.pk)]))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/shared/header.html")
# History view icon is not configurable for consistency with pages
self.assertEqual(response.context["header_icon"], "history")
self.assertContains(response, "icon icon-history")
self.assertNotContains(response, "icon icon-cog")
def test_get_workflow_history_detail(self):
# Assign default workflow to the snippet model
self.content_type = ContentType.objects.get_for_model(type(self.object))
self.workflow = Workflow.objects.first()
WorkflowContentType.objects.create(
content_type=self.content_type,
workflow=self.workflow,
)
self.object.text = "Edited!"
self.object.save_revision()
workflow_state = self.workflow.start(self.object, self.user)
response = self.client.get(
self.get_url(
"workflow_history_detail", [quote(self.object.pk), workflow_state.id]
)
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/shared/header.html")
# The icon is not displayed in the header,
# but it is displayed in the main content
self.assertEqual(response.context["header_icon"], "list-ul")
self.assertContains(response, "icon icon-list-ul")
self.assertContains(response, "icon icon-cog")
class TestSnippetChooserPanelWithIcon(WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()
self.request = get_dummy_request()
self.request.user = self.user
self.text = "Test full-featured snippet with icon text"
test_snippet = SnippetChooserModel.objects.create(
advert=Advert.objects.create(text="foo"),
full_featured=FullFeaturedSnippet.objects.create(text=self.text),
)
self.edit_handler = get_edit_handler(SnippetChooserModel)
self.form_class = self.edit_handler.get_form_class()
form = self.form_class(instance=test_snippet)
edit_handler = self.edit_handler.get_bound_panel(
instance=test_snippet, form=form, request=self.request
)
self.object_chooser_panel = [
panel
for panel in edit_handler.children
if getattr(panel, "field_name", None) == "full_featured"
][0]
def test_render_html(self):
field_html = self.object_chooser_panel.render_html()
self.assertIn(self.text, field_html)
self.assertIn("Choose full-featured snippet", field_html)
self.assertIn("Choose another full-featured snippet", field_html)
self.assertIn("icon icon-cog icon", field_html)
# make sure no snippet icons remain
self.assertNotIn("icon-snippet", field_html)
def test_render_as_empty_field(self):
test_snippet = SnippetChooserModel()
form = self.form_class(instance=test_snippet)
edit_handler = self.edit_handler.get_bound_panel(
instance=test_snippet, form=form, request=self.request
)
snippet_chooser_panel = [
panel
for panel in edit_handler.children
if getattr(panel, "field_name", None) == "full_featured"
][0]
field_html = snippet_chooser_panel.render_html()
self.assertIn("Choose full-featured snippet", field_html)
self.assertIn("Choose another full-featured snippet", field_html)
self.assertIn("icon icon-cog icon", field_html)
# make sure no snippet icons remain
self.assertNotIn("icon-snippet", field_html)
def test_chooser_popup(self):
response = self.client.get(
reverse("wagtailsnippetchoosers_tests_fullfeaturedsnippet:choose")
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["header_icon"], "cog")
self.assertContains(response, "icon icon-cog", count=1)
self.assertEqual(response.context["icon"], "cog")
# make sure no snippet icons remain
for key in response.context.keys():
if "icon" in key:
self.assertNotIn("snippet", response.context[key])

Wyświetl plik

@ -0,0 +1,24 @@
# Generated by Django 4.1.7 on 2023-03-13 16:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("tests", "0022_variousondeletemodel"),
]
operations = [
migrations.AddField(
model_name="snippetchoosermodel",
name="full_featured",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="tests.fullfeaturedsnippet",
),
),
]

Wyświetl plik

@ -1135,9 +1135,6 @@ class FullFeaturedSnippet(
verbose_name_plural = "full-featured snippets"
register_snippet(FullFeaturedSnippet)
def get_default_advert():
return Advert.objects.first()
@ -1343,9 +1340,13 @@ class EventPageChooserModel(models.Model):
class SnippetChooserModel(models.Model):
advert = models.ForeignKey(Advert, help_text="help text", on_delete=models.CASCADE)
full_featured = models.ForeignKey(
FullFeaturedSnippet, on_delete=models.CASCADE, null=True, blank=True
)
panels = [
FieldPanel("advert"),
FieldPanel("full_featured"),
]

Wyświetl plik

@ -12,8 +12,10 @@ from wagtail.admin.ui.components import Component
from wagtail.admin.views.account import BaseSettingsPanel
from wagtail.admin.widgets import Button
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import SnippetViewSet
from wagtail.test.snippets.models import FilterableSnippet
from wagtail.test.snippets.views import FilterableSnippetViewSet
from wagtail.test.testapp.models import FullFeaturedSnippet
from .forms import FavouriteColourForm
@ -226,3 +228,10 @@ def add_broken_links_summary_item(request, items):
register_snippet(FilterableSnippet, viewset=FilterableSnippetViewSet)
class FullFeaturedSnippetViewSet(SnippetViewSet):
icon = "cog"
register_snippet(FullFeaturedSnippet, viewset=FullFeaturedSnippetViewSet)