summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Svoboda <martin.svoboda@gmail.com>2021-08-11 16:42:43 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-10-14 13:07:24 +0200
commitdd8945d361d0c0f3183f32dfc729f28726c005f8 (patch)
tree21ece213e9fbf0c5158c3df10fcc0219ee7b8fbc
parent8ab95364b5182fd702478289f98b01d16a21016e (diff)
[4.0.x] Fixed #33008 -- Fixed prefetch_related() for deleted GenericForeignKeys.
Thanks Simon Charette for the implementation idea. Backport of cc4cb95beff0b75ec169add7e94cc481624a41e6 from main
-rw-r--r--django/contrib/contenttypes/fields.py4
-rw-r--r--tests/contenttypes_tests/test_fields.py19
-rw-r--r--tests/prefetch_related/tests.py18
3 files changed, 37 insertions, 4 deletions
diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py
index fbd55606ae..63ee408bf5 100644
--- a/django/contrib/contenttypes/fields.py
+++ b/django/contrib/contenttypes/fields.py
@@ -213,7 +213,7 @@ class GenericForeignKey(FieldCacheMixin):
gfk_key,
True,
self.name,
- True,
+ False,
)
def __get__(self, instance, cls=None):
@@ -229,6 +229,8 @@ class GenericForeignKey(FieldCacheMixin):
pk_val = getattr(instance, self.fk_field)
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:
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
diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py
index ce5e244df5..5638846cc5 100644
--- a/tests/contenttypes_tests/test_fields.py
+++ b/tests/contenttypes_tests/test_fields.py
@@ -2,14 +2,14 @@ import json
from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models
-from django.test import SimpleTestCase, TestCase
+from django.test import TestCase
from django.test.utils import isolate_apps
-from .models import Answer, Question
+from .models import Answer, Post, Question
@isolate_apps('contenttypes_tests')
-class GenericForeignKeyTests(SimpleTestCase):
+class GenericForeignKeyTests(TestCase):
def test_str(self):
class Model(models.Model):
@@ -24,6 +24,19 @@ class GenericForeignKeyTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, "Custom queryset can't be used for this lookup."):
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):
diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
index cba1897fc5..242ff6c366 100644
--- a/tests/prefetch_related/tests.py
+++ b/tests/prefetch_related/tests.py
@@ -1033,6 +1033,24 @@ class GenericRelationTests(TestCase):
# instance returned by the manager.
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):