Index: django/newforms/fields.py
===================================================================
--- django/newforms/fields.py	(revision 7836)
+++ django/newforms/fields.py	(working copy)
@@ -56,7 +56,7 @@
     creation_counter = 0
 
     def __init__(self, required=True, widget=None, label=None, initial=None,
-                 help_text=None, error_messages=None):
+                 help_text=None, error_messages=None, normalize=None):
         # required -- Boolean that specifies whether the field is required.
         #             True by default.
         # widget -- A Widget class, or instance of a Widget class, that should
@@ -74,6 +74,7 @@
             label = smart_unicode(label)
         self.required, self.label, self.initial = required, label, initial
         self.help_text = smart_unicode(help_text or '')
+        self.normalize = normalize
         widget = widget or self.widget
         if isinstance(widget, type):
             widget = widget()
@@ -106,6 +107,8 @@
 
         Raises ValidationError for any errors.
         """
+        if self.normalize:
+            value = self.normalize(value)
         if self.required and value in EMPTY_VALUES:
             raise ValidationError(self.error_messages['required'])
         return value
@@ -136,7 +139,7 @@
 
     def clean(self, value):
         "Validates max_length and min_length. Returns a Unicode object."
-        super(CharField, self).clean(value)
+        value = super(CharField, self).clean(value)
         if value in EMPTY_VALUES:
             return u''
         value = smart_unicode(value)
@@ -168,7 +171,7 @@
         Validates that int() can be called on the input. Returns the result
         of int(). Returns None for empty values.
         """
-        super(IntegerField, self).clean(value)
+        value = super(IntegerField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         try:
@@ -197,7 +200,7 @@
         Validates that float() can be called on the input. Returns a float.
         Returns None for empty values.
         """
-        super(FloatField, self).clean(value)
+        value = super(FloatField, self).clean(value)
         if not self.required and value in EMPTY_VALUES:
             return None
         try:
@@ -232,7 +235,7 @@
         than max_digits in the number, and no more than decimal_places digits
         after the decimal point.
         """
-        super(DecimalField, self).clean(value)
+        value = super(DecimalField, self).clean(value)
         if not self.required and value in EMPTY_VALUES:
             return None
         value = smart_str(value).strip()
@@ -277,7 +280,7 @@
         Validates that the input can be converted to a date. Returns a Python
         datetime.date object.
         """
-        super(DateField, self).clean(value)
+        value = super(DateField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         if isinstance(value, datetime.datetime):
@@ -310,7 +313,7 @@
         Validates that the input can be converted to a time. Returns a Python
         datetime.time object.
         """
-        super(TimeField, self).clean(value)
+        value = super(TimeField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         if isinstance(value, datetime.time):
@@ -349,7 +352,7 @@
         Validates that the input can be converted to a datetime. Returns a
         Python datetime.datetime object.
         """
-        super(DateTimeField, self).clean(value)
+        value = super(DateTimeField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         if isinstance(value, datetime.datetime):
@@ -444,7 +447,7 @@
         super(FileField, self).__init__(*args, **kwargs)
 
     def clean(self, data, initial=None):
-        super(FileField, self).clean(initial or data)
+        value = super(FileField, self).clean(initial or data)
         if not self.required and data in EMPTY_VALUES:
             return None
         elif not data and initial:
@@ -572,17 +575,20 @@
 
 class BooleanField(Field):
     widget = CheckboxInput
-
-    def clean(self, value):
-        """Returns a Python boolean object."""
+    def __init__(self, normalize=None, *args, **kwargs):
         # Explicitly check for the string 'False', which is what a hidden field
         # will submit for False. Because bool("True") == True, we don't need to
         # handle that explicitly.
-        if value == 'False':
-            value = False
+        bool_norm = lambda value: value != 'False' and bool(value)
+        if normalize:
+            norm_func = lambda value: bool_norm(normalize(value))
         else:
-            value = bool(value)
-        super(BooleanField, self).clean(value)
+            norm_func = bool_norm
+        super(BooleanField, self).__init__(normalize=norm_func, *args, **kwargs)
+
+    def clean(self, value):
+        """Returns a Python boolean object."""
+        value = super(BooleanField, self).clean(value)
         if not value and self.required:
             raise ValidationError(self.error_messages['required'])
         return value
@@ -679,7 +685,7 @@
         Validates the given value against all of self.fields, which is a
         list of Field instances.
         """
-        super(ComboField, self).clean(value)
+        value = super(ComboField, self).clean(value)
         for field in self.fields:
             value = field.clean(value)
         return value
Index: tests/regressiontests/forms/fields.py
===================================================================
--- tests/regressiontests/forms/fields.py	(revision 7836)
+++ tests/regressiontests/forms/fields.py	(working copy)
@@ -104,6 +104,13 @@
 >>> f.clean('1234567890a')
 u'1234567890a'
 
+Normalize away leading and trailing whitspace:
+>>> f = CharField(normalize=lambda x: x.strip())
+>>> f.clean("  \t")
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
 # IntegerField ################################################################
 
 >>> f = IntegerField()
Index: docs/newforms.txt
===================================================================
--- docs/newforms.txt	(revision 7836)
+++ docs/newforms.txt	(working copy)
@@ -1132,6 +1132,20 @@
 In the `built-in Field classes`_ section below, each ``Field`` defines the
 error message keys it uses.
 
+``normalize``
+~~~~~~~~~~~~~
+
+**New in Django development version**
+
+The ``normalize`` argument lets you normalize the input data before it is validated.
+A common use-case is stripping leading and trailinig whitespace from CharFields::
+
+    >>> foo = forms.CharField(normalize=lambda x: x.strip())
+    >>> foo.clean('   ')
+    Traceback (most recent call last):
+      ...
+    ValidationError: [u'This field is required.']    
+
 Dynamic initial values
 ----------------------
 
