Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #12398 -- Added a TypedMultipleChoiceField. Thanks to Tai Lee.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14829 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 4a1f2129d09658e705fbe0660275c6efccf1474a 1 parent ee48da2
@freakboy3742 freakboy3742 authored
View
30 django/forms/fields.py
@@ -40,7 +40,7 @@
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
- 'TypedChoiceField'
+ 'TypedChoiceField', 'TypedMultipleChoiceField'
)
def en_format(name):
@@ -700,7 +700,7 @@ def __init__(self, *args, **kwargs):
def to_python(self, value):
"""
- Validate that the value is in self.choices and can be coerced to the
+ Validates that the value is in self.choices and can be coerced to the
right type.
"""
value = super(TypedChoiceField, self).to_python(value)
@@ -742,6 +742,32 @@ def validate(self, value):
if not self.valid_value(val):
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
+class TypedMultipleChoiceField(MultipleChoiceField):
+ def __init__(self, *args, **kwargs):
+ self.coerce = kwargs.pop('coerce', lambda val: val)
+ self.empty_value = kwargs.pop('empty_value', [])
+ super(TypedMultipleChoiceField, self).__init__(*args, **kwargs)
+
+ def to_python(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)
+ super(TypedMultipleChoiceField, self).validate(value)
+ if value == self.empty_value or value in validators.EMPTY_VALUES:
+ return self.empty_value
+ new_value = []
+ for choice in value:
+ try:
+ new_value.append(self.coerce(choice))
+ except (ValueError, TypeError, ValidationError):
+ raise ValidationError(self.error_messages['invalid_choice'] % {'value': choice})
+ return new_value
+
+ def validate(self, value):
+ pass
+
class ComboField(Field):
"""
A Field whose clean() method calls multiple Field clean() methods.
View
29 docs/ref/forms/fields.txt
@@ -361,13 +361,14 @@ Takes one extra required argument:
.. class:: TypedChoiceField(**kwargs)
-Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes an
-extra ``coerce`` argument.
+Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes two
+extra arguments, ``coerce`` and ``empty_value``.
* Default widget: ``Select``
* Empty value: Whatever you've given as ``empty_value``
- * Normalizes to: the value returned by the ``coerce`` argument.
- * Validates that the given value exists in the list of choices.
+ * Normalizes to: A value of the type provided by the ``coerce`` argument.
+ * Validates that the given value exists in the list of choices and can be
+ coerced.
* Error message keys: ``required``, ``invalid_choice``
Takes extra arguments:
@@ -635,7 +636,25 @@ Takes two optional arguments for validation:
of choices.
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
-Takes one extra argument, ``choices``, as for ``ChoiceField``.
+Takes one extra required argument, ``choices``, as for ``ChoiceField``.
+
+``TypedMultipleChoiceField``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: TypedMultipleChoiceField(**kwargs)
+
+Just like a :class:`MultipleChoiceField`, except :class:`TypedMultipleChoiceField`
+takes two extra arguments, ``coerce`` and ``empty_value``.
+
+ * Default widget: ``SelectMultiple``
+ * Empty value: Whatever you've given as ``empty_value``
+ * Normalizes to: A list of values of the type provided by the ``coerce``
+ argument.
+ * Validates that the given values exists in the list of choices and can be
+ coerced.
+ * Error message keys: ``required``, ``invalid_choice``
+
+Takes two extra arguments, ``coerce`` and ``empty_value``, as for ``TypedChoiceField``.
``NullBooleanField``
~~~~~~~~~~~~~~~~~~~~
View
44 tests/regressiontests/forms/tests/fields.py
@@ -750,7 +750,49 @@ def test_multiplechoicefield_3(self):
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['6'])
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['1','6'])
- # ComboField ##################################################################
+ # TypedMultipleChoiceField ############################################################
+ # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
+ # will be returned:
+
+ def test_typedmultiplechoicefield_1(self):
+ f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
+ self.assertEqual([1], f.clean(['1']))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, ['2'])
+
+ def test_typedmultiplechoicefield_2(self):
+ # Different coercion, same validation.
+ f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float)
+ self.assertEqual([1.0], f.clean(['1']))
+
+ def test_typedmultiplechoicefield_3(self):
+ # This can also cause weirdness: be careful (bool(-1) == True, remember)
+ f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool)
+ self.assertEqual([True], f.clean(['-1']))
+
+ def test_typedmultiplechoicefield_4(self):
+ f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
+ self.assertEqual([1, -1], f.clean(['1','-1']))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, ['1','2'])
+
+ def test_typedmultiplechoicefield_5(self):
+ # Even more weirdness: if you have a valid choice but your coercion function
+ # can't coerce, you'll still get a validation error. Don't do this!
+ f = TypedMultipleChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. B is not one of the available choices.']", f.clean, ['B'])
+ # Required fields require values
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, [])
+
+ def test_typedmultiplechoicefield_6(self):
+ # Non-required fields aren't required
+ f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
+ self.assertEqual([], f.clean([]))
+
+ def test_typedmultiplechoicefield_7(self):
+ # If you want cleaning an empty value to return a different type, tell the field
+ f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
+ self.assertEqual(None, f.clean([]))
+
+ # ComboField ##################################################################
def test_combofield_1(self):
f = ComboField(fields=[CharField(max_length=20), EmailField()])
Please sign in to comment.
Something went wrong with that request. Please try again.