diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index 4d782ba53d485..41cf58ff46fb2 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -228,6 +228,8 @@ def __get__(self, instance, cls=None): 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/models.py b/tests/contenttypes_tests/models.py index 2e0640459f880..70f162b4195d4 100644 --- a/tests/contenttypes_tests/models.py +++ b/tests/contenttypes_tests/models.py @@ -97,6 +97,13 @@ class Meta: order_with_respect_to = 'parent' +class AuditLog(models.Model): + message = models.CharField(max_length=200) + content_type = models.ForeignKey(ContentType, models.CASCADE, null=True) + object_id = models.PositiveIntegerField(null=True) + object = GenericForeignKey() + + class ModelWithNullFKToSite(models.Model): title = models.CharField(max_length=200) site = models.ForeignKey(Site, null=True, on_delete=models.CASCADE) diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py index ce5e244df542d..3eb6c4c10aa4f 100644 --- a/tests/contenttypes_tests/test_fields.py +++ b/tests/contenttypes_tests/test_fields.py @@ -2,14 +2,14 @@ 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, AuditLog, Question @isolate_apps('contenttypes_tests') -class GenericForeignKeyTests(SimpleTestCase): +class GenericForeignKeyTests(TestCase): def test_str(self): class Model(models.Model): @@ -24,6 +24,18 @@ def test_incorrect_get_prefetch_queryset_arguments(self): 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="test") + question_pk = question.pk + log = AuditLog.objects.create(message="Created", object=question) + Question.objects.all().delete() + + log = AuditLog.objects.get(message="Created") + with self.assertNumQueries(1): + self.assertEqual(log.object_id, question_pk) + self.assertIsNone(log.object) + self.assertIsNone(log.object) + class GenericRelationTests(TestCase): diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index 3e4a04ada6efa..36059f19e749e 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -1041,8 +1041,8 @@ def test_deleted_object_GFK(self): book1_pk = self.book1.pk self.book1.delete() - with self.assertNumQueries(3): - qs = TaggedItem.objects.filter(tag='awesome').prefetch_related('content_object') + with self.assertNumQueries(2): + qs = TaggedItem.objects.filter(tag="awesome").prefetch_related("content_object") result = [(t.object_id, t.content_type_id, t.content_object) for t in qs] self.assertEqual(result, [ (book1_pk, ct.pk, None),