Fixed #33008 -- Fixed prefetch_related() for deleted GenericForeignKeys.

Thanks Simon Charette for the implementation idea.
This commit is contained in:
Martin Svoboda 2021-08-11 16:42:43 +02:00 committed by Mariusz Felisiak
parent cdad96e633
commit cc4cb95bef
3 changed files with 37 additions and 4 deletions

View File

@ -213,7 +213,7 @@ class GenericForeignKey(FieldCacheMixin):
gfk_key, gfk_key,
True, True,
self.name, self.name,
True, False,
) )
def __get__(self, instance, cls=None): def __get__(self, instance, cls=None):
@ -229,6 +229,8 @@ class GenericForeignKey(FieldCacheMixin):
pk_val = getattr(instance, self.fk_field) pk_val = getattr(instance, self.fk_field)
rel_obj = self.get_cached_value(instance, default=None) rel_obj = self.get_cached_value(instance, default=None)
if rel_obj is None and self.is_cached(instance):
return rel_obj
if rel_obj is not None: if rel_obj is not None:
ct_match = ct_id == self.get_content_type(obj=rel_obj, using=instance._state.db).id ct_match = ct_id == self.get_content_type(obj=rel_obj, using=instance._state.db).id
pk_match = rel_obj._meta.pk.to_python(pk_val) == rel_obj.pk pk_match = rel_obj._meta.pk.to_python(pk_val) == rel_obj.pk

View File

@ -2,14 +2,14 @@ import json
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models from django.db import models
from django.test import SimpleTestCase, TestCase from django.test import TestCase
from django.test.utils import isolate_apps from django.test.utils import isolate_apps
from .models import Answer, Question from .models import Answer, Post, Question
@isolate_apps('contenttypes_tests') @isolate_apps('contenttypes_tests')
class GenericForeignKeyTests(SimpleTestCase): class GenericForeignKeyTests(TestCase):
def test_str(self): def test_str(self):
class Model(models.Model): class Model(models.Model):
@ -24,6 +24,19 @@ class GenericForeignKeyTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, "Custom queryset can't be used for this lookup."): with self.assertRaisesMessage(ValueError, "Custom queryset can't be used for this lookup."):
Answer.question.get_prefetch_queryset(Answer.objects.all(), Answer.objects.all()) Answer.question.get_prefetch_queryset(Answer.objects.all(), Answer.objects.all())
def test_get_object_cache_respects_deleted_objects(self):
question = Question.objects.create(text='Who?')
post = Post.objects.create(title='Answer', parent=question)
question_pk = question.pk
Question.objects.all().delete()
post = Post.objects.get(pk=post.pk)
with self.assertNumQueries(1):
self.assertEqual(post.object_id, question_pk)
self.assertIsNone(post.parent)
self.assertIsNone(post.parent)
class GenericRelationTests(TestCase): class GenericRelationTests(TestCase):

View File

@ -1033,6 +1033,24 @@ class GenericRelationTests(TestCase):
# instance returned by the manager. # instance returned by the manager.
self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all())) self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all()))
def test_deleted_GFK(self):
TaggedItem.objects.create(tag='awesome', content_object=self.book1)
TaggedItem.objects.create(tag='awesome', content_object=self.book2)
ct = ContentType.objects.get_for_model(Book)
book1_pk = self.book1.pk
self.book1.delete()
with self.assertNumQueries(2):
qs = TaggedItem.objects.filter(tag='awesome').prefetch_related('content_object')
result = [
(tag.object_id, tag.content_type_id, tag.content_object) for tag in qs
]
self.assertEqual(result, [
(book1_pk, ct.pk, None),
(self.book2.pk, ct.pk, self.book2),
])
class MultiTableInheritanceTest(TestCase): class MultiTableInheritanceTest(TestCase):