Kaydet (Commit) a0f3eecc authored tarafından Claude Paroz's avatar Claude Paroz

Fixed #21397 -- Re-added flexibility to TypedChoiceField coercion

Thanks Elec for the report and Simon Charette for the review.
üst 4a00f132
......@@ -822,12 +822,10 @@ class TypedChoiceField(ChoiceField):
self.empty_value = kwargs.pop('empty_value', '')
super(TypedChoiceField, self).__init__(*args, **kwargs)
def to_python(self, value):
def _coerce(self, value):
"""
Validates that the value is in self.choices and can be coerced to the
right type.
Validate that the value can be coerced to the right type (if not empty).
"""
value = super(TypedChoiceField, self).to_python(value)
if value == self.empty_value or value in self.empty_values:
return self.empty_value
try:
......@@ -840,6 +838,10 @@ class TypedChoiceField(ChoiceField):
)
return value
def clean(self, value):
value = super(TypedChoiceField, self).clean(value)
return self._coerce(value)
class MultipleChoiceField(ChoiceField):
hidden_widget = MultipleHiddenInput
......@@ -889,12 +891,11 @@ class TypedMultipleChoiceField(MultipleChoiceField):
self.empty_value = kwargs.pop('empty_value', [])
super(TypedMultipleChoiceField, self).__init__(*args, **kwargs)
def to_python(self, value):
def _coerce(self, value):
"""
Validates that the values are in self.choices and can be coerced to the
right type.
"""
value = super(TypedMultipleChoiceField, self).to_python(value)
if value == self.empty_value or value in self.empty_values:
return self.empty_value
new_value = []
......@@ -909,6 +910,10 @@ class TypedMultipleChoiceField(MultipleChoiceField):
)
return new_value
def clean(self, value):
value = super(TypedMultipleChoiceField, self).clean(value)
return self._coerce(value)
def validate(self, value):
if value != self.empty_value:
super(TypedMultipleChoiceField, self).validate(value)
......
......@@ -375,7 +375,9 @@ For each field, we describe the default widget used if you don't specify
A function that takes one argument and returns a coerced value. Examples
include the built-in ``int``, ``float``, ``bool`` and other types. Defaults
to an identity function.
to an identity function. Note that coercion happens after input
validation, so it is possible to coerce to a value not present in
``choices``.
.. attribute:: empty_value
......
......@@ -317,6 +317,10 @@ Forms
return ``self.cleaned_data``. If it does return a changed dictionary then
that will still be used.
* After a temporary regression in Django 1.6, it's now possible again to make
:class:`~django.forms.TypedChoiceField` ``coerce`` method return an arbitrary
value.
* :attr:`SelectDateWidget.months
<django.forms.extras.widgets.SelectDateWidget.months>` can be used to
customize the wording of the months displayed in the select widget.
......
......@@ -956,6 +956,22 @@ class FieldsTests(SimpleTestCase):
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, ''))
def test_typedchoicefield_special_coerce(self):
"""
Test a coerce function which results in a value not present in choices.
Refs #21397.
"""
def coerce_func(val):
return Decimal('1.%s' % val)
f = TypedChoiceField(choices=[(1, "1"), (2, "2")], coerce=coerce_func, required=True)
self.assertEqual(Decimal('1.2'), f.clean('2'))
self.assertRaisesMessage(ValidationError,
"'This field is required.'", f.clean, '')
self.assertRaisesMessage(ValidationError,
"'Select a valid choice. 3 is not one of the available choices.'",
f.clean, '3')
# NullBooleanField ############################################################
def test_nullbooleanfield_1(self):
......@@ -1110,6 +1126,23 @@ class FieldsTests(SimpleTestCase):
f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, ''))
def test_typedmultiplechoicefield_special_coerce(self):
"""
Test a coerce function which results in a value not present in choices.
Refs #21397.
"""
def coerce_func(val):
return Decimal('1.%s' % val)
f = TypedMultipleChoiceField(
choices=[(1, "1"), (2, "2")], coerce=coerce_func, required=True)
self.assertEqual([Decimal('1.2')], f.clean(['2']))
self.assertRaisesMessage(ValidationError,
"'This field is required.'", f.clean, [])
self.assertRaisesMessage(ValidationError,
"'Select a valid choice. 3 is not one of the available choices.'",
f.clean, ['3'])
# ComboField ##################################################################
def test_combofield_1(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment