diff --git a/wagtail/snippets/templates/wagtailsnippets/snippets/edit.html b/wagtail/snippets/templates/wagtailsnippets/snippets/edit.html index 2828c3db22..8273deb6ea 100644 --- a/wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +++ b/wagtail/snippets/templates/wagtailsnippets/snippets/edit.html @@ -9,6 +9,7 @@ {% trans "Last updated" %} {% include "wagtailadmin/shared/last_updated.html" with last_updated=latest_log_entry.timestamp time_prefix="at" %} + - History {% endif %}
diff --git a/wagtail/snippets/tests.py b/wagtail/snippets/tests.py index 5fc47d2243..b151c7d6d4 100644 --- a/wagtail/snippets/tests.py +++ b/wagtail/snippets/tests.py @@ -1,8 +1,10 @@ +import datetime import json from django.contrib.admin.utils import quote from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser, Permission +from django.contrib.contenttypes.models import ContentType from django.core import checks from django.core.exceptions import ValidationError from django.core.files.base import ContentFile @@ -12,6 +14,7 @@ from django.http import HttpRequest, HttpResponse from django.test import RequestFactory, TestCase from django.test.utils import override_settings from django.urls import reverse +from django.utils.timezone import make_aware from taggit.models import Tag from wagtail.admin.admin_url_finder import AdminURLFinder @@ -19,7 +22,7 @@ from wagtail.admin.edit_handlers import FieldPanel from wagtail.admin.forms import WagtailAdminModelForm from wagtail.core import hooks from wagtail.core.blocks.field_block import FieldBlockAdapter -from wagtail.core.models import Locale, Page +from wagtail.core.models import Locale, ModelLogEntry, Page from wagtail.snippets.action_menu import ActionMenuItem, get_base_snippet_action_menu_items from wagtail.snippets.blocks import SnippetChooserBlock from wagtail.snippets.edit_handlers import SnippetChooserPanel @@ -1054,6 +1057,32 @@ class TestUsedBy(TestCase): self.assertEqual(type(advert.get_usage()[0]), Page) +class TestSnippetHistory(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def get(self, params={}): + snippet = self.test_snippet + args = (snippet._meta.app_label, snippet._meta.model_name, quote(snippet.pk)) + return self.client.get(reverse('wagtailsnippets:history', args=args), params) + + def setUp(self): + self.user = self.login() + self.test_snippet = Advert.objects.get(pk=1) + ModelLogEntry.objects.create( + content_type=ContentType.objects.get_for_model(Advert), + label="Test Advert", + action='wagtail.create', + timestamp=make_aware(datetime.datetime(2021, 9, 30, 10, 1, 0)), + object_id='1', + ) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Created', html=True) + self.assertContains(response, '
') + + class TestSnippetChoose(TestCase, WagtailTestUtils): fixtures = ['test.json'] diff --git a/wagtail/snippets/urls.py b/wagtail/snippets/urls.py index bcbefd0a32..6da0b9c1fe 100644 --- a/wagtail/snippets/urls.py +++ b/wagtail/snippets/urls.py @@ -21,6 +21,7 @@ urlpatterns = [ path('//multiple/delete/', snippets.delete, name='delete-multiple'), path('//delete//', snippets.delete, name='delete'), path('//usage//', snippets.usage, name='usage'), + path('//history//', snippets.HistoryView.as_view(), name='history'), # legacy URLs that could potentially collide if the pk matches one of the reserved names above # ('add', 'edit' etc) - redirect to the unambiguous version diff --git a/wagtail/snippets/views/snippets.py b/wagtail/snippets/views/snippets.py index 34f344c913..f8c2aa3928 100644 --- a/wagtail/snippets/views/snippets.py +++ b/wagtail/snippets/views/snippets.py @@ -12,12 +12,14 @@ from django.template.response import TemplateResponse from django.urls import reverse from django.utils.text import capfirst from django.utils.translation import gettext as _ -from django.utils.translation import ngettext +from django.utils.translation import gettext_lazy, ngettext from django.views.generic import TemplateView from wagtail.admin import messages from wagtail.admin.edit_handlers import ObjectList, extract_panel_definitions_from_model_class from wagtail.admin.forms.search import SearchForm +from wagtail.admin.ui.tables import Column, DateColumn, UserColumn +from wagtail.admin.views.generic.models import IndexView from wagtail.core import hooks from wagtail.core.log_actions import log from wagtail.core.log_actions import registry as log_registry @@ -441,3 +443,32 @@ def redirect_to_delete(request, app_label, model_name, pk): def redirect_to_usage(request, app_label, model_name, pk): return redirect('wagtailsnippets:usage', app_label, model_name, pk, permanent=True) + + +class HistoryView(IndexView): + template_name = 'wagtailadmin/generic/index.html' + page_title = gettext_lazy('Snippet history') + header_icon = 'history' + paginate_by = 50 + columns = [ + Column('message', label=gettext_lazy("Action")), + UserColumn('user', blank_display_name='system'), + DateColumn('timestamp', label=gettext_lazy("Date")), + ] + + def dispatch(self, request, app_label, model_name, pk): + self.app_label = app_label + self.model_name = model_name + self.model = get_snippet_model_from_url_params(app_label, model_name) + self.object = get_object_or_404(self.model, pk=unquote(pk)) + + return super().dispatch(request) + + def get_page_subtitle(self): + return str(self.object) + + def get_index_url(self): + return reverse('wagtailsnippets:history', args=(self.app_label, self.model_name, quote(self.object.pk))) + + def get_queryset(self): + return log_registry.get_logs_for_instance(self.object).prefetch_related('user__wagtail_userprofile')