summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Inglesby <peter.inglesby@gmail.com>2014-10-27 20:21:59 +0000
committerTim Graham <timograham@gmail.com>2014-11-04 11:23:58 -0500
commit74e1980cf96eb45079bef464fabdcbe0a6db2423 (patch)
tree0541e693623c87c9789f1beb37d7f5831426c74b
parente0685368c6b122404cf0817d5cb17ab1b211cf6d (diff)
Fixed #13181 -- Added support for callable choices to forms.ChoiceField
Thanks vanschelven and expleo for the initial patch.
-rw-r--r--django/forms/fields.py16
-rw-r--r--docs/ref/forms/fields.txt14
-rw-r--r--docs/releases/1.8.txt3
-rw-r--r--tests/forms_tests/tests/test_fields.py22
4 files changed, 50 insertions, 5 deletions
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 6403509fbb..81b3e2f4f9 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -798,6 +798,15 @@ class NullBooleanField(BooleanField):
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):
widget = Select
default_error_messages = {
@@ -822,7 +831,12 @@ class ChoiceField(Field):
# 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.
- 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)
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index d7fcef2e75..070ccdf7a8 100644
--- a/docs/ref/forms/fields.txt
+++ b/docs/ref/forms/fields.txt
@@ -387,10 +387,16 @@ For each field, we describe the default widget used if you don't specify
.. attribute:: choices
- An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
- field. This argument accepts the same formats as the ``choices`` argument
- to a model field. See the :ref:`model field reference documentation on
- choices <field-choices>` for more details.
+ Either an iterable (e.g., a list or tuple) of 2-tuples to use as
+ choices for this field, or a callable that returns such an iterable.
+ This argument accepts the same formats as the ``choices`` argument to a
+ 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``
~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index da2e5ae466..d5bae559a7 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -240,6 +240,9 @@ Forms
will also update ``UploadedFile.content_type`` with the image's content type
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
^^^^^^^^^^^^^
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index 22af2cf23d..3caf902349 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -961,6 +961,28 @@ class FieldsTests(SimpleTestCase):
self.assertEqual('5', f.clean('5'))
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 is just like ChoiceField, except that coerced types will
# be returned: