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
This commit is contained in:
Russell Keith-Magee 2010-12-05 04:47:19 +00:00
parent ee48da2405
commit 4a1f2129d0
3 changed files with 95 additions and 8 deletions

View File

@ -40,7 +40,7 @@ __all__ = (
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField', 'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
'TypedChoiceField' 'TypedChoiceField', 'TypedMultipleChoiceField'
) )
def en_format(name): def en_format(name):
@ -700,7 +700,7 @@ class TypedChoiceField(ChoiceField):
def to_python(self, value): 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. right type.
""" """
value = super(TypedChoiceField, self).to_python(value) value = super(TypedChoiceField, self).to_python(value)
@ -742,6 +742,32 @@ class MultipleChoiceField(ChoiceField):
if not self.valid_value(val): if not self.valid_value(val):
raise ValidationError(self.error_messages['invalid_choice'] % {'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): class ComboField(Field):
""" """
A Field whose clean() method calls multiple Field clean() methods. A Field whose clean() method calls multiple Field clean() methods.

View File

@ -361,13 +361,14 @@ Takes one extra required argument:
.. class:: TypedChoiceField(**kwargs) .. class:: TypedChoiceField(**kwargs)
Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes an Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes two
extra ``coerce`` argument. extra arguments, ``coerce`` and ``empty_value``.
* Default widget: ``Select`` * Default widget: ``Select``
* Empty value: Whatever you've given as ``empty_value`` * Empty value: Whatever you've given as ``empty_value``
* Normalizes to: the value returned by the ``coerce`` argument. * Normalizes to: A value of the type provided by the ``coerce`` argument.
* Validates that the given value exists in the list of choices. * Validates that the given value exists in the list of choices and can be
coerced.
* Error message keys: ``required``, ``invalid_choice`` * Error message keys: ``required``, ``invalid_choice``
Takes extra arguments: Takes extra arguments:
@ -635,7 +636,25 @@ Takes two optional arguments for validation:
of choices. of choices.
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list`` * 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`` ``NullBooleanField``
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View File

@ -750,7 +750,49 @@ class FieldsTests(TestCase):
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, ['6'])
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['1','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): def test_combofield_1(self):
f = ComboField(fields=[CharField(max_length=20), EmailField()]) f = ComboField(fields=[CharField(max_length=20), EmailField()])