summaryrefslogtreecommitdiff
path: root/django/forms/models.py
diff options
context:
space:
mode:
authorCharles Roelli <charles@aurox.ch>2026-05-01 13:45:01 +0000
committerCharles Roelli <charles@aurox.ch>2026-05-01 13:45:01 +0000
commit70b0be281caa08a0b639acb73e3b9fe5f6e7d278 (patch)
tree52f99149db13ccf9af6ad6d91d06110725b7224a /django/forms/models.py
parent9f790ef1a0f356cf6342b5d57bbaeac35aed0d9f (diff)
Fixed #31295 -- Avoid Select widget triggering additional query when using ModelChoiceIterator.fix-31295
Diffstat (limited to 'django/forms/models.py')
-rw-r--r--django/forms/models.py41
1 files changed, 32 insertions, 9 deletions
diff --git a/django/forms/models.py b/django/forms/models.py
index 9686baa6f2..ac56a4df78 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -3,6 +3,7 @@ Helper functions for creating Form classes from Django models
and database field objects.
"""
+from collections import deque
from itertools import chain
from django.core.exceptions import (
@@ -1438,16 +1439,30 @@ class ModelChoiceIterator(BaseChoiceIterator):
def __init__(self, field):
self.field = field
self.queryset = field.queryset
+ self._deque = deque()
+
+ def generator():
+ if self.field.empty_label is not None:
+ yield ("", self.field.empty_label)
+ queryset = self.queryset
+ # Can't use iterator() when queryset uses prefetch_related()
+ if not queryset._prefetch_related_lookups:
+ queryset = queryset.iterator()
+ for obj in iter(queryset):
+ yield self.choice(obj)
+ # Reset the generator after it's exhausted so that it can
+ # be used again.
+ self.generator = generator()
+
+ self.generator = generator()
def __iter__(self):
- if self.field.empty_label is not None:
- yield ("", self.field.empty_label)
- queryset = self.queryset
- # Can't use iterator() when queryset uses prefetch_related()
- if not queryset._prefetch_related_lookups:
- queryset = queryset.iterator()
- for obj in queryset:
- yield self.choice(obj)
+ return self
+
+ def __next__(self):
+ if self._deque:
+ return self._deque.popleft()
+ return next(self.generator)
def __len__(self):
# count() adds a query but uses less memory since the QuerySet results
@@ -1456,7 +1471,9 @@ class ModelChoiceIterator(BaseChoiceIterator):
return self.queryset.count() + (1 if self.field.empty_label is not None else 0)
def __bool__(self):
- return self.field.empty_label is not None or self.queryset.exists()
+ return (
+ self.field.empty_label is not None or self._deque or self.queryset.exists()
+ )
def choice(self, obj):
return (
@@ -1464,6 +1481,12 @@ class ModelChoiceIterator(BaseChoiceIterator):
self.field.label_from_instance(obj),
)
+ def peek(self):
+ value = next(self.generator, None)
+ if value is not None:
+ self._deque.append(value)
+ return value
+
class ModelChoiceField(ChoiceField):
"""A ChoiceField whose choices are a model QuerySet."""