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): ...@@ -822,12 +822,10 @@ class TypedChoiceField(ChoiceField):
self.empty_value = kwargs.pop('empty_value', '') self.empty_value = kwargs.pop('empty_value', '')
super(TypedChoiceField, self).__init__(*args, **kwargs) 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 Validate that the value can be coerced to the right type (if not empty).
right type.
""" """
value = super(TypedChoiceField, self).to_python(value)
if value == self.empty_value or value in self.empty_values: if value == self.empty_value or value in self.empty_values:
return self.empty_value return self.empty_value
try: try:
...@@ -840,6 +838,10 @@ class TypedChoiceField(ChoiceField): ...@@ -840,6 +838,10 @@ class TypedChoiceField(ChoiceField):
) )
return value return value
def clean(self, value):
value = super(TypedChoiceField, self).clean(value)
return self._coerce(value)
class MultipleChoiceField(ChoiceField): class MultipleChoiceField(ChoiceField):
hidden_widget = MultipleHiddenInput hidden_widget = MultipleHiddenInput
...@@ -889,12 +891,11 @@ class TypedMultipleChoiceField(MultipleChoiceField): ...@@ -889,12 +891,11 @@ class TypedMultipleChoiceField(MultipleChoiceField):
self.empty_value = kwargs.pop('empty_value', []) self.empty_value = kwargs.pop('empty_value', [])
super(TypedMultipleChoiceField, self).__init__(*args, **kwargs) 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 Validates that the values are in self.choices and can be coerced to the
right type. right type.
""" """
value = super(TypedMultipleChoiceField, self).to_python(value)
if value == self.empty_value or value in self.empty_values: if value == self.empty_value or value in self.empty_values:
return self.empty_value return self.empty_value
new_value = [] new_value = []
...@@ -909,6 +910,10 @@ class TypedMultipleChoiceField(MultipleChoiceField): ...@@ -909,6 +910,10 @@ class TypedMultipleChoiceField(MultipleChoiceField):
) )
return new_value return new_value
def clean(self, value):
value = super(TypedMultipleChoiceField, self).clean(value)
return self._coerce(value)
def validate(self, value): def validate(self, value):
if value != self.empty_value: if value != self.empty_value:
super(TypedMultipleChoiceField, self).validate(value) super(TypedMultipleChoiceField, self).validate(value)
......
...@@ -375,7 +375,9 @@ For each field, we describe the default widget used if you don't specify ...@@ -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 A function that takes one argument and returns a coerced value. Examples
include the built-in ``int``, ``float``, ``bool`` and other types. Defaults 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 .. attribute:: empty_value
......
...@@ -317,6 +317,10 @@ Forms ...@@ -317,6 +317,10 @@ Forms
return ``self.cleaned_data``. If it does return a changed dictionary then return ``self.cleaned_data``. If it does return a changed dictionary then
that will still be used. 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 * :attr:`SelectDateWidget.months
<django.forms.extras.widgets.SelectDateWidget.months>` can be used to <django.forms.extras.widgets.SelectDateWidget.months>` can be used to
customize the wording of the months displayed in the select widget. customize the wording of the months displayed in the select widget.
......
...@@ -956,6 +956,22 @@ class FieldsTests(SimpleTestCase): ...@@ -956,6 +956,22 @@ class FieldsTests(SimpleTestCase):
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, '')) 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 ############################################################ # NullBooleanField ############################################################
def test_nullbooleanfield_1(self): def test_nullbooleanfield_1(self):
...@@ -1110,6 +1126,23 @@ class FieldsTests(SimpleTestCase): ...@@ -1110,6 +1126,23 @@ class FieldsTests(SimpleTestCase):
f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, '')) 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 ################################################################## # ComboField ##################################################################
def test_combofield_1(self): 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