diff --git a/docs/reference/viewsets.md b/docs/reference/viewsets.md
index 73cb301828..7204fdb777 100644
--- a/docs/reference/viewsets.md
+++ b/docs/reference/viewsets.md
@@ -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
diff --git a/docs/topics/snippets/customising.md b/docs/topics/snippets/customising.md
index 7d326fba22..888c4d6639 100644
--- a/docs/topics/snippets/customising.md
+++ b/docs/topics/snippets/customising.md
@@ -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:
diff --git a/wagtail/snippets/tests/test_viewset.py b/wagtail/snippets/tests/test_viewset.py
index 528f3cdf9b..f658f0dc06 100644
--- a/wagtail/snippets/tests/test_viewset.py
+++ b/wagtail/snippets/tests/test_viewset.py
@@ -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, 'Text
",
+ 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,
+ "