summaryrefslogtreecommitdiff
path: root/django/forms
diff options
context:
space:
mode:
authorNick Pope <nick@nickpope.me.uk>2023-08-31 02:57:40 +0100
committerGitHub <noreply@github.com>2023-08-30 22:57:40 -0300
commit500e01073adda32d5149624ee9a5cb7aa3d3583f (patch)
treef9416872a811aa39646deaf002414e0a7841b6d1 /django/forms
parent68a8996bdfce2d191decd7b1c1a2b9fdea8e4b2f (diff)
Fixed #31262 -- Added support for mappings on model fields and ChoiceField's choices.
Diffstat (limited to 'django/forms')
-rw-r--r--django/forms/fields.py32
-rw-r--r--django/forms/models.py5
-rw-r--r--django/forms/widgets.py14
3 files changed, 21 insertions, 30 deletions
diff --git a/django/forms/fields.py b/django/forms/fields.py
index bd226de543..3a0510ffc9 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -17,7 +17,6 @@ from urllib.parse import urlsplit, urlunsplit
from django.core import validators
from django.core.exceptions import ValidationError
-from django.db.models.enums import ChoicesMeta
from django.forms.boundfield import BoundField
from django.forms.utils import from_current_timezone, to_current_timezone
from django.forms.widgets import (
@@ -42,6 +41,7 @@ from django.forms.widgets import (
URLInput,
)
from django.utils import formats
+from django.utils.choices import normalize_choices
from django.utils.dateparse import parse_datetime, parse_duration
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.duration import duration_string
@@ -861,14 +861,6 @@ class NullBooleanField(BooleanField):
pass
-class CallableChoiceIterator:
- def __init__(self, choices_func):
- self.choices_func = choices_func
-
- def __iter__(self):
- yield from self.choices_func()
-
-
class ChoiceField(Field):
widget = Select
default_error_messages = {
@@ -879,8 +871,6 @@ class ChoiceField(Field):
def __init__(self, *, choices=(), **kwargs):
super().__init__(**kwargs)
- if isinstance(choices, ChoicesMeta):
- choices = choices.choices
self.choices = choices
def __deepcopy__(self, memo):
@@ -888,21 +878,15 @@ class ChoiceField(Field):
result._choices = copy.deepcopy(self._choices, memo)
return result
- def _get_choices(self):
+ @property
+ def choices(self):
return self._choices
- def _set_choices(self, value):
- # Setting choices also sets the choices on the widget.
- # choices can be any iterable, but we call list() on it because
- # it will be consumed more than once.
- if callable(value):
- value = CallableChoiceIterator(value)
- else:
- value = list(value)
-
- self._choices = self.widget.choices = value
-
- choices = property(_get_choices, _set_choices)
+ @choices.setter
+ def choices(self, value):
+ # Setting choices on the field also sets the choices on the widget.
+ # Note that the property setter for the widget will re-normalize.
+ self._choices = self.widget.choices = normalize_choices(value)
def to_python(self, value):
"""Return a string."""
diff --git a/django/forms/models.py b/django/forms/models.py
index dc30d79b5d..d353da4ddc 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -21,6 +21,7 @@ from django.forms.widgets import (
RadioSelect,
SelectMultiple,
)
+from django.utils.choices import ChoiceIterator
from django.utils.text import capfirst, get_text_list
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
@@ -1402,7 +1403,7 @@ class ModelChoiceIteratorValue:
return self.value == other
-class ModelChoiceIterator:
+class ModelChoiceIterator(ChoiceIterator):
def __init__(self, field):
self.field = field
self.queryset = field.queryset
@@ -1532,7 +1533,7 @@ class ModelChoiceField(ChoiceField):
# the queryset.
return self.iterator(self)
- choices = property(_get_choices, ChoiceField._set_choices)
+ choices = property(_get_choices, ChoiceField.choices.fset)
def prepare_value(self, value):
if hasattr(value, "_meta"):
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index ab7c0f755f..2c734052d5 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -12,6 +12,7 @@ from itertools import chain
from django.forms.utils import to_current_timezone
from django.templatetags.static import static
from django.utils import formats
+from django.utils.choices import normalize_choices
from django.utils.dates import MONTHS
from django.utils.formats import get_format
from django.utils.html import format_html, html_safe
@@ -620,10 +621,7 @@ class ChoiceWidget(Widget):
def __init__(self, attrs=None, choices=()):
super().__init__(attrs)
- # choices can be any iterable, but we may need to render this widget
- # multiple times. Thus, collapse it into a list so it can be consumed
- # more than once.
- self.choices = list(choices)
+ self.choices = choices
def __deepcopy__(self, memo):
obj = copy.copy(self)
@@ -741,6 +739,14 @@ class ChoiceWidget(Widget):
value = [value]
return [str(v) if v is not None else "" for v in value]
+ @property
+ def choices(self):
+ return self._choices
+
+ @choices.setter
+ def choices(self, value):
+ self._choices = normalize_choices(value)
+
class Select(ChoiceWidget):
input_type = "select"