Kaydet (Commit) 4a1f2129 authored tarafından Russell Keith-Magee's avatar Russell Keith-Magee

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
üst ee48da24
...@@ -40,7 +40,7 @@ __all__ = ( ...@@ -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): ...@@ -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): ...@@ -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: ...@@ -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: ...@@ -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,7 +750,49 @@ class FieldsTests(TestCase): ...@@ -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()])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment