summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2013-11-18 18:24:56 +0100
committerClaude Paroz <claude@2xlibre.net>2013-11-18 18:24:56 +0100
commita0f3eeccf3d5a90f9914bfe20b15df05673ea59d (patch)
treeda91c208646cdef2a8f1dd535ab5bcd07d566a52
parent4a00f132e0cc5246f7e7bd04b6d84a9d9ea4a0c1 (diff)
Fixed #21397 -- Re-added flexibility to TypedChoiceField coercion
Thanks Elec for the report and Simon Charette for the review.
-rw-r--r--django/forms/fields.py17
-rw-r--r--docs/ref/forms/fields.txt4
-rw-r--r--docs/releases/1.7.txt4
-rw-r--r--tests/forms_tests/tests/test_fields.py33
4 files changed, 51 insertions, 7 deletions
diff --git a/django/forms/fields.py b/django/forms/fields.py
index c95930a2d0..56d0e316f2 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -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)
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index 81b65d04d4..f2cd122526 100644
--- a/docs/ref/forms/fields.txt
+++ b/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
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 2a2fd30363..8428c1853a 100644
--- a/docs/releases/1.7.txt
+++ b/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.
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index a7e124a50c..fe11370bde 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -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):