Fix RevisionQuerySet.for_instance() when used with a non-specific instance

If the method was used with a base Page instance, it wouldn't return any
revisions because we would be filtering on the content_type FK using the
current model (the base Page model) instead of the specific model.

Filter on base_content_type and make use of
RevisionMixin.get_base_content_type() instead, which will
resolve to the base Page model (and the correct the most basic
non-abstract model for non-Page models with MTI).

Use the old logic if the instance's model does not use RevisionMixin for
some reason.

This logic is similar to WorkflowStateQuerySet.for_instance() and
TaskQuerySet.for_instance().
pull/12314/head
Sage Abdullah 2024-08-27 17:18:54 +01:00 zatwierdzone przez Matt Westcott
rodzic d79a2e7012
commit 1b691a905f
2 zmienionych plików z 41 dodań i 6 usunięć

Wyświetl plik

@ -2792,12 +2792,20 @@ class RevisionQuerySet(models.QuerySet):
return self.exclude(self.page_revisions_q())
def for_instance(self, instance):
return self.filter(
content_type=ContentType.objects.get_for_model(
instance, for_concrete_model=False
),
object_id=str(instance.pk),
)
try:
# Use RevisionMixin.get_base_content_type() if available
return self.filter(
base_content_type=instance.get_base_content_type(),
object_id=str(instance.pk),
)
except AttributeError:
# Fallback to ContentType for the model
return self.filter(
content_type=ContentType.objects.get_for_model(
instance, for_concrete_model=False
),
object_id=str(instance.pk),
)
class RevisionsManager(models.Manager.from_queryset(RevisionQuerySet)):

Wyświetl plik

@ -70,6 +70,9 @@ class TestRevisableModel(TestCase):
self.assertEqual(self.instance.get_base_content_type(), self.content_type)
self.assertEqual(self.instance.get_content_type(), self.content_type)
# The for_instance() method should return the revision
self.assertEqual(Revision.objects.for_instance(self.instance).first(), revision)
def test_content_type_with_inheritance(self):
instance = RevisableGrandChildModel.objects.create(text="test")
instance.text = "test updated"
@ -87,6 +90,18 @@ class TestRevisableModel(TestCase):
self.assertEqual(instance.get_base_content_type(), base_content_type)
self.assertEqual(instance.get_content_type(), content_type)
# The for_instance() method should return the revision,
# whether we're using the specific instance
self.assertIsInstance(instance, RevisableModel)
self.assertIsInstance(instance, RevisableGrandChildModel)
self.assertEqual(Revision.objects.for_instance(instance).first(), revision)
# or the base instance
base_instance = RevisableModel.objects.get(pk=instance.pk)
self.assertIsInstance(base_instance, RevisableModel)
self.assertNotIsInstance(base_instance, RevisableGrandChildModel)
self.assertEqual(Revision.objects.for_instance(base_instance).first(), revision)
def test_content_type_for_page_model(self):
hello_page = self.create_page()
hello_page.content = "Updated world"
@ -104,6 +119,18 @@ class TestRevisableModel(TestCase):
self.assertEqual(hello_page.get_base_content_type(), base_content_type)
self.assertEqual(hello_page.get_content_type(), content_type)
# The for_instance() method should return the revision,
# whether we're using the specific instance
self.assertIsInstance(hello_page, SimplePage)
self.assertIsInstance(hello_page, Page)
self.assertEqual(Revision.objects.for_instance(hello_page).first(), revision)
# or the base instance
base_instance = Page.objects.get(pk=hello_page.pk)
self.assertIsInstance(base_instance, Page)
self.assertNotIsInstance(base_instance, SimplePage)
self.assertEqual(Revision.objects.for_instance(base_instance).first(), revision)
def test_as_object(self):
self.instance.text = "updated"
self.instance.save_revision()