Commit 74e1980c authored by Peter Inglesby's avatar Peter Inglesby Committed by Tim Graham
Browse files

Fixed #13181 -- Added support for callable choices to forms.ChoiceField

Thanks vanschelven and expleo for the initial patch.
parent e0685368
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -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)

+10 −4
Original line number Diff line number Diff line
@@ -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``
~~~~~~~~~~~~~~~~~~~~
+3 −0
Original line number Diff line number Diff line
@@ -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
^^^^^^^^^^^^^

+22 −0
Original line number Diff line number Diff line
@@ -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: