Loading django/forms/fields.py +11 −6 Original line number Diff line number Diff line Loading @@ -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: Loading @@ -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 Loading Loading @@ -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 = [] Loading @@ -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) Loading docs/ref/forms/fields.txt +3 −1 Original line number Diff line number Diff line Loading @@ -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 Loading docs/releases/1.7.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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. Loading tests/forms_tests/tests/test_fields.py +33 −0 Original line number Diff line number Diff line Loading @@ -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): Loading Loading @@ -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): Loading Loading
django/forms/fields.py +11 −6 Original line number Diff line number Diff line Loading @@ -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: Loading @@ -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 Loading Loading @@ -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 = [] Loading @@ -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) Loading
docs/ref/forms/fields.txt +3 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
docs/releases/1.7.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
tests/forms_tests/tests/test_fields.py +33 −0 Original line number Diff line number Diff line Loading @@ -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): Loading Loading @@ -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): Loading