mirror of https://github.com/django/django.git
Fixed #21397 -- Re-added flexibility to TypedChoiceField coercion
Thanks Elec for the report and Simon Charette for the review.
This commit is contained in:
parent
4a00f132e0
commit
a0f3eeccf3
|
@ -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):
|
||||||
)
|
)
|
||||||
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):
|
||||||
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):
|
||||||
)
|
)
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
Loading…
Reference in New Issue