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:
parent
ee48da2405
commit
4a1f2129d0
|
@ -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.
|
||||||
|
|
|
@ -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``
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -750,6 +750,48 @@ 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'])
|
||||||
|
|
||||||
|
# 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 ##################################################################
|
# ComboField ##################################################################
|
||||||
|
|
||||||
def test_combofield_1(self):
|
def test_combofield_1(self):
|
||||||
|
|
Loading…
Reference in New Issue