kopia lustrzana https://github.com/wagtail/wagtail
Fix audit log display of deleted models
The audit log report currently raises an unhandled exception if it tries to display log entries for model instances where the model was deleted after the log entry was made. This commit fixes that bug. If the model was deleted via a migration but the content type still exists in the database (a "stale content type" in Django parlance), the report will now display a capitalized version of the content type's "name" field, for example "Homepage" for the HomePage model. If the content type has also been deleted (likely via Django's remove_stale_contenttypes management command), the report will now display the phrase "Unknown content type". Fixes issue 8699.pull/10132/head
rodzic
93077eaccd
commit
8d1835f55c
|
@ -4,6 +4,7 @@ from io import BytesIO
|
|||
from django.conf import settings
|
||||
from django.conf.locale import LANG_INFO
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
|
@ -11,7 +12,7 @@ from django.utils import timezone, translation
|
|||
from openpyxl import load_workbook
|
||||
|
||||
from wagtail.admin.views.mixins import ExcelDateFormatter
|
||||
from wagtail.models import Page, PageLogEntry
|
||||
from wagtail.models import ModelLogEntry, Page, PageLogEntry
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
||||
|
||||
|
@ -279,6 +280,34 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
|
|||
],
|
||||
)
|
||||
|
||||
def test_log_entry_with_stale_content_type(self):
|
||||
stale_content_type = ContentType.objects.create(
|
||||
app_label="fake_app", model="deleted model"
|
||||
)
|
||||
|
||||
ModelLogEntry.objects.create(
|
||||
object_id=123,
|
||||
content_type=stale_content_type,
|
||||
label="This instance's model was deleted, but its content type was not",
|
||||
action="wagtail.create",
|
||||
timestamp=timezone.now(),
|
||||
)
|
||||
|
||||
response = self.get()
|
||||
self.assertContains(response, "Deleted model")
|
||||
|
||||
def test_log_entry_with_null_content_type(self):
|
||||
ModelLogEntry.objects.create(
|
||||
object_id=123,
|
||||
content_type=None,
|
||||
label="This instance's model was deleted, and so was its content type",
|
||||
action="wagtail.create",
|
||||
timestamp=timezone.now(),
|
||||
)
|
||||
|
||||
response = self.get()
|
||||
self.assertContains(response, "Unknown content type")
|
||||
|
||||
|
||||
@override_settings(
|
||||
USE_L10N=True,
|
||||
|
|
|
@ -19,6 +19,7 @@ from django.test import RequestFactory
|
|||
from django.utils.encoding import force_str
|
||||
from django.utils.text import capfirst, slugify
|
||||
from django.utils.translation import check_for_language, get_supported_language_variant
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from wagtail.models import Site
|
||||
|
@ -156,6 +157,9 @@ def get_content_type_label(content_type):
|
|||
Return a human-readable label for a content type object, suitable for display in the admin
|
||||
in place of the default 'wagtailcore | page' representation
|
||||
"""
|
||||
if content_type is None:
|
||||
return _("Unknown content type")
|
||||
|
||||
model = content_type.model_class()
|
||||
if model:
|
||||
return str(capfirst(model._meta.verbose_name))
|
||||
|
|
|
@ -65,8 +65,19 @@ class LogEntryQuerySet(models.QuerySet):
|
|||
{}
|
||||
) # lookup of (content_type_id, stringified_object_id) to instance
|
||||
for content_type_id, object_ids in ids_by_content_type.items():
|
||||
model = ContentType.objects.get_for_id(content_type_id).model_class()
|
||||
model_instances = model.objects.in_bulk(object_ids)
|
||||
try:
|
||||
content_type = ContentType.objects.get_for_id(content_type_id)
|
||||
model = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
model = None
|
||||
|
||||
if model:
|
||||
model_instances = model.objects.in_bulk(object_ids)
|
||||
else:
|
||||
# The model class for the logged instance no longer exists,
|
||||
# so we have no instance to return. Return None instead.
|
||||
model_instances = {object_id: None for object_id in object_ids}
|
||||
|
||||
for object_id, instance in model_instances.items():
|
||||
instances_by_id[(content_type_id, str(object_id))] = instance
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*
|
||||
import pickle
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
|
||||
from django.test import SimpleTestCase, TestCase, override_settings
|
||||
from django.utils.text import slugify
|
||||
|
@ -14,6 +15,7 @@ from wagtail.coreutils import (
|
|||
cautious_slugify,
|
||||
find_available_slug,
|
||||
get_content_languages,
|
||||
get_content_type_label,
|
||||
get_dummy_request,
|
||||
get_supported_content_language_variant,
|
||||
multigetattr,
|
||||
|
@ -295,6 +297,21 @@ class TestGetContentLanguages(TestCase):
|
|||
)
|
||||
|
||||
|
||||
def TestGetContentTypeLabel(TestCase):
|
||||
def test_none(self):
|
||||
self.assertEqual(get_content_type_label(None), "Unknown content type")
|
||||
|
||||
def test_valid_content_type(self):
|
||||
page_content_type = ContentType.objects.get_for_model(Page)
|
||||
self.assertEqual(get_content_type_label(page_content_type), "Page")
|
||||
|
||||
def test_stale_content_type(self):
|
||||
stale_content_type = ContentType.objects.create(
|
||||
app_label="fake_app", model="deleted model"
|
||||
)
|
||||
self.assertEqual(get_content_type_label(stale_content_type), "Deleted model")
|
||||
|
||||
|
||||
@override_settings(
|
||||
USE_I18N=True,
|
||||
WAGTAIL_I18N_ENABLED=True,
|
||||
|
|
Ładowanie…
Reference in New Issue