Loading django/forms/fields.py +15 −1 Original line number Diff line number Diff line Loading @@ -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 = { Loading @@ -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) Loading docs/ref/forms/fields.txt +10 −4 Original line number Diff line number Diff line Loading @@ -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`` ~~~~~~~~~~~~~~~~~~~~ Loading docs/releases/1.8.txt +3 −0 Original line number Diff line number Diff line Loading @@ -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 ^^^^^^^^^^^^^ Loading tests/forms_tests/tests/test_fields.py +22 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading
django/forms/fields.py +15 −1 Original line number Diff line number Diff line Loading @@ -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 = { Loading @@ -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) Loading
docs/ref/forms/fields.txt +10 −4 Original line number Diff line number Diff line Loading @@ -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`` ~~~~~~~~~~~~~~~~~~~~ Loading
docs/releases/1.8.txt +3 −0 Original line number Diff line number Diff line Loading @@ -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 ^^^^^^^^^^^^^ Loading
tests/forms_tests/tests/test_fields.py +22 −0 Original line number Diff line number Diff line Loading @@ -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: Loading