diff options
| author | Take Weiland <diesieben07@gmail.com> | 2025-03-30 11:53:32 +0200 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-07-22 12:14:20 +0200 |
| commit | e709301000edddc48ebfe9535e2e2177d3bcedb1 (patch) | |
| tree | 522431700f0bfc35274c54a1d2a637dcf3b7bd37 /tests | |
| parent | 5488530a272b863794484ee2b027294ff2ec86d2 (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.py | 4 | ||||
| -rw-r--r-- | tests/prefetch_related/tests.py | 182 |
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(), [] + ) |
