Added a `TypedChoiceField` which acts just like `ChoiceField`, except that it
returns a value coerced by some provided function. Refs #6967. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8771 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
3e71a684b3
commit
4ae746b574
|
@ -23,6 +23,7 @@ try:
|
||||||
except NameError:
|
except NameError:
|
||||||
from sets import Set as set
|
from sets import Set as set
|
||||||
|
|
||||||
|
import django.core.exceptions
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.encoding import smart_unicode, smart_str
|
from django.utils.encoding import smart_unicode, smart_str
|
||||||
|
|
||||||
|
@ -39,6 +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'
|
||||||
)
|
)
|
||||||
|
|
||||||
# These values, if given to to_python(), will trigger the self.required check.
|
# These values, if given to to_python(), will trigger the self.required check.
|
||||||
|
@ -657,6 +659,33 @@ class ChoiceField(Field):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
class TypedChoiceField(ChoiceField):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.coerce = kwargs.pop('coerce', lambda val: val)
|
||||||
|
self.empty_value = kwargs.pop('empty_value', '')
|
||||||
|
super(TypedChoiceField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
"""
|
||||||
|
Validate that the value is in self.choices and can be coerced to the
|
||||||
|
right type.
|
||||||
|
"""
|
||||||
|
value = super(TypedChoiceField, self).clean(value)
|
||||||
|
if value == self.empty_value or value in EMPTY_VALUES:
|
||||||
|
return self.empty_value
|
||||||
|
|
||||||
|
# Hack alert: This field is purpose-made to use with Field.to_python as
|
||||||
|
# a coercion function so that ModelForms with choices work. However,
|
||||||
|
# Django's Field.to_python raises django.core.exceptions.ValidationError,
|
||||||
|
# which is a *different* exception than
|
||||||
|
# django.forms.utils.ValidationError. So unfortunatly we need to catch
|
||||||
|
# both.
|
||||||
|
try:
|
||||||
|
value = self.coerce(value)
|
||||||
|
except (ValueError, TypeError, django.core.exceptions.ValidationError):
|
||||||
|
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
|
||||||
|
return value
|
||||||
|
|
||||||
class MultipleChoiceField(ChoiceField):
|
class MultipleChoiceField(ChoiceField):
|
||||||
hidden_widget = MultipleHiddenInput
|
hidden_widget = MultipleHiddenInput
|
||||||
widget = SelectMultiple
|
widget = SelectMultiple
|
||||||
|
|
|
@ -363,6 +363,33 @@ Takes one extra required argument:
|
||||||
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
|
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
|
||||||
field.
|
field.
|
||||||
|
|
||||||
|
``TypedChoiceField``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: TypedChoiceField(**kwargs)
|
||||||
|
|
||||||
|
Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes an
|
||||||
|
extra ``coerce`` argument.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
* Error message keys: ``required``, ``invalid_choice``
|
||||||
|
|
||||||
|
Takes extra arguments:
|
||||||
|
|
||||||
|
.. attribute:: TypedChoiceField.coerce
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. attribute:: TypedChoiceField.empty_value
|
||||||
|
|
||||||
|
The value to use to represent "empty." Defaults to the empty string;
|
||||||
|
``None`` is another common choice here.
|
||||||
|
|
||||||
``DateField``
|
``DateField``
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -1077,6 +1077,53 @@ Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
|
||||||
|
|
||||||
|
# TypedChoiceField ############################################################
|
||||||
|
|
||||||
|
# TypedChoiceField is just like ChoiceField, except that coerced types will
|
||||||
|
# be returned:
|
||||||
|
>>> f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
|
||||||
|
>>> f.clean('1')
|
||||||
|
1
|
||||||
|
>>> f.clean('2')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'Select a valid choice. 2 is not one of the available choices.']
|
||||||
|
|
||||||
|
# Different coercion, same validation.
|
||||||
|
>>> f.coerce = float
|
||||||
|
>>> f.clean('1')
|
||||||
|
1.0
|
||||||
|
|
||||||
|
|
||||||
|
# This can also cause weirdness: be careful (bool(-1) == True, remember)
|
||||||
|
>>> f.coerce = bool
|
||||||
|
>>> f.clean('-1')
|
||||||
|
True
|
||||||
|
|
||||||
|
# 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 = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
|
||||||
|
>>> f.clean('B')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'Select a valid choice. B is not one of the available choices.']
|
||||||
|
|
||||||
|
# Required fields require values
|
||||||
|
>>> f.clean('')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'This field is required.']
|
||||||
|
|
||||||
|
# Non-required fields aren't required
|
||||||
|
>>> f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
|
||||||
|
>>> f.clean('')
|
||||||
|
''
|
||||||
|
|
||||||
|
# If you want cleaning an empty value to return a different type, tell the field
|
||||||
|
>>> f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
|
||||||
|
>>> print f.clean('')
|
||||||
|
None
|
||||||
|
|
||||||
# NullBooleanField ############################################################
|
# NullBooleanField ############################################################
|
||||||
|
|
||||||
>>> f = NullBooleanField()
|
>>> f = NullBooleanField()
|
||||||
|
|
Loading…
Reference in New Issue