Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
View
29 django/forms/fields.py
@@ -23,6 +23,7 @@
except NameError:
from sets import Set as set
+import django.core.exceptions
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, smart_str
@@ -39,6 +40,7 @@
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
+ 'TypedChoiceField'
)
# These values, if given to to_python(), will trigger the self.required check.
@@ -657,6 +659,33 @@ def valid_value(self, value):
return True
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):
hidden_widget = MultipleHiddenInput
widget = SelectMultiple
View
27 docs/ref/forms/fields.txt
@@ -362,6 +362,33 @@ Takes one extra required argument:
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
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``
~~~~~~~~~~~~~
View
47 tests/regressiontests/forms/fields.py
@@ -1077,6 +1077,53 @@
...
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 ############################################################
>>> f = NullBooleanField()
Please sign in to comment.
Something went wrong with that request. Please try again.