[1.11.x] Fixed #27554 -- Fixed prefetch_related() crash when fetching relations in nested Prefetches.
Backport of c0a2b9508a
from master
This commit is contained in:
parent
fec151222e
commit
c679ac0449
|
@ -1471,10 +1471,15 @@ def prefetch_related_objects(model_instances, *related_lookups):
|
|||
# that we can continue with nullable or reverse relations.
|
||||
new_obj_list = []
|
||||
for obj in obj_list:
|
||||
try:
|
||||
new_obj = getattr(obj, through_attr)
|
||||
except exceptions.ObjectDoesNotExist:
|
||||
continue
|
||||
if through_attr in getattr(obj, '_prefetched_objects_cache', ()):
|
||||
# If related objects have been prefetched, use the
|
||||
# cache rather than the object's through_attr.
|
||||
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:
|
||||
continue
|
||||
# We special-case `list` rather than something more generic
|
||||
|
|
|
@ -91,3 +91,6 @@ Bugfixes
|
|||
|
||||
* Corrected the return type of ``ArrayField(CITextField())`` values retrieved
|
||||
from the database (:ticket:`28161`).
|
||||
|
||||
* Fixed ``QuerySet.prefetch_related()`` crash when fetching relations in nested
|
||||
``Prefetch`` objects (:ticket:`27554`).
|
||||
|
|
|
@ -1391,3 +1391,37 @@ class Ticket25546Tests(TestCase):
|
|||
self.assertListEqual(book1.first_authors[0].happy_place, [self.author1_address1])
|
||||
self.assertListEqual(book1.first_authors[1].happy_place, [])
|
||||
self.assertListEqual(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