summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorblingblin-g <luvbliny@gmail.com>2025-07-31 00:03:27 +0900
committerJacob Walls <jacobtylerwalls@gmail.com>2025-09-16 14:14:23 -0400
commite08fa42fa6d0e9f2a74e8fcdc5a47f5c3b825877 (patch)
treee0073b0fa22279f46b3156a5397760cb4f9d2458
parente059bbec96ed85d5f4d63893d687c8af966430c8 (diff)
Fixed #36426 -- Added support for further iterables in prefetch_related_objects().
Thanks Sarah Boyce for the review.
-rw-r--r--django/db/models/query.py6
-rw-r--r--docs/ref/models/querysets.txt5
-rw-r--r--tests/prefetch_related/test_prefetch_related_objects.py31
3 files changed, 37 insertions, 5 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 2359ee3bb4..0de5787f42 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -2333,8 +2333,8 @@ def normalize_prefetch_lookups(lookups, prefix=None):
def prefetch_related_objects(model_instances, *related_lookups):
"""
- Populate prefetched object caches for a list of model instances based on
- the lookups/Prefetch instances given.
+ Populate prefetched object caches for an iterable of model instances based
+ on the lookups/Prefetch instances given.
"""
if not model_instances:
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
# premise of prefetch_related), so what applies to first object
# applies to all.
- first_obj = obj_list[0]
+ first_obj = next(iter(obj_list))
to_attr = lookup.get_current_to_attr(level)[0]
prefetcher, descriptor, attr_found, is_fetched = get_prefetcher(
first_obj, through_attr, to_attr
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index baeb3e8746..59550e6690 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -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``;
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
-lookups or :class:`Prefetch` objects you want to prefetch for. For example:
+Pass an iterable of model instances (must all be of the same class and able to
+be iterated multiple times) and the lookups or :class:`Prefetch` objects you
+want to prefetch for. For example:
.. code-block:: pycon
diff --git a/tests/prefetch_related/test_prefetch_related_objects.py b/tests/prefetch_related/test_prefetch_related_objects.py
index eea9a7fff7..20f620417a 100644
--- a/tests/prefetch_related/test_prefetch_related_objects.py
+++ b/tests/prefetch_related/test_prefetch_related_objects.py
@@ -1,3 +1,5 @@
+from collections import deque
+
from django.db.models import Prefetch, prefetch_related_objects
from django.test import TestCase
@@ -221,3 +223,32 @@ class PrefetchRelatedObjectsTests(TestCase):
with self.assertNumQueries(0):
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")