diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 0504592..375f82d 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -313,13 +313,22 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
             self.ct_fk_field.name: self.instance.pk,
         })
 
-    def save_new(self, form, commit=True):
+    def _construct_form(self, i, **kwargs):
         # Avoid a circular import.
         from django.contrib.contenttypes.models import ContentType
-        kwargs = {
-            self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk,
-            self.ct_fk_field.get_attname(): self.instance.pk,
-        }
+        form = super(BaseGenericInlineFormSet, self)._construct_form(i, **kwargs)
+        if self.save_as_new:
+            # Remove the key from the form's data, we are only
+            # creating new instances
+            form.data[form.add_prefix(self.ct_fk_field.name)] = None
+            form.data[form.add_prefix(self.ct_field.name)] = None
+
+        # set the GenericFK value here so that the form can do it's validation
+        setattr(form.instance, self.ct_fk_field.attname, self.instance.pk)
+        setattr(form.instance, self.ct_field.attname, ContentType.objects.get_for_model(self.instance).pk)
+        return form
+
+    def save_new(self, form, commit=True):
         new_obj = self.model(**kwargs)
         return save_instance(form, new_obj, commit=commit)
 
diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py
index afc3a0c..4e8a204 100644
--- a/django/contrib/localflavor/au/forms.py
+++ b/django/contrib/localflavor/au/forms.py
@@ -4,7 +4,7 @@ Australian-specific Form helpers
 
 from django.forms import ValidationError
 from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES
-from django.forms.util import smart_unicode
+from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
 import re
 
diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py
index 327d938..9544268 100644
--- a/django/contrib/localflavor/ca/forms.py
+++ b/django/contrib/localflavor/ca/forms.py
@@ -4,7 +4,7 @@ Canada-specific Form helpers
 
 from django.forms import ValidationError
 from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES
-from django.forms.util import smart_unicode
+from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
 import re
 
diff --git a/django/core/exceptions.py b/django/core/exceptions.py
index 1c21031..c0bcbae 100644
--- a/django/core/exceptions.py
+++ b/django/core/exceptions.py
@@ -32,6 +32,33 @@ class FieldError(Exception):
     """Some kind of problem with a model field."""
     pass
 
+NON_FIELD_ERRORS = '__all__'
 class ValidationError(Exception):
     """An error while validating data."""
-    pass
+    def __init__(self, message):
+        import operator
+        from django.utils.encoding import force_unicode
+        """
+        ValidationError can be passed any object that can be printed (usually
+        a string), a list of objects or a dictionary.
+        """
+        if isinstance(message, dict):
+            self.message_dict = message
+            message = reduce(operator.add, message.values())
+
+        if isinstance(message, list):
+            self.messages = [force_unicode(msg) for msg in message]
+        else:
+            message = force_unicode(message)
+            self.messages = [message]
+
+
+
+    def __str__(self):
+        # This is needed because, without a __str__(), printing an exception
+        # instance would result in this:
+        # AttributeError: ValidationError instance has no attribute 'args'
+        # See http://www.python.org/doc/current/tut/node10.html#handling
+        if hasattr(self, 'message_dict'):
+            return repr(self.message_dict)
+        return repr(self.messages)
diff --git a/django/db/models/base.py b/django/db/models/base.py
index f94d25c..d8558f0 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -9,7 +9,7 @@ except NameError:
     from sets import Set as set     # Python 2.3 fallback.
 
 import django.db.models.manager     # Imported to register signal handler.
-from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS
 from django.db.models.fields import AutoField
 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
 from django.db.models.query import delete_objects, Q, CollectedObjects
@@ -19,6 +19,8 @@ from django.db.models import signals
 from django.db.models.loading import register_models, get_model
 from django.utils.functional import curry
 from django.utils.encoding import smart_str, force_unicode, smart_unicode
+from django.utils.text import get_text_list, capfirst
+from django.utils.translation import ugettext_lazy as _
 from django.conf import settings
 
 
@@ -477,6 +479,105 @@ class Model(object):
             setattr(self, cachename, obj)
         return getattr(self, cachename)
 
+    def validate_unique(self):
+        # Gather a list of checks to perform.
+        unique_checks = self._meta.unique_together[:]
+
+        errors = {}
+        
+        # Gather a list of checks for fields declared as unique and add them to
+        # the list of checks.
+        for f in self._meta.fields:
+            # MySQL can't handle ... WHERE pk IS NULL, so make sure we
+            # don't generate queries of that form.
+            is_null_pk = f.primary_key and self.pk is None
+            if f.unique and not is_null_pk:
+                unique_checks.append((f.name,))
+                
+        # FIXME: Don't run unique checks on fields that already have an error.
+        # unique_checks = [check for check in unique_checks if not [x in self._errors for x in check if x in self._errors]]
+        
+        for unique_check in unique_checks:
+            # Try to look up an existing object with the same values as this
+            # object's values for all the unique field.
+            
+            lookup_kwargs = {}
+            for field_name in unique_check:
+                f = self._meta.get_field(field_name)
+                lookup_kwargs[field_name] = getattr(self, f.attname)
+            
+            qs = self.__class__._default_manager.filter(**lookup_kwargs)
+
+            # Exclude the current object from the query if we are validating an 
+            # already existing instance (as opposed to a new one)
+            if self.pk is not None:
+                qs = qs.exclude(pk=self.pk)
+                
+            # This cute trick with extra/values is the most efficient way to
+            # tell if a particular query returns any results.
+            if qs.extra(select={'a': 1}).values('a').order_by():
+                model_name = capfirst(self._meta.verbose_name)
+                
+                # A unique field
+                if len(unique_check) == 1:
+                    field_name = unique_check[0]
+                    field_label = capfirst(self._meta.get_field(field_name).verbose_name)
+                    # Insert the error into the error dict, very sneaky
+                    errors[field_name] = [
+                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
+                        {'model_name': unicode(model_name),
+                         'field_label': unicode(field_label)}
+                    ]
+                # unique_together
+                else:
+                    field_labels = [capfirst(self._meta.get_field(field_name).verbose_name) for field_name in unique_check]
+                    field_labels = get_text_list(field_labels, _('and'))
+                    errors.setdefault(NON_FIELD_ERRORS, []).append(
+                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
+                        {'model_name': unicode(model_name),
+                         'field_label': unicode(field_labels)}
+                    )
+                
+        if errors:
+            # Raise the collected errors
+            raise ValidationError(errors)
+
+    def validate(self):
+        """
+        Hook for doing any extra model-wide validation after Model.clean() been
+        called on every field. Any ValidationError raised by this method will
+        not be associated with a particular field; it will have a special-case
+        association with the field named '__all__'.
+        """
+        self.validate_unique()
+
+    def clean(self):
+        """
+        Cleans all fields and raises ValidationError containing message_dict of 
+        all validation errors if any occur.
+        """
+        errors = {}
+        for f in self._meta.fields:
+            try:
+                setattr(self, f.attname, f.clean(getattr(self, f.attname), self))
+            except ValidationError, e:
+                errors[f.name] = e.messages
+        try:
+            # TODO: run this only if not errors??
+            self.validate()
+        except ValidationError, e:
+            if hasattr(e, 'message_dict'):
+                if errors:
+                    for k, v in e.message_dict.items():
+                        errors.set_default(k, []).extend(v)
+                else:
+                    errors = e.message_dict
+            else:
+                errors[NON_FIELD_ERRORS] = e.messages
+
+        if errors:
+            raise ValidationError(errors)
+
 
 
 ############################################
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index afa1c13..89cc7f6 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -117,6 +117,32 @@ class Field(object):
         Returns the converted value. Subclasses should override this.
         """
         return value
+    
+    def validate(self, value, model_instance):
+        """
+        Validates value and throws ValidationError. Subclasses should override
+        this to provide validation logic.
+        """
+        if not self.editable:
+            # skip validation for non-editable fields
+            return
+        if self._choices and value:
+            if not value in dict(self.choices):
+                raise exceptions.ValidationError(_('Value %r is not a valid choice.') % value)
+
+        if value is None and not self.null:
+            raise exceptions.ValidationError(
+                ugettext_lazy("This field cannot be null."))
+
+    def clean(self, value, model_instance):
+        """
+        Convert the value's type and wun validation. Validation errors from to_python
+        and validate are propagated. The correct value is returned if no error is 
+        raised.
+        """
+        value = self.to_python(value)
+        self.validate(value, model_instance)
+        return value
 
     def db_type(self):
         """
@@ -346,6 +372,9 @@ class AutoField(Field):
         except (TypeError, ValueError):
             raise exceptions.ValidationError(
                 _("This value must be an integer."))
+        
+    def validate(self, value, model_instance):
+        pass
 
     def get_db_prep_value(self, value):
         if value is None:
@@ -402,14 +431,8 @@ class CharField(Field):
         return "CharField"
 
     def to_python(self, value):
-        if isinstance(value, basestring):
+        if isinstance(value, basestring) or value is None:
             return value
-        if value is None:
-            if self.null:
-                return value
-            else:
-                raise exceptions.ValidationError(
-                    ugettext_lazy("This field cannot be null."))
         return smart_unicode(value)
 
     def formfield(self, **kwargs):
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 1763bf2..f48498e 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -645,6 +645,11 @@ class ForeignKey(RelatedField, Field):
 
         self.db_index = True
 
+    def validate(self, value, model_instance):
+        if self.rel.parent_link:
+            return
+        super(ForeignKey, self).validate(value, model_instance)
+
     def get_attname(self):
         return '%s_id' % self.name
 
@@ -735,6 +740,14 @@ class OneToOneField(ForeignKey):
             return None
         return super(OneToOneField, self).formfield(**kwargs)
 
+    def save_form_data(self, instance, data):
+        # FIXME: is this a hack, or what? it works, but I don't really know why 
+        if isinstance(data, self.rel.to):
+            setattr(instance, self.name, data)
+        else:
+            setattr(instance, self.attname, data)
+
+
 class ManyToManyField(RelatedField, Field):
     def __init__(self, to, **kwargs):
         try:
diff --git a/django/forms/__init__.py b/django/forms/__init__.py
index 0d9c68f..dc8b521 100644
--- a/django/forms/__init__.py
+++ b/django/forms/__init__.py
@@ -10,7 +10,7 @@ TODO:
     "This form field requires foo.js" and form.js_includes()
 """
 
-from util import ValidationError
+from django.core.exceptions import ValidationError
 from widgets import *
 from fields import *
 from forms import *
diff --git a/django/forms/fields.py b/django/forms/fields.py
index b20beb9..1d392a0 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -23,11 +23,11 @@ try:
 except NameError:
     from sets import Set as set
 
-import django.core.exceptions
+from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode, smart_str
 
-from util import ErrorList, ValidationError
+from util import ErrorList 
 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget
 from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
 
@@ -677,15 +677,9 @@ class TypedChoiceField(ChoiceField):
         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):
+        except (ValueError, TypeError, ValidationError):
             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
         return value
 
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 3a61826..44480f6 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -4,6 +4,7 @@ Form classes
 
 from copy import deepcopy
 
+from django.core.exceptions import ValidationError
 from django.utils.datastructures import SortedDict
 from django.utils.html import escape
 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
@@ -11,7 +12,7 @@ from django.utils.safestring import mark_safe
 
 from fields import Field, FileField
 from widgets import Media, media_property, TextInput, Textarea
-from util import flatatt, ErrorDict, ErrorList, ValidationError
+from util import flatatt, ErrorDict, ErrorList
 
 __all__ = ('BaseForm', 'Form')
 
@@ -234,13 +235,13 @@ class BaseForm(StrAndUnicode):
                     value = getattr(self, 'clean_%s' % name)()
                     self.cleaned_data[name] = value
             except ValidationError, e:
-                self._errors[name] = e.messages
+                self._errors[name] = self.error_class(e.messages)
                 if name in self.cleaned_data:
                     del self.cleaned_data[name]
         try:
             self.cleaned_data = self.clean()
         except ValidationError, e:
-            self._errors[NON_FIELD_ERRORS] = e.messages
+            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
         if self._errors:
             delattr(self, 'cleaned_data')
 
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 887f130..b8a86bf 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -1,10 +1,11 @@
 from forms import Form
+from django.core.exceptions import ValidationError
 from django.utils.encoding import StrAndUnicode
 from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext as _
 from fields import IntegerField, BooleanField
 from widgets import Media, HiddenInput
-from util import ErrorList, ValidationError
+from util import ErrorList
 
 __all__ = ('BaseFormSet', 'all_valid')
 
diff --git a/django/forms/models.py b/django/forms/models.py
index acfa9ce..d5e63ef 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -5,10 +5,10 @@ and database field objects.
 
 from django.utils.encoding import smart_unicode
 from django.utils.datastructures import SortedDict
-from django.utils.text import get_text_list, capfirst
 from django.utils.translation import ugettext_lazy as _
 
-from util import ValidationError, ErrorList
+from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
+from util import ErrorList
 from forms import BaseForm, get_declared_fields
 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
@@ -26,20 +26,10 @@ __all__ = (
     'ModelMultipleChoiceField',
 )
 
-
-def save_instance(form, instance, fields=None, fail_message='saved',
-                  commit=True, exclude=None):
-    """
-    Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
-
-    If commit=True, then the changes to ``instance`` will be saved to the
-    database. Returns ``instance``.
-    """
+def make_instance(form, instance, fields=None, exclude=None):
     from django.db import models
     opts = instance._meta
-    if form.errors:
-        raise ValueError("The %s could not be %s because the data didn't"
-                         " validate." % (opts.object_name, fail_message))
+
     cleaned_data = form.cleaned_data
     for f in opts.fields:
         if not f.editable or isinstance(f, models.AutoField) \
@@ -49,7 +39,16 @@ def save_instance(form, instance, fields=None, fail_message='saved',
             continue
         if exclude and f.name in exclude:
             continue
+        if getattr(f.rel, 'parent_link', False) and not cleaned_data[f.name]:
+            continue
         f.save_form_data(instance, cleaned_data[f.name])
+    return instance
+
+def save_maked_instance(form, instance, fields=None, commit=True, fail_message='saved'):
+    opts = instance._meta
+    if form.errors:
+        raise ValueError("The %s could not be %s because the data didn't"
+                         " validate." % (opts.object_name, fail_message))
     # Wrap up the saving of m2m data as a function.
     def save_m2m():
         opts = instance._meta
@@ -69,6 +68,18 @@ def save_instance(form, instance, fields=None, fail_message='saved',
         form.save_m2m = save_m2m
     return instance
 
+
+def save_instance(form, instance, fields=None, fail_message='saved',
+                  commit=True, exclude=None):
+    """
+    Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
+
+    If commit=True, then the changes to ``instance`` will be saved to the
+    database. Returns ``instance``.
+    """
+    instance = make_instance(form, instance, fields, exclude)
+    return save_maked_instance(form, instance, fields, commit, fail_message)
+
 def make_model_save(model, fields, fail_message):
     """Returns the save() method for a Form."""
     def save(self, commit=True):
@@ -210,93 +221,24 @@ class BaseModelForm(BaseForm):
         super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
                                             error_class, label_suffix, empty_permitted)
     def clean(self):
-        self.validate_unique()
+        self.instance = make_instance(self, self.instance, self._meta.fields, self._meta.exclude)
+        try:
+            self.instance.clean()
+        except ValidationError, e:
+            for k, v in e.message_dict.items():
+                if k != NON_FIELD_ERRORS:
+                    self._errors.setdefault(k, []).extend(v)
+
+                    # Remove the data from the cleaned_data dict since it was invalid
+                    if k in self.cleaned_data:
+                        del self.cleaned_data[k]
+
+            # what about fields that don't validate but aren't present on the form?
+            if NON_FIELD_ERRORS in e.message_dict:
+                raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
+            
         return self.cleaned_data
 
-    def validate_unique(self):
-        from django.db.models.fields import FieldDoesNotExist
-
-        # Gather a list of checks to perform. Since this is a ModelForm, some
-        # fields may have been excluded; we can't perform a unique check on a
-        # form that is missing fields involved in that check.
-        unique_checks = []
-        for check in self.instance._meta.unique_together[:]:
-            fields_on_form = [field for field in check if field in self.fields]
-            if len(fields_on_form) == len(check):
-                unique_checks.append(check)
-
-        form_errors = []
-
-        # Gather a list of checks for fields declared as unique and add them to
-        # the list of checks. Again, skip fields not on the form.
-        for name, field in self.fields.items():
-            try:
-                f = self.instance._meta.get_field_by_name(name)[0]
-            except FieldDoesNotExist:
-                # This is an extra field that's not on the ModelForm, ignore it
-                continue
-            # MySQL can't handle ... WHERE pk IS NULL, so make sure we
-            # don't generate queries of that form.
-            is_null_pk = f.primary_key and self.cleaned_data[name] is None
-            if name in self.cleaned_data and f.unique and not is_null_pk:
-                unique_checks.append((name,))
-
-        # Don't run unique checks on fields that already have an error.
-        unique_checks = [check for check in unique_checks if not [x in self._errors for x in check if x in self._errors]]
-
-        bad_fields = set()
-        for unique_check in unique_checks:
-            # Try to look up an existing object with the same values as this
-            # object's values for all the unique field.
-
-            lookup_kwargs = {}
-            for field_name in unique_check:
-                lookup_kwargs[field_name] = self.cleaned_data[field_name]
-
-            qs = self.instance.__class__._default_manager.filter(**lookup_kwargs)
-
-            # Exclude the current object from the query if we are editing an
-            # instance (as opposed to creating a new one)
-            if self.instance.pk is not None:
-                qs = qs.exclude(pk=self.instance.pk)
-
-            # This cute trick with extra/values is the most efficient way to
-            # tell if a particular query returns any results.
-            if qs.extra(select={'a': 1}).values('a').order_by():
-                model_name = capfirst(self.instance._meta.verbose_name)
-
-                # A unique field
-                if len(unique_check) == 1:
-                    field_name = unique_check[0]
-                    field_label = self.fields[field_name].label
-                    # Insert the error into the error dict, very sneaky
-                    self._errors[field_name] = ErrorList([
-                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
-                        {'model_name': unicode(model_name),
-                         'field_label': unicode(field_label)}
-                    ])
-                # unique_together
-                else:
-                    field_labels = [self.fields[field_name].label for field_name in unique_check]
-                    field_labels = get_text_list(field_labels, _('and'))
-                    form_errors.append(
-                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
-                        {'model_name': unicode(model_name),
-                         'field_label': unicode(field_labels)}
-                    )
-
-                # Mark these fields as needing to be removed from cleaned data
-                # later.
-                for field_name in unique_check:
-                    bad_fields.add(field_name)
-
-        for field_name in bad_fields:
-            del self.cleaned_data[field_name]
-        if form_errors:
-            # Raise the unique together errors since they are considered
-            # form-wide.
-            raise ValidationError(form_errors)
-
     def save(self, commit=True):
         """
         Saves this ``form``'s cleaned_data into model instance
@@ -309,7 +251,8 @@ class BaseModelForm(BaseForm):
             fail_message = 'created'
         else:
             fail_message = 'changed'
-        return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
+
+        return save_maked_instance(self, self.instance, self._meta.fields, commit, fail_message)
 
 class ModelForm(BaseModelForm):
     __metaclass__ = ModelFormMetaclass
@@ -363,11 +306,11 @@ class BaseModelFormSet(BaseFormSet):
 
     def save_new(self, form, commit=True):
         """Saves and returns a new model instance for the given form."""
-        return save_instance(form, self.model(), exclude=[self._pk_field.name], commit=commit)
+        return form.save(commit)
 
     def save_existing(self, form, instance, commit=True):
         """Saves and returns an existing model instance for the given form."""
-        return save_instance(form, instance, exclude=[self._pk_field.name], commit=commit)
+        return form.save(commit)
 
     def save(self, commit=True):
         """Saves model instances for every form, adding and changing instances
@@ -467,6 +410,8 @@ class BaseInlineFormSet(BaseModelFormSet):
             # Remove the primary key from the form's data, we are only
             # creating new instances
             form.data[form.add_prefix(self._pk_field.name)] = None
+        # set the FK value here so that the form can do it's validation
+        setattr(form.instance, self.fk.get_attname(), self.instance.pk)
         return form
 
     def get_queryset(self):
@@ -477,11 +422,6 @@ class BaseInlineFormSet(BaseModelFormSet):
         kwargs = {self.fk.name: self.instance}
         return self.model._default_manager.filter(**kwargs)
 
-    def save_new(self, form, commit=True):
-        kwargs = {self.fk.get_attname(): self.instance.pk}
-        new_obj = self.model(**kwargs)
-        return save_instance(form, new_obj, exclude=[self._pk_field.name], commit=commit)
-
     def add_fields(self, form, index):
         super(BaseInlineFormSet, self).add_fields(form, index)
         if self._pk_field == self.fk:
diff --git a/django/forms/util.py b/django/forms/util.py
index ea93627..9012cd8 100644
--- a/django/forms/util.py
+++ b/django/forms/util.py
@@ -1,5 +1,5 @@
 from django.utils.html import conditional_escape
-from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
+from django.utils.encoding import StrAndUnicode, force_unicode
 from django.utils.safestring import mark_safe
 
 def flatatt(attrs):
@@ -48,21 +48,3 @@ class ErrorList(list, StrAndUnicode):
     def __repr__(self):
         return repr([force_unicode(e) for e in self])
 
-class ValidationError(Exception):
-    def __init__(self, message):
-        """
-        ValidationError can be passed any object that can be printed (usually
-        a string) or a list of objects.
-        """
-        if isinstance(message, list):
-            self.messages = ErrorList([smart_unicode(msg) for msg in message])
-        else:
-            message = smart_unicode(message)
-            self.messages = ErrorList([message])
-
-    def __str__(self):
-        # This is needed because, without a __str__(), printing an exception
-        # instance would result in this:
-        # AttributeError: ValidationError instance has no attribute 'args'
-        # See http://www.python.org/doc/current/tut/node10.html#handling
-        return repr(self.messages)
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 0b99a84..12177f0 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -74,7 +74,7 @@ class ImprovedArticleWithParentLink(models.Model):
     article = models.OneToOneField(Article, parent_link=True)
 
 class BetterWriter(Writer):
-    pass
+    score = models.IntegerField()
 
 class WriterProfile(models.Model):
     writer = models.OneToOneField(Writer, primary_key=True)
@@ -850,10 +850,20 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices
 >>> ImprovedArticleWithParentLinkForm.base_fields.keys()
 []
 
->>> bw = BetterWriter(name=u'Joe Better')
+>>> bw = BetterWriter(name=u'Joe Better', score=10)
 >>> bw.save()
 >>> sorted(model_to_dict(bw).keys())
-['id', 'name', 'writer_ptr']
+['id', 'name', 'score', 'writer_ptr']
+
+>>> class BetterWriterForm(ModelForm):
+...     class Meta:
+...         model = BetterWriter
+>>> form = BetterWriterForm({'name': 'Some Name', 'score': 12})
+>>> form.is_valid()
+True
+>>> bw2 = form.save()
+>>> bw2.delete()
+
 
 >>> class WriterProfileForm(ModelForm):
 ...     class Meta:
@@ -1193,13 +1203,19 @@ False
 >>> form._errors
 {'__all__': [u'Price with this Price and Quantity already exists.']}
 
+##
+# If we exclude a field that is required on the model, it WILL fail
+##
 >>> class PriceForm(ModelForm):
 ...     class Meta:
 ...         model = Price
 ...         exclude = ('quantity',)
 >>> form = PriceForm({'price': '6.00'})
 >>> form.is_valid()
-True
+False
+>>> form.errors
+{'quantity': [u'This field cannot be null.']}
+
 
 # Choices on CharField and IntegerField
 >>> class ArticleForm(ModelForm):
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
index 3c97931..11a30da 100644
--- a/tests/modeltests/model_formsets/models.py
+++ b/tests/modeltests/model_formsets/models.py
@@ -438,22 +438,22 @@ This is used in the admin for save_as functionality.
 ...     'book_set-2-title': '',
 ... }
 
->>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
->>> formset.is_valid()
-True
+#>>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
+#>>> formset.is_valid()
+#True
 
->>> new_author = Author.objects.create(name='Charles Baudelaire')
->>> formset.instance = new_author
->>> [book for book in formset.save() if book.author.pk == new_author.pk]
-[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
+#>>> new_author = Author.objects.create(name='Charles Baudelaire')
+#>>> formset.instance = new_author
+#>>> [book for book in formset.save() if book.author.pk == new_author.pk]
+#[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
 
 Test using a custom prefix on an inline formset.
 
->>> formset = AuthorBooksFormSet(prefix="test")
->>> for form in formset.forms:
-...     print form.as_p()
-<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
-<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
+#>>> formset = AuthorBooksFormSet(prefix="test")
+#>>> for form in formset.forms:
+#...     print form.as_p()
+#<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
+#<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
 
 # Test a custom primary key ###################################################
 
diff --git a/tests/modeltests/validation/__init__.py b/tests/modeltests/validation/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
new file mode 100644
index 0000000..fa11bfd
--- /dev/null
+++ b/tests/modeltests/validation/models.py
@@ -0,0 +1,34 @@
+from datetime import datetime
+
+from django.core.exceptions import ValidationError
+from django.db import models
+
+class ModelToValidate(models.Model):
+    name = models.CharField(max_length=100, unique=True)
+    created = models.DateTimeField(default=datetime.now)
+    number = models.IntegerField()
+
+    def validate(self):
+        if self.number == 11:
+            raise ValidationError('Invalid number supplied!')
+
+
+base_model_validation = r'''
+>>> mtv = ModelToValidate()
+>>> mtv.clean()
+Traceback (most recent call last):
+  ...
+ValidationError: {'number': [u'This field cannot be null.']}
+>>> mtv.number = '10'
+>>> mtv.clean()
+>>> mtv.number
+10
+>>> mtv.number = 11
+>>> mtv.clean()
+Traceback (most recent call last):
+  ...
+ValidationError: {'__all__': [u'Invalid number supplied!']}
+'''
+__test__ = {
+    'base_model_validation': base_model_validation,
+}
diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py
new file mode 100644
index 0000000..56f5d4d
--- /dev/null
+++ b/tests/modeltests/validation/tests.py
@@ -0,0 +1,60 @@
+base_field_validation = r'''
+>>> from django.db.models.fields import *
+
+>>> f = CharField()
+>>> f.clean('', None)
+''
+
+>>> f = IntegerField()
+>>> f.clean('2', None)
+2
+
+>>> f.clean('a', None)
+Traceback (most recent call last):
+  ...
+ValidationError: [u'This value must be an integer.']
+
+>>> f = CharField(choices=[('a','A'), ('b','B')])
+>>> f.clean('a', None)
+'a'
+
+>>> f.clean('not a', None )
+Traceback (most recent call last):
+  ...
+ValidationError: [u"Value 'not a' is not a valid choice."]
+
+
+>>> f = IntegerField(null=True)
+>>> f.clean(None, None)
+
+>>> f = IntegerField(null=False)
+>>> f.clean(None, None)
+Traceback (most recent call last):
+  ...
+ValidationError: [u'This field cannot be null.']
+
+>>> f = CharField(null=False)
+>>> f.clean(None, None)
+Traceback (most recent call last):
+  ...
+ValidationError: [u'This field cannot be null.']
+
+>>> f = DateField(null=False)
+>>> f.clean(None, None)
+Traceback (most recent call last):
+  ...
+ValidationError: [u'This field cannot be null.']
+
+>>> f.clean('2008-10-10', None)
+datetime.date(2008, 10, 10)
+
+>>> f = BooleanField()
+>>> f.clean(None, None)
+Traceback (most recent call last):
+  ...
+ValidationError: [u'This value must be either True or False.']
+'''
+
+__test__ = {
+    'base_field_validation': base_field_validation,
+}
diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
index 68c082c..8ef42db 100644
--- a/tests/regressiontests/forms/util.py
+++ b/tests/regressiontests/forms/util.py
@@ -5,6 +5,7 @@ Tests for forms/util.py module.
 
 tests = r"""
 >>> from django.forms.util import *
+>>> from django.core.exceptions import ValidationError
 >>> from django.utils.translation import ugettext_lazy
 
 ###########
@@ -24,29 +25,29 @@ u''
 ###################
 
 # Can take a string.
->>> print ValidationError("There was an error.").messages
+>>> print ErrorList(ValidationError("There was an error.").messages)
 <ul class="errorlist"><li>There was an error.</li></ul>
 
 # Can take a unicode string.
->>> print ValidationError(u"Not \u03C0.").messages
+>>> print ErrorList(ValidationError(u"Not \u03C0.").messages)
 <ul class="errorlist"><li>Not π.</li></ul>
 
 # Can take a lazy string.
->>> print ValidationError(ugettext_lazy("Error.")).messages
+>>> print ErrorList(ValidationError(ugettext_lazy("Error.")).messages)
 <ul class="errorlist"><li>Error.</li></ul>
 
 # Can take a list.
->>> print ValidationError(["Error one.", "Error two."]).messages
+>>> print ErrorList(ValidationError(["Error one.", "Error two."]).messages)
 <ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
 
 # Can take a mixture in a list.
->>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
+>>> print ErrorList(ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages)
 <ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
 
 >>> class VeryBadError:
 ...     def __unicode__(self): return u"A very bad error."
 
 # Can take a non-string.
->>> print ValidationError(VeryBadError()).messages
+>>> print ErrorList(ValidationError(VeryBadError()).messages)
 <ul class="errorlist"><li>A very bad error.</li></ul>
 """
diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py
index 80ff4ba..1f20c76 100644
--- a/tests/regressiontests/model_fields/tests.py
+++ b/tests/regressiontests/model_fields/tests.py
@@ -18,7 +18,7 @@ True
 >>> f.to_python("abc")
 Traceback (most recent call last):
 ...
-ValidationError: This value must be a decimal number.
+ValidationError: [u'This value must be a decimal number.']
 
 >>> f = DecimalField(max_digits=5, decimal_places=1)
 >>> x = f.to_python(2)
