summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorTake Weiland <diesieben07@gmail.com>2025-03-30 11:53:32 +0200
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-07-22 12:14:20 +0200
commite709301000edddc48ebfe9535e2e2177d3bcedb1 (patch)
tree522431700f0bfc35274c54a1d2a637dcf3b7bd37 /tests
parent5488530a272b863794484ee2b027294ff2ec86d2 (diff)
Fixed #36282 -- Used prefetched values in ForwardManyToOneDescriptor from indirect ancestors.
When looking for cached values in ManyRelatedManager and ForwardManyToOneDescriptor walk up the whole chain of ancestors (as long as they are cached) to find the prefetched relation.
Diffstat (limited to 'tests')
-rw-r--r--tests/prefetch_related/models.py4
-rw-r--r--tests/prefetch_related/tests.py182
2 files changed, 186 insertions, 0 deletions
diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py
index 405e9bba00..de2d88395d 100644
--- a/tests/prefetch_related/models.py
+++ b/tests/prefetch_related/models.py
@@ -28,6 +28,10 @@ class AuthorWithAge(Author):
age = models.IntegerField()
+class AuthorWithAgeChild(AuthorWithAge):
+ pass
+
+
class FavoriteAuthors(models.Model):
author = models.ForeignKey(
Author, models.CASCADE, to_field="name", related_name="i_like"
diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
index bd37ca0ec3..1955809aec 100644
--- a/tests/prefetch_related/tests.py
+++ b/tests/prefetch_related/tests.py
@@ -21,6 +21,7 @@ from .models import (
Author2,
AuthorAddress,
AuthorWithAge,
+ AuthorWithAgeChild,
Bio,
Book,
Bookmark,
@@ -2086,3 +2087,184 @@ class PrefetchLimitTests(TestDataMixin, TestCase):
with self.subTest(book=book):
self.assertEqual(len(book.authors_sliced), 1)
self.assertIn(book.authors_sliced[0], list(book.authors.all()))
+
+
+class PrefetchRelatedMTICacheTests(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.book = Book.objects.create(title="Book1")
+ cls.related1 = Author.objects.create(name="related1", first_book=cls.book)
+ cls.related2 = Author.objects.create(name="related2", first_book=cls.book)
+ cls.related3 = Author.objects.create(name="related3", first_book=cls.book)
+ cls.related4 = Author.objects.create(name="related4", first_book=cls.book)
+
+ cls.child = AuthorWithAgeChild.objects.create(
+ name="child",
+ age=31,
+ first_book=cls.book,
+ )
+ cls.m2m_child = AuthorWithAgeChild.objects.create(
+ name="m2m_child",
+ age=31,
+ first_book=cls.book,
+ )
+ cls.m2m_child.favorite_authors.set([cls.related1, cls.related2, cls.related3])
+
+ def test_parent_fk_available_in_child(self):
+ qs = (
+ Author.objects.select_related("authorwithage")
+ .prefetch_related("first_book")
+ .filter(pk=self.child.pk)
+ )
+ with self.assertNumQueries(2):
+ results = list(qs)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0].authorwithage.first_book, self.book)
+
+ def test_grandparent_fk_available_in_child(self):
+ qs = (
+ Author.objects.select_related(
+ "authorwithage", "authorwithage__authorwithagechild"
+ )
+ .prefetch_related("first_book")
+ .filter(pk=self.child.pk)
+ )
+ with self.assertNumQueries(2):
+ results = list(qs)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(
+ results[0].authorwithage.authorwithagechild.first_book, self.book
+ )
+
+ def test_parent_m2m_available_in_child(self):
+ qs = (
+ Author.objects.select_related("authorwithage")
+ .prefetch_related("favorite_authors")
+ .filter(pk=self.m2m_child.pk)
+ )
+ with self.assertNumQueries(2):
+ results = list(qs)
+ self.assertEqual(len(results), 1)
+ self.assertQuerySetEqual(
+ results[0].authorwithage.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+
+ def test_grandparent_m2m_available_in_child(self):
+ qs = (
+ Author.objects.select_related(
+ "authorwithage", "authorwithage__authorwithagechild"
+ )
+ .prefetch_related("favorite_authors")
+ .filter(pk=self.m2m_child.pk)
+ )
+ with self.assertNumQueries(2):
+ results = list(qs)
+ self.assertEqual(len(results), 1)
+ self.assertQuerySetEqual(
+ set(results[0].authorwithage.authorwithagechild.favorite_authors.all()),
+ {self.related1, self.related2, self.related3},
+ )
+
+ def test_add_clears_prefetched_objects_in_parent(self):
+ gp = (
+ Author.objects.select_related("authorwithage")
+ .prefetch_related("favorite_authors")
+ .get(pk=self.m2m_child.pk)
+ )
+ self.assertQuerySetEqual(
+ gp.favorite_authors.all(),
+ {self.related1, self.related2, self.related3},
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.favorite_authors.all(),
+ {self.related1, self.related2, self.related3},
+ )
+ gp.authorwithage.favorite_authors.add(self.related4)
+ self.assertQuerySetEqual(
+ gp.favorite_authors.all(),
+ {self.related1, self.related2, self.related3, self.related4},
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.favorite_authors.all(),
+ {self.related1, self.related2, self.related3, self.related4},
+ )
+
+ def test_add_clears_prefetched_objects_in_grandparent(self):
+ gp = (
+ Author.objects.select_related(
+ "authorwithage", "authorwithage__authorwithagechild"
+ )
+ .prefetch_related("favorite_authors")
+ .get(pk=self.m2m_child.pk)
+ )
+ self.assertQuerySetEqual(
+ gp.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.authorwithagechild.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ gp.authorwithage.authorwithagechild.favorite_authors.add(self.related4)
+ self.assertQuerySetEqual(
+ gp.favorite_authors.all(),
+ [self.related1, self.related2, self.related3, self.related4],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.favorite_authors.all(),
+ [self.related1, self.related2, self.related3, self.related4],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.authorwithagechild.favorite_authors.all(),
+ [self.related1, self.related2, self.related3, self.related4],
+ )
+
+ def test_remove_clears_prefetched_objects_in_parent(self):
+ gp = (
+ Author.objects.select_related("authorwithage")
+ .prefetch_related("favorite_authors")
+ .get(pk=self.m2m_child.pk)
+ )
+ self.assertQuerySetEqual(
+ gp.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ gp.authorwithage.favorite_authors.clear()
+ self.assertQuerySetEqual(gp.favorite_authors.all(), [])
+ self.assertQuerySetEqual(gp.authorwithage.favorite_authors.all(), [])
+
+ def test_remove_clears_prefetched_objects_in_grandparent(self):
+ gp = (
+ Author.objects.select_related(
+ "authorwithage", "authorwithage__authorwithagechild"
+ )
+ .prefetch_related("favorite_authors")
+ .get(pk=self.m2m_child.pk)
+ )
+ self.assertQuerySetEqual(
+ gp.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ self.assertQuerySetEqual(
+ gp.authorwithage.authorwithagechild.favorite_authors.all(),
+ [self.related1, self.related2, self.related3],
+ )
+ gp.authorwithage.favorite_authors.clear()
+ self.assertQuerySetEqual(gp.favorite_authors.all(), [])
+ self.assertQuerySetEqual(gp.authorwithage.favorite_authors.all(), [])
+ self.assertQuerySetEqual(
+ gp.authorwithage.authorwithagechild.favorite_authors.all(), []
+ )