diff --git a/django/db/models/fields/mixins.py b/django/db/models/fields/mixins.py index c6dbc4ec5e5..f79b7b0c6eb 100644 --- a/django/db/models/fields/mixins.py +++ b/django/db/models/fields/mixins.py @@ -12,6 +12,22 @@ class FieldCacheMixin: try: return instance._state.fields_cache[cache_name] except KeyError: + # An ancestor link will exist if this field is defined on a + # multi-table inheritance parent of the instance's class. + ancestor_link = instance._meta.get_ancestor_link(self.model) + if ancestor_link: + try: + # The value might be cached on an ancestor if the instance + # originated from walking down the inheritance chain. + ancestor = ancestor_link.get_cached_value(instance) + except KeyError: + pass + else: + value = self.get_cached_value(ancestor) + # Cache the ancestor value locally to speed up future + # lookups. + self.set_cached_value(instance, value) + return value if default is NOT_PROVIDED: raise return default diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py index a542385d602..a4f9cfe6962 100644 --- a/tests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py @@ -178,6 +178,7 @@ class GrandParent(models.Model): first_name = models.CharField(max_length=80) last_name = models.CharField(max_length=80) email = models.EmailField(unique=True) + place = models.ForeignKey(Place, models.CASCADE, null=True, related_name='+') class Meta: unique_together = ('first_name', 'last_name') diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index 88f1e623c47..ad85a653d5b 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -368,6 +368,22 @@ class ModelInheritanceDataTests(TestCase): self.assertEqual(qs[1].italianrestaurant.name, 'Ristorante Miron') self.assertEqual(qs[1].italianrestaurant.rating, 4) + def test_parent_cache_reuse(self): + place = Place.objects.create() + GrandChild.objects.create(place=place) + grand_parent = GrandParent.objects.latest('pk') + with self.assertNumQueries(1): + self.assertEqual(grand_parent.place, place) + parent = grand_parent.parent + with self.assertNumQueries(0): + self.assertEqual(parent.place, place) + child = parent.child + with self.assertNumQueries(0): + self.assertEqual(child.place, place) + grandchild = child.grandchild + with self.assertNumQueries(0): + self.assertEqual(grandchild.place, place) + def test_update_query_counts(self): """ Update queries do not generate unnecessary queries (#18304). diff --git a/tests/select_related_onetoone/tests.py b/tests/select_related_onetoone/tests.py index 089a1062d9b..992de982175 100644 --- a/tests/select_related_onetoone/tests.py +++ b/tests/select_related_onetoone/tests.py @@ -82,7 +82,7 @@ class ReverseSelectRelatedTestCase(TestCase): stat = UserStat.objects.select_related('user', 'advanceduserstat').get(posts=200) self.assertEqual(stat.advanceduserstat.posts, 200) self.assertEqual(stat.user.username, 'bob') - with self.assertNumQueries(1): + with self.assertNumQueries(0): self.assertEqual(stat.advanceduserstat.user.username, 'bob') def test_nullable_relation(self):