Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

Thanks Elec for the report and Simon Charette for the review.
  • Loading branch information...
commit a0f3eeccf3d5a90f9914bfe20b15df05673ea59d 1 parent 4a00f13
@claudep claudep authored
View
17 django/forms/fields.py
@@ -822,12 +822,10 @@ def __init__(self, *args, **kwargs):
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 @@ def to_python(self, value):
)
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 @@ def __init__(self, *args, **kwargs):
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 @@ def to_python(self, value):
)
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)
View
4 docs/ref/forms/fields.txt
@@ -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
View
4 docs/releases/1.7.txt
@@ -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.
View
33 tests/forms_tests/tests/test_fields.py
@@ -956,6 +956,22 @@ def test_typedchoicefield_has_changed(self):
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 @@ def test_typedmultiplechoicefield_has_changed(self):
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):
Please sign in to comment.
Something went wrong with that request. Please try again.