Add docs and tests for snippets inspect view

pull/10477/head
Sage Abdullah 2023-06-30 17:42:57 +01:00 zatwierdzone przez Matt Westcott
rodzic 4b9f7df4fd
commit 0bebe532e8
6 zmienionych plików z 264 dodań i 5 usunięć

Wyświetl plik

@ -96,6 +96,9 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit
.. autoattribute:: chooser_per_page
.. autoattribute:: export_filename
.. autoattribute:: ordering
.. autoattribute:: inspect_view_enabled
.. autoattribute:: inspect_view_fields
.. autoattribute:: inspect_view_fields_exclude
.. autoattribute:: admin_url_namespace
.. autoattribute:: base_url_path
.. autoattribute:: chooser_admin_url_namespace
@ -106,6 +109,7 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit
.. autoattribute:: delete_view_class
.. autoattribute:: usage_view_class
.. autoattribute:: history_view_class
.. autoattribute:: inspect_view_class
.. autoattribute:: revisions_view_class
.. autoattribute:: revisions_revert_view_class
.. autoattribute:: revisions_compare_view_class
@ -137,6 +141,7 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit
.. automethod:: get_edit_template
.. automethod:: get_delete_template
.. automethod:: get_history_template
.. automethod:: get_inspect_template
.. automethod:: get_admin_url_namespace
.. automethod:: get_admin_base_path
.. automethod:: get_chooser_admin_url_namespace

Wyświetl plik

@ -53,6 +53,7 @@ class MemberViewSet(SnippetViewSet):
icon = "user"
list_display = ["name", "shirt_size", "get_shirt_size_display", UpdatedAtColumn()]
list_per_page = 50
inspect_view_enabled = True
admin_url_namespace = "member_views"
base_url_path = "internal/member"
filterset_class = MemberFilterSet
@ -99,6 +100,18 @@ You can add the ability to export the listing view to a spreadsheet by setting t
The ability to export the listing view was added.
```
## Inspect view
```{versionadded} 5.1
The ability to enable inspect view was added.
```
The inspect view is disabled by default, as it's not often useful for most models. However, if you need a view that enables users to view more detailed information about an instance without the option to edit it, you can enable the inspect view by setting {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.inspect_view_enabled` on your `SnippetViewSet` class.
When inspect view is enabled, an 'Inspect' button will automatically appear for each row on the listing view, which takes you to a view that shows a list of field values for that particular snippet.
By default, all 'concrete' fields (where the field value is stored as a column in the database table for your model) will be shown. You can customise what values are displayed by specifying the {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.inspect_view_fields` or the {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.inspect_view_fields_exclude` attributes to your `SnippetViewSet` class.
## Templates
For all views that are used for a snippet model, Wagtail looks for templates in the following directories within your project or app, before resorting to the defaults:

Wyświetl plik

@ -8,8 +8,9 @@ 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
from django.template.defaultfilters import date
from django.test import TestCase, TransactionTestCase
from django.urls import reverse
from django.urls import NoReverseMatch, resolve, reverse
from django.utils.timezone import now
from openpyxl import load_workbook
@ -20,6 +21,10 @@ from wagtail.admin.staticfiles import versioned_static
from wagtail.admin.views.mixins import ExcelDateFormatter
from wagtail.blocks.field_block import FieldBlockAdapter
from wagtail.coreutils import get_dummy_request
from wagtail.documents import get_document_model
from wagtail.documents.tests.utils import get_test_document_file
from wagtail.images import get_image_model
from wagtail.images.tests.utils import get_test_image_file
from wagtail.models import Locale, Workflow, WorkflowContentType
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.snippets.models import register_snippet
@ -33,6 +38,7 @@ from wagtail.test.testapp.models import (
RevisableChildModel,
RevisableModel,
SnippetChooserModel,
VariousOnDeleteModel,
)
from wagtail.test.utils import WagtailTestUtils
@ -1073,3 +1079,222 @@ class TestCustomFormClass(BaseSnippetViewSetTests):
edit_view = self.client.get(self.get_url("edit", args=(quote(obj.pk),)))
self.assertContains(edit_view, '<input type="text" name="text"')
self.assertNotContains(edit_view, '<textarea name="text"')
class TestInspectViewConfiguration(BaseSnippetViewSetTests):
model = FullFeaturedSnippet
def setUp(self):
super().setUp()
self.viewset = self.model.snippet_viewset
self.object = self.model.objects.create(text="Perkedel", country_code="ID")
def test_enabled(self):
self.model = FullFeaturedSnippet
url = self.get_url("inspect", args=(quote(self.object.pk),))
response = self.client.get(url)
self.assertContains(
response,
"<dt>Text</dt> <dd>Perkedel</dd>",
html=True,
)
self.assertContains(
response,
"<dt>Country code</dt> <dd>Indonesia</dd>",
html=True,
)
self.assertContains(
response,
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
html=True,
)
self.assertNotContains(
response,
"<dt>Some attribute</dt> <dd>some value</dd>",
html=True,
)
self.assertContains(
response,
self.get_url("edit", args=(quote(self.object.pk),)),
)
self.assertContains(
response,
self.get_url("delete", args=(quote(self.object.pk),)),
)
def test_disabled(self):
self.model = Advert
object = self.model.objects.create(text="ad")
with self.assertRaises(NoReverseMatch):
self.get_url("inspect", args=(quote(object.pk),))
def test_only_add_permission(self):
self.model = FullFeaturedSnippet
self.user.is_superuser = False
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
Permission.objects.get(
content_type__app_label=self.model._meta.app_label,
codename=get_permission_codename("add", self.model._meta),
),
)
self.user.save()
url = self.get_url("inspect", args=(quote(self.object.pk),))
response = self.client.get(url)
self.assertContains(
response,
"<dt>Text</dt> <dd>Perkedel</dd>",
html=True,
)
self.assertContains(
response,
"<dt>Country code</dt> <dd>Indonesia</dd>",
html=True,
)
self.assertContains(
response,
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
html=True,
)
self.assertNotContains(
response,
self.get_url("edit", args=(quote(self.object.pk),)),
)
self.assertNotContains(
response,
self.get_url("delete", args=(quote(self.object.pk),)),
)
def test_custom_fields(self):
self.model = FullFeaturedSnippet
url = self.get_url("inspect", args=(quote(self.object.pk),))
view_func = resolve(url).func
adverts = [Advert.objects.create(text=f"advertisement {i}") for i in range(3)]
queryset = Advert.objects.filter(pk=adverts[0].pk)
mock_manager = mock.patch.object(
self.model, "adverts", Advert.objects, create=True
)
mock_queryset = mock.patch.object(
self.model, "some_queryset", queryset, create=True
)
mock_fields = mock.patch.dict(
view_func.view_initkwargs,
{
"fields": [
"country_code", # Field with choices (thus get_FOO_display method)
"some_date", # DateField
"some_attribute", # Model attribute
"adverts", # Manager
"some_queryset", # QuerySet
]
},
)
# We need to mock the view's init kwargs instead of the viewset's
# attributes, because the viewset's attributes are only used when the
# view is instantiated, and the view is instantiated once at startup.
with mock_manager, mock_queryset, mock_fields:
response = self.client.get(url)
self.assertNotContains(
response,
"<dt>Text</dt> <dd>Perkedel</dd>",
html=True,
)
self.assertContains(
response,
"<dt>Country code</dt> <dd>Indonesia</dd>",
html=True,
)
self.assertContains(
response,
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
html=True,
)
self.assertContains(
response,
"<dt>Some attribute</dt> <dd>some value</dd>",
html=True,
)
self.assertContains(
response,
"""
<dt>Adverts</dt>
<dd>advertisement 0, advertisement 1, advertisement 2</dd>
""",
html=True,
)
self.assertContains(
response,
"<dt>Some queryset</dt> <dd>advertisement 0</dd>",
html=True,
)
def test_exclude_fields(self):
self.model = FullFeaturedSnippet
url = self.get_url("inspect", args=(quote(self.object.pk),))
view_func = resolve(url).func
# We need to mock the view's init kwargs instead of the viewset's
# attributes, because the viewset's attributes are only used when the
# view is instantiated, and the view is instantiated once at startup.
with mock.patch.dict(
view_func.view_initkwargs,
{"fields_exclude": ["some_date"]},
):
response = self.client.get(url)
self.assertContains(
response,
"<dt>Text</dt> <dd>Perkedel</dd>",
html=True,
)
self.assertContains(
response,
"<dt>Country code</dt> <dd>Indonesia</dd>",
html=True,
)
self.assertNotContains(
response,
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
html=True,
)
self.assertNotContains(
response,
"<dt>Some attribute</dt> <dd>some value</dd>",
html=True,
)
def test_image_and_document_fields(self):
self.model = VariousOnDeleteModel
image = get_image_model().objects.create(
title="Test image",
file=get_test_image_file(),
)
document = get_document_model().objects.create(
title="Test document", file=get_test_document_file()
)
object = self.model.objects.create(
protected_image=image, protected_document=document
)
response = self.client.get(self.get_url("inspect", args=(quote(object.pk),)))
self.assertContains(
response,
f"<dt>Protected image</dt> <dd>{image.get_rendition('max-400x400').img_tag()}</dd>",
html=True,
)
self.assertContains(response, "<dt>Protected document</dt>", html=True)
self.assertContains(response, f'<a href="{document.url}">')
self.assertContains(response, "Test document")
self.assertContains(response, "TXT")
self.assertContains(response, f"{document.file.size}\xa0bytes")

Wyświetl plik

@ -701,7 +701,17 @@ class SnippetViewSet(ModelViewSet):
#: Whether to enable the inspect view. Defaults to ``False``.
inspect_view_enabled = False
#: The fields to display in the inspect view.
#: The model fields or attributes to display in the inspect view.
#:
#: If the field has a corresponding :meth:`~django.db.models.Model.get_FOO_display`
#: method on the model, the method's return value will be used instead.
#:
#: If you have ``wagtail.images`` installed, and the field's value is an instance of
#: ``wagtail.images.models.AbstractImage``, a thumbnail of that image will be rendered.
#:
#: If you have ``wagtail.documents`` installed, and the field's value is an instance of
#: ``wagtail.docs.models.AbstractDocument``, a link to that document will be rendered,
#: along with the document title, file extension and size.
inspect_view_fields = []
#: The fields to exclude from the inspect view.

Wyświetl plik

@ -1117,6 +1117,8 @@ class FullFeaturedSnippet(
)
some_date = models.DateField(auto_now=True)
some_attribute = "some value"
search_fields = [
index.SearchField("text"),
index.FilterField("text"),
@ -1225,9 +1227,6 @@ class VariousOnDeleteModel(models.Model):
rich_text = RichTextField(blank=True)
register_snippet(VariousOnDeleteModel)
class StandardIndex(Page):
"""Index for the site"""

Wyświetl plik

@ -28,6 +28,7 @@ from wagtail.test.testapp.models import (
ModeratedModel,
RevisableChildModel,
RevisableModel,
VariousOnDeleteModel,
)
from .forms import FavouriteColourForm
@ -342,7 +343,13 @@ class ModeratedModelViewSet(SnippetViewSet):
}
class VariousOnDeleteModelViewSet(SnippetViewSet):
model = VariousOnDeleteModel
inspect_view_enabled = True
register_snippet(FullFeaturedSnippet, viewset=FullFeaturedSnippetViewSet)
register_snippet(DraftStateModel, viewset=DraftStateModelViewSet)
register_snippet(ModeratedModelViewSet)
register_snippet(RevisableViewSetGroup)
register_snippet(VariousOnDeleteModelViewSet)