Fixed #13181 -- Added support for callable choices to forms.ChoiceField
Thanks vanschelven and expleo for the initial patch.
This commit is contained in:
parent
e0685368c6
commit
74e1980cf9
|
@ -798,6 +798,15 @@ class NullBooleanField(BooleanField):
|
||||||
return initial != data
|
return initial != data
|
||||||
|
|
||||||
|
|
||||||
|
class CallableChoiceIterator(object):
|
||||||
|
def __init__(self, choices_func):
|
||||||
|
self.choices_func = choices_func
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for e in self.choices_func():
|
||||||
|
yield e
|
||||||
|
|
||||||
|
|
||||||
class ChoiceField(Field):
|
class ChoiceField(Field):
|
||||||
widget = Select
|
widget = Select
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
@ -822,7 +831,12 @@ class ChoiceField(Field):
|
||||||
# Setting choices also sets the choices on the widget.
|
# Setting choices also sets the choices on the widget.
|
||||||
# choices can be any iterable, but we call list() on it because
|
# choices can be any iterable, but we call list() on it because
|
||||||
# it will be consumed more than once.
|
# it will be consumed more than once.
|
||||||
self._choices = self.widget.choices = list(value)
|
if callable(value):
|
||||||
|
value = CallableChoiceIterator(value)
|
||||||
|
else:
|
||||||
|
value = list(value)
|
||||||
|
|
||||||
|
self._choices = self.widget.choices = value
|
||||||
|
|
||||||
choices = property(_get_choices, _set_choices)
|
choices = property(_get_choices, _set_choices)
|
||||||
|
|
||||||
|
|
|
@ -387,10 +387,16 @@ For each field, we describe the default widget used if you don't specify
|
||||||
|
|
||||||
.. attribute:: choices
|
.. attribute:: choices
|
||||||
|
|
||||||
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
|
Either an iterable (e.g., a list or tuple) of 2-tuples to use as
|
||||||
field. This argument accepts the same formats as the ``choices`` argument
|
choices for this field, or a callable that returns such an iterable.
|
||||||
to a model field. See the :ref:`model field reference documentation on
|
This argument accepts the same formats as the ``choices`` argument to a
|
||||||
choices <field-choices>` for more details.
|
model field. See the :ref:`model field reference documentation on
|
||||||
|
choices <field-choices>` for more details. If the argument is a
|
||||||
|
callable, it is evaluated each time the field's form is initialized.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8
|
||||||
|
|
||||||
|
The ability to pass a callable to ``choices`` was added.
|
||||||
|
|
||||||
``TypedChoiceField``
|
``TypedChoiceField``
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -240,6 +240,9 @@ Forms
|
||||||
will also update ``UploadedFile.content_type`` with the image's content type
|
will also update ``UploadedFile.content_type`` with the image's content type
|
||||||
as determined by Pillow.
|
as determined by Pillow.
|
||||||
|
|
||||||
|
* You can now pass a callable that returns an iterable of choices when
|
||||||
|
instantiating a :class:`~django.forms.ChoiceField`.
|
||||||
|
|
||||||
Generic Views
|
Generic Views
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -961,6 +961,28 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertEqual('5', f.clean('5'))
|
self.assertEqual('5', f.clean('5'))
|
||||||
self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, '6')
|
self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, '6')
|
||||||
|
|
||||||
|
def test_choicefield_callable(self):
|
||||||
|
choices = lambda: [('J', 'John'), ('P', 'Paul')]
|
||||||
|
f = ChoiceField(choices=choices)
|
||||||
|
self.assertEqual('J', f.clean('J'))
|
||||||
|
|
||||||
|
def test_choicefield_callable_may_evaluate_to_different_values(self):
|
||||||
|
choices = []
|
||||||
|
|
||||||
|
def choices_as_callable():
|
||||||
|
return choices
|
||||||
|
|
||||||
|
class ChoiceFieldForm(Form):
|
||||||
|
choicefield = ChoiceField(choices=choices_as_callable)
|
||||||
|
|
||||||
|
choices = [('J', 'John')]
|
||||||
|
form = ChoiceFieldForm()
|
||||||
|
self.assertEqual([('J', 'John')], list(form.fields['choicefield'].choices))
|
||||||
|
|
||||||
|
choices = [('P', 'Paul')]
|
||||||
|
form = ChoiceFieldForm()
|
||||||
|
self.assertEqual([('P', 'Paul')], list(form.fields['choicefield'].choices))
|
||||||
|
|
||||||
# TypedChoiceField ############################################################
|
# TypedChoiceField ############################################################
|
||||||
# TypedChoiceField is just like ChoiceField, except that coerced types will
|
# TypedChoiceField is just like ChoiceField, except that coerced types will
|
||||||
# be returned:
|
# be returned:
|
||||||
|
|
Loading…
Reference in New Issue