Fixed #27554 -- Fixed prefetch_related() crash when fetching relations in nested Prefetches.
This commit is contained in:
parent
16121da78d
commit
c0a2b9508a
|
@ -1423,10 +1423,15 @@ def prefetch_related_objects(model_instances, *related_lookups):
|
||||||
# that we can continue with nullable or reverse relations.
|
# that we can continue with nullable or reverse relations.
|
||||||
new_obj_list = []
|
new_obj_list = []
|
||||||
for obj in obj_list:
|
for obj in obj_list:
|
||||||
try:
|
if through_attr in getattr(obj, '_prefetched_objects_cache', ()):
|
||||||
new_obj = getattr(obj, through_attr)
|
# If related objects have been prefetched, use the
|
||||||
except exceptions.ObjectDoesNotExist:
|
# cache rather than the object's through_attr.
|
||||||
continue
|
new_obj = list(obj._prefetched_objects_cache.get(through_attr))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
new_obj = getattr(obj, through_attr)
|
||||||
|
except exceptions.ObjectDoesNotExist:
|
||||||
|
continue
|
||||||
if new_obj is None:
|
if new_obj is None:
|
||||||
continue
|
continue
|
||||||
# We special-case `list` rather than something more generic
|
# We special-case `list` rather than something more generic
|
||||||
|
|
|
@ -91,3 +91,6 @@ Bugfixes
|
||||||
|
|
||||||
* Corrected the return type of ``ArrayField(CITextField())`` values retrieved
|
* Corrected the return type of ``ArrayField(CITextField())`` values retrieved
|
||||||
from the database (:ticket:`28161`).
|
from the database (:ticket:`28161`).
|
||||||
|
|
||||||
|
* Fixed ``QuerySet.prefetch_related()`` crash when fetching relations in nested
|
||||||
|
``Prefetch`` objects (:ticket:`27554`).
|
||||||
|
|
|
@ -1361,3 +1361,37 @@ class Ticket25546Tests(TestCase):
|
||||||
self.assertEqual(book1.first_authors[0].happy_place, [self.author1_address1])
|
self.assertEqual(book1.first_authors[0].happy_place, [self.author1_address1])
|
||||||
self.assertEqual(book1.first_authors[1].happy_place, [])
|
self.assertEqual(book1.first_authors[1].happy_place, [])
|
||||||
self.assertEqual(book2.first_authors[0].happy_place, [self.author2_address1])
|
self.assertEqual(book2.first_authors[0].happy_place, [self.author2_address1])
|
||||||
|
|
||||||
|
|
||||||
|
class ReadPrefetchedObjectsCacheTests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.book1 = Book.objects.create(title='Les confessions Volume I')
|
||||||
|
cls.book2 = Book.objects.create(title='Candide')
|
||||||
|
cls.author1 = AuthorWithAge.objects.create(name='Rousseau', first_book=cls.book1, age=70)
|
||||||
|
cls.author2 = AuthorWithAge.objects.create(name='Voltaire', first_book=cls.book2, age=65)
|
||||||
|
cls.book1.authors.add(cls.author1)
|
||||||
|
cls.book2.authors.add(cls.author2)
|
||||||
|
FavoriteAuthors.objects.create(author=cls.author1, likes_author=cls.author2)
|
||||||
|
|
||||||
|
def test_retrieves_results_from_prefetched_objects_cache(self):
|
||||||
|
"""
|
||||||
|
When intermediary results are prefetched without a destination
|
||||||
|
attribute, they are saved in the RelatedManager's cache
|
||||||
|
(_prefetched_objects_cache). prefetch_related() uses this cache
|
||||||
|
(#27554).
|
||||||
|
"""
|
||||||
|
authors = AuthorWithAge.objects.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'author',
|
||||||
|
queryset=Author.objects.prefetch_related(
|
||||||
|
# Results are saved in the RelatedManager's cache
|
||||||
|
# (_prefetched_objects_cache) and do not replace the
|
||||||
|
# RelatedManager on Author instances (favorite_authors)
|
||||||
|
Prefetch('favorite_authors__first_book'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
with self.assertNumQueries(4):
|
||||||
|
# AuthorWithAge -> Author -> FavoriteAuthors, Book
|
||||||
|
self.assertQuerysetEqual(authors, ['<AuthorWithAge: Rousseau>', '<AuthorWithAge: Voltaire>'])
|
||||||
|
|
Loading…
Reference in New Issue