1
0
mirror of https://github.com/django/django.git synced 2025-10-10 15:29:11 +00:00

Fixed #36426 -- Added support for further iterables in prefetch_related_objects().

Thanks Sarah Boyce for the review.
This commit is contained in:
blingblin-g 2025-07-31 00:03:27 +09:00 committed by Jacob Walls
parent e059bbec96
commit e08fa42fa6
3 changed files with 37 additions and 5 deletions

View File

@ -2333,8 +2333,8 @@ def normalize_prefetch_lookups(lookups, prefix=None):
def prefetch_related_objects(model_instances, *related_lookups): def prefetch_related_objects(model_instances, *related_lookups):
""" """
Populate prefetched object caches for a list of model instances based on Populate prefetched object caches for an iterable of model instances based
the lookups/Prefetch instances given. on the lookups/Prefetch instances given.
""" """
if not model_instances: if not model_instances:
return # nothing to do return # nothing to do
@ -2402,7 +2402,7 @@ def prefetch_related_objects(model_instances, *related_lookups):
# We assume that objects retrieved are homogeneous (which is the # We assume that objects retrieved are homogeneous (which is the
# premise of prefetch_related), so what applies to first object # premise of prefetch_related), so what applies to first object
# applies to all. # applies to all.
first_obj = obj_list[0] first_obj = next(iter(obj_list))
to_attr = lookup.get_current_to_attr(level)[0] to_attr = lookup.get_current_to_attr(level)[0]
prefetcher, descriptor, attr_found, is_fetched = get_prefetcher( prefetcher, descriptor, attr_found, is_fetched = get_prefetcher(
first_obj, through_attr, to_attr first_obj, through_attr, to_attr

View File

@ -4223,8 +4223,9 @@ Prefetches the given lookups on an iterable of model instances. This is useful
in code that receives a list of model instances as opposed to a ``QuerySet``; in code that receives a list of model instances as opposed to a ``QuerySet``;
for example, when fetching models from a cache or instantiating them manually. for example, when fetching models from a cache or instantiating them manually.
Pass an iterable of model instances (must all be of the same class) and the Pass an iterable of model instances (must all be of the same class and able to
lookups or :class:`Prefetch` objects you want to prefetch for. For example: be iterated multiple times) and the lookups or :class:`Prefetch` objects you
want to prefetch for. For example:
.. code-block:: pycon .. code-block:: pycon

View File

@ -1,3 +1,5 @@
from collections import deque
from django.db.models import Prefetch, prefetch_related_objects from django.db.models import Prefetch, prefetch_related_objects
from django.test import TestCase from django.test import TestCase
@ -221,3 +223,32 @@ class PrefetchRelatedObjectsTests(TestCase):
with self.assertNumQueries(0): with self.assertNumQueries(0):
self.assertCountEqual(book1.authors.all(), [self.author1, self.author2]) self.assertCountEqual(book1.authors.all(), [self.author1, self.author2])
def test_prefetch_related_objects_with_various_iterables(self):
book = self.book1
class MyIterable:
def __iter__(self):
yield book
cases = {
"set": {book},
"tuple": (book,),
"dict_values": {"a": book}.values(),
"frozenset": frozenset([book]),
"deque": deque([book]),
"custom iterator": MyIterable(),
}
for case_type, case in cases.items():
with self.subTest(case=case_type):
# Clear the prefetch cache.
book._prefetched_objects_cache = {}
with self.assertNumQueries(1):
prefetch_related_objects(case, "authors")
with self.assertNumQueries(0):
self.assertCountEqual(
book.authors.all(), [self.author1, self.author2, self.author3]
)
def test_prefetch_related_objects_empty(self):
prefetch_related_objects([], "authors")