diff --git a/AUTHORS b/AUTHORS
index bb77b68..6ffb8f7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -387,6 +387,7 @@ answer newbie questions, and generally made Django that much better:
     ymasuda@ethercube.com
     Jarek Zgoda <jarek.zgoda@gmail.com>
     Cheng Zhang
+    Honza Kral <Honza.Kral@gmail.com>
 
 A big THANK YOU goes to:
 
diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py
index a3b4538..89e6952 100644
--- a/django/contrib/admin/views/template.py
+++ b/django/contrib/admin/views/template.py
@@ -1,5 +1,5 @@
 from django.contrib.admin.views.decorators import staff_member_required
-from django.core import validators
+from django.core import validation
 from django import template, oldforms
 from django.template import loader
 from django.shortcuts import render_to_response
@@ -69,4 +69,4 @@ class TemplateValidator(oldforms.Manipulator):
             error = e
         template.builtins.remove(register)
         if error:
-            raise validators.ValidationError, e.args
+            raise validation.ValidationError, e.args
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 47a974c..b1af278 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -2,7 +2,7 @@ from django.contrib.auth.models import User
 from django.contrib.auth import authenticate
 from django.contrib.sites.models import Site
 from django.template import Context, loader
-from django.core import validators
+from django.oldforms import validators
 from django import oldforms
 from django.utils.translation import ugettext as _
 
diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
index 4299762..1e8a775 100644
--- a/django/contrib/auth/management/commands/createsuperuser.py
+++ b/django/contrib/auth/management/commands/createsuperuser.py
@@ -39,7 +39,7 @@ class Command(BaseCommand):
             if not RE_VALID_USERNAME.match(username):
                 raise CommandError("Invalid username. Use only letters, digits, and underscores")
             try:
-                validators.isValidEmail(email, None)
+                validators.validate_email(email)
             except validators.ValidationError:
                 raise CommandError("Invalid email address.")
 
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index f74d1d7..891579a 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -1,5 +1,5 @@
 from django.contrib import auth
-from django.core import validators
+from django.oldforms import validators
 from django.core.exceptions import ImproperlyConfigured
 from django.db import models
 from django.db.models.manager import EmptyManager
@@ -135,7 +135,7 @@ class User(models.Model):
 
     Username and password are required. Other fields are optional.
     """
-    username = models.CharField(_('username'), max_length=30, unique=True, validator_list=[validators.isAlphaNumeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
+    username = models.CharField(_('username'), max_length=30, unique=True, validators=[validators.isAlphaNumeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
     first_name = models.CharField(_('first name'), max_length=30, blank=True)
     last_name = models.CharField(_('last name'), max_length=30, blank=True)
     email = models.EmailField(_('e-mail address'), blank=True)
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 67da575..9c743bf 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -1,4 +1,4 @@
-from django.core import validators
+from django.oldforms import validators
 from django import oldforms
 from django.core.mail import mail_admins, mail_managers
 from django.http import Http404
diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
index 190ffbd..19e3a73 100644
--- a/django/contrib/flatpages/models.py
+++ b/django/contrib/flatpages/models.py
@@ -1,11 +1,11 @@
-from django.core import validators
+from django.oldforms import validators
 from django.db import models
 from django.contrib.sites.models import Site
 from django.utils.translation import ugettext_lazy as _
 
 
 class FlatPage(models.Model):
-    url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
+    url = models.CharField(_('URL'), max_length=100, validators=[validators.isAlphaNumericURL], db_index=True,
         help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
     title = models.CharField(_('title'), max_length=200)
     content = models.TextField(_('content'))
diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
index aa7e3b2..ca9e61b 100644
--- a/django/contrib/localflavor/br/forms.py
+++ b/django/contrib/localflavor/br/forms.py
@@ -31,10 +31,10 @@ class BRPhoneNumberField(Field):
     }
 
     def clean(self, value):
-        super(BRPhoneNumberField, self).clean(value)
+        value = super(BRPhoneNumberField, self).clean(value)
         if value in EMPTY_VALUES:
             return u''
-        value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
+        value = re.sub('(\(|\)|\s+)', '', value)
         m = phone_digits_re.search(value)
         if m:
             return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py
index a0274da..c91db42 100644
--- a/django/contrib/localflavor/fi/forms.py
+++ b/django/contrib/localflavor/fi/forms.py
@@ -29,8 +29,11 @@ class FISocialSecurityNumber(Field):
     }
 
     def clean(self, value):
-        super(FISocialSecurityNumber, self).clean(value)
-        if value in EMPTY_VALUES:
+        # changed not to throw UnicodeDecode error when passed invalid data
+        # I think this SHOULD call super.clean, which would mean ^^^
+        if self.required and value in EMPTY_VALUES:
+             raise ValidationError(self.error_messages['required'])
+        elif value in EMPTY_VALUES:
             return u''
 
         checkmarks = "0123456789ABCDEFHJKLMNPRSTUVWXY"
diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
index d726f82..4d334aa 100644
--- a/django/contrib/localflavor/jp/forms.py
+++ b/django/contrib/localflavor/jp/forms.py
@@ -2,7 +2,6 @@
 JP-specific Form helpers
 """
 
-from django.core import validators
 from django.newforms import ValidationError
 from django.utils.translation import ugettext
 from django.newforms.fields import RegexField, Select
diff --git a/django/core/validation.py b/django/core/validation.py
new file mode 100644
index 0000000..4de7c98
--- /dev/null
+++ b/django/core/validation.py
@@ -0,0 +1,50 @@
+from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
+from django.utils.safestring import mark_safe
+
+NON_FIELD_ERRORS = '__all__'
+
+class ErrorList(list, StrAndUnicode):
+    """
+    A collection of errors that knows how to display itself in various formats.
+    """
+    def __unicode__(self):
+        return self.as_ul()
+
+    def as_ul(self):
+        if not self: return u''
+        return mark_safe(u'<ul class="errorlist">%s</ul>'
+                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
+
+    def as_text(self):
+        if not self: return u''
+        return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
+
+    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 hasattr(message, '__iter__'):
+            self.messages = ErrorList([smart_unicode(msg) for msg in message])
+        else:
+            message = smart_unicode(message)
+            self.messages = ErrorList([message])
+
+        if isinstance(message, dict):
+            self.message_dict = 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)
+
+class TypeCoercionError(ValidationError):
+    pass
diff --git a/django/core/validators.py b/django/core/validators.py
index e728dbc..2703455 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -1,245 +1,75 @@
 """
 A library of validators that return None and raise ValidationError when the
 provided data isn't valid.
-
-Validators may be callable classes, and they may have an 'always_test'
-attribute. If an 'always_test' attribute exists (regardless of value), the
-validator will *always* be run, regardless of whether its associated
-form field is required.
 """
 
-import urllib2
 import re
-try:
-    from decimal import Decimal, DecimalException
-except ImportError:
-    from django.utils._decimal import Decimal, DecimalException    # Python 2.3
+import urllib2
 
 from django.conf import settings
-from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
-from django.utils.functional import Promise, lazy
-from django.utils.encoding import force_unicode, smart_str
+from django.utils.translation import ugettext as _
+from django.core.validation import ValidationError
+
+def regexp_validator(regexp, message):
+    if isinstance(regexp, basestring):
+        regexp = re.compile(regexp)
+
+    def _regexp_validator(value):
+        if not regexp.search(value):
+            raise ValidationError, _(message)
+    return _regexp_validator
 
-_datere = r'\d{4}-\d{1,2}-\d{1,2}'
-_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
-alnum_re = re.compile(r'^\w+$')
-alnumurl_re = re.compile(r'^[-\w/]+$')
-ansi_date_re = re.compile('^%s$' % _datere)
-ansi_time_re = re.compile('^%s$' % _timere)
-ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
 email_re = re.compile(
     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
     r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
-integer_re = re.compile(r'^-?\d+$')
-ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
-phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
-slug_re = re.compile(r'^[-\w]+$')
-url_re = re.compile(r'^https?://\S+$')
+validate_email = regexp_validator(email_re, 'Enter a valid e-mail address.')
 
-lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
+validate_ip_address4 = regexp_validator(
+        re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$'),
+        "Please enter a valid IP address."
+    )
 
-class ValidationError(Exception):
-    def __init__(self, message):
-        "ValidationError can be passed a string or a list."
-        if isinstance(message, list):
-            self.messages = [force_unicode(msg) for msg in message]
-        else:
-            assert isinstance(message, (basestring, Promise)), ("%s should be a string" % repr(message))
-            self.messages = [force_unicode(message)]
+validate_phone_number = regexp_validator(
+        re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE),
+        _('Phone numbers must be in XXX-XXX-XXXX format.')
+    )
 
-    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 str(self.messages)
+validate_slug = regexp_validator(
+        re.compile(r'^[-\w]+$'),
+        "This value must contain only letters, numbers, underscores or hyphens."
+    )
 
-class CriticalValidationError(Exception):
-    def __init__(self, message):
-        "ValidationError can be passed a string or a list."
-        if isinstance(message, list):
-            self.messages = [force_unicode(msg) for msg in message]
-        else:
-            assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
-            self.messages = [force_unicode(message)]
-
-    def __str__(self):
-        return str(self.messages)
-
-def isAlphaNumeric(field_data, all_data):
-    if not alnum_re.search(field_data):
-        raise ValidationError, _("This value must contain only letters, numbers and underscores.")
-
-def isAlphaNumericURL(field_data, all_data):
-    if not alnumurl_re.search(field_data):
-        raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
-
-def isSlug(field_data, all_data):
-    if not slug_re.search(field_data):
-        raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
-
-def isLowerCase(field_data, all_data):
-    if field_data.lower() != field_data:
-        raise ValidationError, _("Uppercase letters are not allowed here.")
+_datere = r'\d{4}-\d{1,2}-\d{1,2}'
+validate_ansi_date = regexp_validator(
+        re.compile('^%s$' % _datere),
+        _('Enter a valid date in YYYY-MM-DD format.')
+    )
 
-def isUpperCase(field_data, all_data):
-    if field_data.upper() != field_data:
-        raise ValidationError, _("Lowercase letters are not allowed here.")
+_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
+validate_ansi_time = regexp_validator(
+        re.compile('^%s$' % _timere),
+        _('Enter a valid time in HH:MM format.')
+    )
 
-def isCommaSeparatedIntegerList(field_data, all_data):
-    for supposed_int in field_data.split(','):
-        try:
-            int(supposed_int)
-        except ValueError:
-            raise ValidationError, _("Enter only digits separated by commas.")
+validate_ansi_datetime = regexp_validator(
+        re.compile('^%s %s$' % (_datere, _timere)),
+        _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+    )
 
-def isCommaSeparatedEmailList(field_data, all_data):
+def validate_comma_separated_email_list(value):
     """
-    Checks that field_data is a string of e-mail addresses separated by commas.
-    Blank field_data values will not throw a validation error, and whitespace
+    Checks that value is a string of e-mail addresses separated by commas.
+    Blank value values will not throw a validation error, and whitespace
     is allowed around the commas.
     """
-    for supposed_email in field_data.split(','):
+    for supposed_email in value.split(','):
         try:
-            isValidEmail(supposed_email.strip(), '')
+            validate_email(supposed_email.strip())
         except ValidationError:
             raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
 
-def isValidIPAddress4(field_data, all_data):
-    if not ip4_re.search(field_data):
-        raise ValidationError, _("Please enter a valid IP address.")
-
-def isNotEmpty(field_data, all_data):
-    if field_data.strip() == '':
-        raise ValidationError, _("Empty values are not allowed here.")
-
-def isOnlyDigits(field_data, all_data):
-    if not field_data.isdigit():
-        raise ValidationError, _("Non-numeric characters aren't allowed here.")
-
-def isNotOnlyDigits(field_data, all_data):
-    if field_data.isdigit():
-        raise ValidationError, _("This value can't be comprised solely of digits.")
-
-def isInteger(field_data, all_data):
-    # This differs from isOnlyDigits because this accepts the negative sign
-    if not integer_re.search(field_data):
-        raise ValidationError, _("Enter a whole number.")
-
-def isOnlyLetters(field_data, all_data):
-    if not field_data.isalpha():
-        raise ValidationError, _("Only alphabetical characters are allowed here.")
-
-def _isValidDate(date_string):
-    """
-    A helper function used by isValidANSIDate and isValidANSIDatetime to
-    check if the date is valid.  The date string is assumed to already be in
-    YYYY-MM-DD format.
-    """
-    from datetime import date
-    # Could use time.strptime here and catch errors, but datetime.date below
-    # produces much friendlier error messages.
-    year, month, day = map(int, date_string.split('-'))
-    # This check is needed because strftime is used when saving the date
-    # value to the database, and strftime requires that the year be >=1900.
-    if year < 1900:
-        raise ValidationError, _('Year must be 1900 or later.')
-    try:
-        date(year, month, day)
-    except ValueError, e:
-        msg = _('Invalid date: %s') % _(str(e))
-        raise ValidationError, msg
-
-def isValidANSIDate(field_data, all_data):
-    if not ansi_date_re.search(field_data):
-        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
-    _isValidDate(field_data)
-
-def isValidANSITime(field_data, all_data):
-    if not ansi_time_re.search(field_data):
-        raise ValidationError, _('Enter a valid time in HH:MM format.')
-
-def isValidANSIDatetime(field_data, all_data):
-    if not ansi_datetime_re.search(field_data):
-        raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
-    _isValidDate(field_data.split()[0])
-
-def isValidEmail(field_data, all_data):
-    if not email_re.search(field_data):
-        raise ValidationError, _('Enter a valid e-mail address.')
-
-def isValidImage(field_data, all_data):
-    """
-    Checks that the file-upload field data contains a valid image (GIF, JPG,
-    PNG, possibly others -- whatever the Python Imaging Library supports).
-    """
-    from PIL import Image
-    from cStringIO import StringIO
-    try:
-        content = field_data['content']
-    except TypeError:
-        raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
-    try:
-        # load() is the only method that can spot a truncated JPEG,
-        #  but it cannot be called sanely after verify()
-        trial_image = Image.open(StringIO(content))
-        trial_image.load()
-        # verify() is the only method that can spot a corrupt PNG,
-        #  but it must be called immediately after the constructor
-        trial_image = Image.open(StringIO(content))
-        trial_image.verify()
-    except Exception: # Python Imaging Library doesn't recognize it as an image
-        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
-
-def isValidImageURL(field_data, all_data):
-    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
-    try:
-        uc(field_data, all_data)
-    except URLMimeTypeCheck.InvalidContentType:
-        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
-
-def isValidPhone(field_data, all_data):
-    if not phone_re.search(field_data):
-        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
-
-def isValidQuicktimeVideoURL(field_data, all_data):
-    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
-    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
-    try:
-        uc(field_data, all_data)
-    except URLMimeTypeCheck.InvalidContentType:
-        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
-
-def isValidURL(field_data, all_data):
-    if not url_re.search(field_data):
-        raise ValidationError, _("A valid URL is required.")
-
-def isValidHTML(field_data, all_data):
-    import urllib, urllib2
-    try:
-        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
-    except:
-        # Validator or Internet connection is unavailable. Fail silently.
-        return
-    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
-    if html_is_valid:
-        return
-    from xml.dom.minidom import parseString
-    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
-    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
-
-def isWellFormedXml(field_data, all_data):
-    from xml.dom.minidom import parseString
-    try:
-        parseString(field_data)
-    except Exception, e: # Naked except because we're not sure what will be thrown
-        raise ValidationError, _("Badly formed XML: %s") % str(e)
-
-def isWellFormedXmlFragment(field_data, all_data):
-    isWellFormedXml('<root>%s</root>' % field_data, all_data)
-
-def isExistingURL(field_data, all_data):
+def validate_existing_url(value):
     try:
         headers = {
             "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
@@ -248,271 +78,17 @@ def isExistingURL(field_data, all_data):
             "Connection" : "close",
             "User-Agent": settings.URL_VALIDATOR_USER_AGENT
             }
-        req = urllib2.Request(field_data,None, headers)
+        req = urllib2.Request(value,None, headers)
         u = urllib2.urlopen(req)
     except ValueError:
-        raise ValidationError, _("Invalid URL: %s") % field_data
+        raise ValidationError, _("Invalid URL: %s") % value
     except urllib2.HTTPError, e:
         # 401s are valid; they just mean authorization is required.
         # 301 and 302 are redirects; they just mean look somewhere else.
         if str(e.code) not in ('401','301','302'):
-            raise ValidationError, _("The URL %s is a broken link.") % field_data
+            raise ValidationError, _("The URL %s is a broken link.") % value
     except: # urllib2.URLError, httplib.InvalidURL, etc.
-        raise ValidationError, _("The URL %s is a broken link.") % field_data
-
-def isValidUSState(field_data, all_data):
-    "Checks that the given string is a valid two-letter U.S. state abbreviation"
-    states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
-    if field_data.upper() not in states:
-        raise ValidationError, _("Enter a valid U.S. state abbreviation.")
-
-def hasNoProfanities(field_data, all_data):
-    """
-    Checks that the given string has no profanities in it. This does a simple
-    check for whether each profanity exists within the string, so 'fuck' will
-    catch 'motherfucker' as well. Raises a ValidationError such as:
-        Watch your mouth! The words "f--k" and "s--t" are not allowed here.
-    """
-    field_data = field_data.lower() # normalize
-    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
-    if words_seen:
-        from django.utils.text import get_text_list
-        plural = len(words_seen)
-        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
-            "Watch your mouth! The words %s are not allowed here.", plural) % \
-            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
-
-class AlwaysMatchesOtherField(object):
-    def __init__(self, other_field_name, error_message=None):
-        self.other = other_field_name
-        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
-        self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        if field_data != all_data[self.other]:
-            raise ValidationError, self.error_message
-
-class ValidateIfOtherFieldEquals(object):
-    def __init__(self, other_field, other_value, validator_list):
-        self.other_field, self.other_value = other_field, other_value
-        self.validator_list = validator_list
-        self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        if self.other_field in all_data and all_data[self.other_field] == self.other_value:
-            for v in self.validator_list:
-                v(field_data, all_data)
-
-class RequiredIfOtherFieldNotGiven(object):
-    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
-        self.other, self.error_message = other_field_name, error_message
-        self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        if not all_data.get(self.other, False) and not field_data:
-            raise ValidationError, self.error_message
-
-class RequiredIfOtherFieldsGiven(object):
-    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
-        self.other, self.error_message = other_field_names, error_message
-        self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        for field in self.other:
-            if all_data.get(field, False) and not field_data:
-                raise ValidationError, self.error_message
-
-class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
-    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
-    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
-        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
-
-class RequiredIfOtherFieldEquals(object):
-    def __init__(self, other_field, other_value, error_message=None, other_label=None):
-        self.other_field = other_field
-        self.other_value = other_value
-        other_label = other_label or other_value
-        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
-            'field': other_field, 'value': other_label})
-        self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
-            raise ValidationError(self.error_message)
-
-class RequiredIfOtherFieldDoesNotEqual(object):
-    def __init__(self, other_field, other_value, other_label=None, error_message=None):
-        self.other_field = other_field
-        self.other_value = other_value
-        other_label = other_label or other_value
-        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
-            'field': other_field, 'value': other_label})
-        self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
-            raise ValidationError(self.error_message)
-
-class IsLessThanOtherField(object):
-    def __init__(self, other_field_name, error_message):
-        self.other, self.error_message = other_field_name, error_message
-
-    def __call__(self, field_data, all_data):
-        if field_data > all_data[self.other]:
-            raise ValidationError, self.error_message
-
-class UniqueAmongstFieldsWithPrefix(object):
-    def __init__(self, field_name, prefix, error_message):
-        self.field_name, self.prefix = field_name, prefix
-        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
-
-    def __call__(self, field_data, all_data):
-        for field_name, value in all_data.items():
-            if field_name != self.field_name and value == field_data:
-                raise ValidationError, self.error_message
-
-class NumberIsInRange(object):
-    """
-    Validator that tests if a value is in a range (inclusive).
-    """
-    def __init__(self, lower=None, upper=None, error_message=''):
-        self.lower, self.upper = lower, upper
-        if not error_message:
-            if lower and upper:
-                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
-            elif lower:
-                self.error_message = _("This value must be at least %s.") % lower
-            elif upper:
-                self.error_message = _("This value must be no more than %s.") % upper
-        else:
-            self.error_message = error_message
-
-    def __call__(self, field_data, all_data):
-        # Try to make the value numeric. If this fails, we assume another
-        # validator will catch the problem.
-        try:
-            val = float(field_data)
-        except ValueError:
-            return
-
-        # Now validate
-        if self.lower and self.upper and (val < self.lower or val > self.upper):
-            raise ValidationError(self.error_message)
-        elif self.lower and val < self.lower:
-            raise ValidationError(self.error_message)
-        elif self.upper and val > self.upper:
-            raise ValidationError(self.error_message)
-
-class IsAPowerOf(object):
-    """
-    Usage: If you create an instance of the IsPowerOf validator:
-        v = IsAPowerOf(2)
-    
-    The following calls will succeed:
-        v(4, None) 
-        v(8, None)
-        v(16, None)
-    
-    But this call:
-        v(17, None)
-    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
-    """
-    def __init__(self, power_of):
-        self.power_of = power_of
-
-    def __call__(self, field_data, all_data):
-        from math import log
-        val = log(int(field_data)) / log(self.power_of)
-        if val != int(val):
-            raise ValidationError, _("This value must be a power of %s.") % self.power_of
-
-class IsValidDecimal(object):
-    def __init__(self, max_digits, decimal_places):
-        self.max_digits, self.decimal_places = max_digits, decimal_places
-
-    def __call__(self, field_data, all_data):
-        try:
-            val = Decimal(field_data)
-        except DecimalException:
-            raise ValidationError, _("Please enter a valid decimal number.")
-
-        pieces = str(val).lstrip("-").split('.')
-        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
-        digits = len(pieces[0])
-
-        if digits + decimals > self.max_digits:
-            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
-                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
-        if digits > (self.max_digits - self.decimal_places):
-            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
-                "Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places)
-        if decimals > self.decimal_places:
-            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
-                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
-
-def isValidFloat(field_data, all_data):
-    data = smart_str(field_data)
-    try:
-        float(data)
-    except ValueError:
-        raise ValidationError, _("Please enter a valid floating point number.")
-
-class HasAllowableSize(object):
-    """
-    Checks that the file-upload field data is a certain size. min_size and
-    max_size are measurements in bytes.
-    """
-    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
-        self.min_size, self.max_size = min_size, max_size
-        self.min_error_message = min_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size)
-        self.max_error_message = max_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size)
-
-    def __call__(self, field_data, all_data):
-        try:
-            content = field_data['content']
-        except TypeError:
-            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
-        if self.min_size is not None and len(content) < self.min_size:
-            raise ValidationError, self.min_error_message
-        if self.max_size is not None and len(content) > self.max_size:
-            raise ValidationError, self.max_error_message
-
-class MatchesRegularExpression(object):
-    """
-    Checks that the field matches the given regular-expression. The regex
-    should be in string format, not already compiled.
-    """
-    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
-        self.regexp = re.compile(regexp)
-        self.error_message = error_message
-
-    def __call__(self, field_data, all_data):
-        if not self.regexp.search(field_data):
-            raise ValidationError(self.error_message)
-
-class AnyValidator(object):
-    """
-    This validator tries all given validators. If any one of them succeeds,
-    validation passes. If none of them succeeds, the given message is thrown
-    as a validation error. The message is rather unspecific, so it's best to
-    specify one on instantiation.
-    """
-    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
-        if validator_list is None: validator_list = []
-        self.validator_list = validator_list
-        self.error_message = error_message
-        for v in validator_list:
-            if hasattr(v, 'always_test'):
-                self.always_test = True
-
-    def __call__(self, field_data, all_data):
-        for v in self.validator_list:
-            try:
-                v(field_data, all_data)
-                return
-            except ValidationError, e:
-                pass
-        raise ValidationError(self.error_message)
+        raise ValidationError, _("The URL %s is a broken link.") % value
 
 class URLMimeTypeCheck(object):
     "Checks that the provided URL points to a document with a listed mime type"
@@ -524,79 +100,27 @@ class URLMimeTypeCheck(object):
     def __init__(self, mime_type_list):
         self.mime_type_list = mime_type_list
 
-    def __call__(self, field_data, all_data):
-        import urllib2
+    def __call__(self, value):
+        validate_existing_url(value)
         try:
-            isValidURL(field_data, all_data)
-        except ValidationError:
-            raise
-        try:
-            info = urllib2.urlopen(field_data).info()
+            info = urllib2.urlopen(value).info()
         except (urllib2.HTTPError, urllib2.URLError):
-            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
+            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % value
         content_type = info['content-type']
         if content_type not in self.mime_type_list:
             raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
-                'url': field_data, 'contenttype': content_type}
+                'url': value, 'contenttype': content_type}
 
-class RelaxNGCompact(object):
-    "Validate against a Relax NG compact schema"
-    def __init__(self, schema_path, additional_root_element=None):
-        self.schema_path = schema_path
-        self.additional_root_element = additional_root_element
+def validate_imaga_url(value):
+    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
+    try:
+        uc(value)
+    except URLMimeTypeCheck.InvalidContentType:
+        raise ValidationError, _("The URL %s does not point to a valid image.") % value
 
-    def __call__(self, field_data, all_data):
-        import os, tempfile
-        if self.additional_root_element:
-            field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
-                'are': self.additional_root_element,
-                'data': field_data
-            }
-        filename = tempfile.mktemp() # Insecure, but nothing else worked
-        fp = open(filename, 'w')
-        fp.write(field_data)
-        fp.close()
-        if not os.path.exists(settings.JING_PATH):
-            raise Exception, "%s not found!" % settings.JING_PATH
-        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
-        errors = [line.strip() for line in p.readlines()]
-        p.close()
-        os.unlink(filename)
-        display_errors = []
-        lines = field_data.split('\n')
-        for error in errors:
-            ignored, line, level, message = error.split(':', 3)
-            # Scrape the Jing error messages to reword them more nicely.
-            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
-            if m:
-                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
-                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
-                continue
-            if message.strip() == 'text not allowed here':
-                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
-                    {'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
-            if m:
-                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
-                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            m = re.search(r'\s*unknown element "(.*?)"', message)
-            if m:
-                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
-                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            if message.strip() == 'required attributes missing':
-                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
-                    {'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
-            if m:
-                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
-                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            # Failing all those checks, use the default error message.
-            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
-            display_errors.append(display_error)
-        if len(display_errors) > 0:
-            raise ValidationError, display_errors
+def validate_float(value):
+    data = smart_str(value)
+    try:
+        float(data)
+    except ValueError:
+        raise ValidationError, _("Please enter a valid floating point number.")
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index 86763d9..afd9151 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -1,6 +1,5 @@
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-from django.core import validators
 from django.db import connection
 from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
 from django.db.models.query import Q
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 5dd11a9..9838765 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -7,6 +7,7 @@ from itertools import izip
 import django.db.models.manipulators    # Imported to register signal handler.
 import django.db.models.manager         # Ditto.
 from django.core import validators
+from django.core.validation import ValidationError, NON_FIELD_ERRORS
 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
 from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
@@ -19,6 +20,7 @@ from django.dispatch import dispatcher
 from django.utils.datastructures import SortedDict
 from django.utils.functional import curry
 from django.utils.encoding import smart_str, force_unicode, smart_unicode
+from django.utils.translation import ugettext as _
 from django.conf import settings
 
 try:
@@ -346,27 +348,62 @@ class Model(object):
 
     save_base.alters_data = True
 
-    def validate(self):
+    def clean(self, new_data=None):
+        self.to_python()
+        self.validate(new_data)
+
+    def to_python(self):
+        error_dict = {}
+        for f in self._meta.fields:
+            try:
+                value = f.to_python(getattr(self, f.attname, f.get_default()))
+                setattr(self, f.attname, value)
+            except ValidationError, e:
+                error_dict[f.name] = e.messages
+        if error_dict:
+            raise ValidationError(error_dict)
+
+    def validate(self, new_data=None):
         """
         First coerces all fields on this instance to their proper Python types.
         Then runs validation on every field. Returns a dictionary of
         field_name -> error_list.
         """
+        if new_data is not None:
+            def get_value(f):
+                if f.name in new_data:
+                    return f.to_python(new_data[f.name])
+                return getattr(self, f.attname, f.get_default())
+        else:
+            get_value = lambda f: getattr(self, f.attname, f.get_default())
         error_dict = {}
-        invalid_python = {}
         for f in self._meta.fields:
             try:
-                setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default())))
-            except validators.ValidationError, e:
+                value = get_value(f)
+                f.validate(value, instance=self)
+                if hasattr(self, 'validate_%s' % f.name):
+                    getattr(self, 'validate_%s' % f.name)(value)
+            except ValidationError, e:
                 error_dict[f.name] = e.messages
-                invalid_python[f.name] = 1
-        for f in self._meta.fields:
-            if f.name in invalid_python:
-                continue
-            errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__)
-            if errors:
-                error_dict[f.name] = errors
-        return error_dict
+
+        for un_together in self._meta.unique_together:
+            lookup = {}
+            for name in un_together:
+                if name in error_dict:
+                    break
+                f = self._meta.get_field(name)
+                lookup['%s__exact' % name] = get_value(f)
+            try:
+                qset = self.__class__._default_manager.all()
+                if self.pk:
+                    qset = qset.exclude(pk=self.pk)
+                obj = qset.get(**lookup)
+                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together)
+            except self.DoesNotExist:
+                pass
+
+        if error_dict:
+            raise ValidationError(error_dict)
 
     def _collect_sub_objects(self, seen_objs):
         """
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 8803375..6a5f4c5 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -12,6 +12,7 @@ from django.db.models import signals
 from django.db.models.query_utils import QueryWrapper
 from django.dispatch import dispatcher
 from django.conf import settings
+from django.oldforms import validators as oldvalidators
 from django.core import validators
 from django import oldforms
 from django import newforms as forms
@@ -83,13 +84,15 @@ class Field(object):
     creation_counter = 0
     auto_creation_counter = -1
 
+    validators = []
+
     def __init__(self, verbose_name=None, name=None, primary_key=False,
             max_length=None, unique=False, blank=False, null=False,
             db_index=False, core=False, rel=None, default=NOT_PROVIDED,
             editable=True, serialize=True, prepopulate_from=None,
             unique_for_date=None, unique_for_month=None, unique_for_year=None,
             validator_list=None, choices=None, radio_admin=None, help_text='',
-            db_column=None, db_tablespace=None, auto_created=False):
+            db_column=None, db_tablespace=None, auto_created=False, validators=[]):
         self.name = name
         self.verbose_name = verbose_name
         self.primary_key = primary_key
@@ -102,6 +105,7 @@ class Field(object):
         self.core, self.rel, self.default = core, rel, default
         self.editable = editable
         self.serialize = serialize
+        self.validators = validators + self.validators
         self.validator_list = validator_list or []
         self.prepopulate_from = prepopulate_from
         self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
@@ -169,28 +173,27 @@ class Field(object):
         except KeyError:
             return None
 
-    def validate_full(self, field_data, all_data):
-        """
-        Returns a list of errors for this field. This is the main interface,
-        as it encapsulates some basic validation logic used by all fields.
-        Subclasses should implement validate(), not validate_full().
-        """
-        if not self.blank and not field_data:
-            return [_('This field is required.')]
-        try:
-            self.validate(field_data, all_data)
-        except validators.ValidationError, e:
-            return e.messages
-        return []
-
-    def validate(self, field_data, all_data):
+    def validate(self, value, instance=None):
         """
-        Raises validators.ValidationError if field_data has any errors.
+        Raises validators.ValidationError if value has any errors.
         Subclasses should override this to specify field-specific validation
-        logic. This method should assume field_data has already been converted
+        logic. This method should assume value has already been converted
         into the appropriate data type by Field.to_python().
         """
-        pass
+        if not self.blank and self.editable and not value:
+            raise validators.ValidationError(_('This field is required.'))
+        elist = []
+        for validator in self.validators:
+            validator(value)    
+        if self.unique and instance:
+            try:
+                qset = instance.__class__._default_manager.all()
+                if instance.pk:
+                    qset = qset.exclude(pk=instance.pk)
+                obj = qset.get(**{'%s__exact' % self.name : value})
+                raise validators.ValidationError(_('This field must be unique'))
+            except instance.DoesNotExist:
+                pass
 
     def set_attributes_from_name(self, name):
         self.name = name
@@ -345,7 +348,7 @@ class Field(object):
                     core_field_names.extend(f.get_manipulator_field_names(name_prefix))
             # Now, if there are any, add the validator to this FormField.
             if core_field_names:
-                params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
+                params['validator_list'].append(oldvalidators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
 
         # Finally, add the field_names.
         field_names = self.get_manipulator_field_names(name_prefix)
@@ -541,7 +544,6 @@ class DateField(Field):
             return value.date()
         if isinstance(value, datetime.date):
             return value
-        validators.isValidANSIDate(value, None)
         try:
             return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
         except ValueError:
@@ -734,6 +736,7 @@ class DecimalField(Field):
         return super(DecimalField, self).formfield(**defaults)
 
 class EmailField(CharField):
+    validators = [validators.validate_email]
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 75)
         CharField.__init__(self, *args, **kwargs)
@@ -741,9 +744,6 @@ class EmailField(CharField):
     def get_manipulator_field_objs(self):
         return [oldforms.EmailField]
 
-    def validate(self, field_data, all_data):
-        validators.isValidEmail(field_data, all_data)
-
     def formfield(self, **kwargs):
         defaults = {'form_class': forms.EmailField}
         defaults.update(kwargs)
@@ -777,7 +777,7 @@ class FileField(Field):
                         self.always_test = True
                     def __call__(self, field_data, all_data):
                         if not all_data.get(self.other_file_field_name, False):
-                            c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
+                            c = oldvalidators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
                             c(field_data, all_data)
                 # First, get the core fields, if any.
                 core_field_names = []
@@ -788,7 +788,7 @@ class FileField(Field):
                 if core_field_names:
                     field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
             else:
-                v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
+                v = oldvalidators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
                 v.always_test = True
                 field_list[0].validator_list.append(v)
                 field_list[0].is_required = field_list[1].is_required = False
@@ -946,6 +946,8 @@ class IntegerField(Field):
 
 class IPAddressField(Field):
     empty_strings_allowed = False
+    validators = [validators.validate_ip_address4]
+
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = 15
         Field.__init__(self, *args, **kwargs)
@@ -956,9 +958,6 @@ class IPAddressField(Field):
     def get_internal_type(self):
         return "IPAddressField"
 
-    def validate(self, field_data, all_data):
-        validators.isValidIPAddress4(field_data, None)
-
     def formfield(self, **kwargs):
         defaults = {'form_class': forms.IPAddressField}
         defaults.update(kwargs)
@@ -989,15 +988,14 @@ class NullBooleanField(Field):
         return super(NullBooleanField, self).formfield(**defaults)
 
 class PhoneNumberField(IntegerField):
+    validators = [validators.validate_phone_number]
+
     def get_manipulator_field_objs(self):
         return [oldforms.PhoneNumberField]
 
     def get_internal_type(self):
         return "PhoneNumberField"
 
-    def validate(self, field_data, all_data):
-        validators.isValidPhone(field_data, all_data)
-
     def formfield(self, **kwargs):
         from django.contrib.localflavor.us.forms import USPhoneNumberField
         defaults = {'form_class': USPhoneNumberField}
@@ -1029,9 +1027,9 @@ class PositiveSmallIntegerField(IntegerField):
         return super(PositiveSmallIntegerField, self).formfield(**defaults)
 
 class SlugField(CharField):
+    validators = [validators.validate_slug]
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 50)
-        kwargs.setdefault('validator_list', []).append(validators.isSlug)
         # Set db_index=True unless it's been set manually.
         if 'db_index' not in kwargs:
             kwargs['db_index'] = True
@@ -1127,7 +1125,7 @@ class URLField(CharField):
     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 200)
         if verify_exists:
-            kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
+            kwargs.setdefault('validators', []).append(validators.validate_existing_url)
         self.verify_exists = verify_exists
         CharField.__init__(self, verbose_name, name, **kwargs)
 
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 41d6251..cb1ad03 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -792,7 +792,7 @@ class ManyToManyField(RelatedField, Field):
         objects = mod._default_manager.in_bulk(pks)
         if len(objects) != len(pks):
             badkeys = [k for k in pks if k not in objects]
-            raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
+            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
                     "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
                 'self': self.verbose_name,
                 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
index 0d9c68f..6f881f0 100644
--- a/django/newforms/__init__.py
+++ b/django/newforms/__init__.py
@@ -10,7 +10,7 @@ TODO:
     "This form field requires foo.js" and form.js_includes()
 """
 
-from util import ValidationError
+from django.core.validation import ValidationError
 from widgets import *
 from fields import *
 from forms import *
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index 08e8b84..a7426f7 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -19,8 +19,8 @@ except NameError:
 
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
+from django.core.validation import ValidationError, ErrorList, TypeCoercionError
 
-from util import ErrorList, ValidationError
 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
 
 
@@ -41,6 +41,7 @@ EMPTY_VALUES = (None, '')
 
 class Field(object):
     widget = TextInput # Default widget to use when rendering this type of Field.
+    validators = []
     hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
     default_error_messages = {
         'required': _(u'This field is required.'),
@@ -51,7 +52,7 @@ class Field(object):
     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, validators=[]):
         # required -- Boolean that specifies whether the field is required.
         #             True by default.
         # widget -- A Widget class, or instance of a Widget class, that should
@@ -65,8 +66,10 @@ class Field(object):
         # initial -- A value to use in this Field's initial display. This value
         #            is *not* used as a fallback if data isn't given.
         # help_text -- An optional string to use as "help text" for this Field.
+        # validators -- Optional list of additional validator functions
         if label is not None:
             label = smart_unicode(label)
+        self.validators = self.validators + validators
         self.required, self.label, self.initial = required, label, initial
         self.help_text = smart_unicode(help_text or '')
         widget = widget or self.widget
@@ -94,6 +97,23 @@ class Field(object):
         messages.update(error_messages or {})
         self.error_messages = messages
 
+    def to_python(self, value):
+        if value in EMPTY_VALUES:
+            return None
+        return smart_unicode(value)
+
+    def validate(self, value):
+        if self.required and value in EMPTY_VALUES:
+            raise ValidationError(self.error_messages['required'])
+        elist = ErrorList()
+        for validator in self.validators:
+            try:
+                validator(value)
+            except ValidationError, e:
+                elist.extend(e.messages)
+        if elist:
+            raise ValidationError(elist)
+
     def clean(self, value):
         """
         Validates the given value and returns its "cleaned" value as an
@@ -101,8 +121,8 @@ class Field(object):
 
         Raises ValidationError for any errors.
         """
-        if self.required and value in EMPTY_VALUES:
-            raise ValidationError(self.error_messages['required'])
+        value = self.to_python(value)
+        self.validate(value)
         return value
 
     def widget_attrs(self, widget):
@@ -129,18 +149,21 @@ class CharField(Field):
         self.max_length, self.min_length = max_length, min_length
         super(CharField, self).__init__(*args, **kwargs)
 
-    def clean(self, value):
-        "Validates max_length and min_length. Returns a Unicode object."
-        super(CharField, self).clean(value)
+    def to_python(self, value):
         if value in EMPTY_VALUES:
             return u''
-        value = smart_unicode(value)
+        return smart_unicode(value)
+
+    def validate(self, value):
+        "Validates max_length and min_length. Returns a Unicode object."
+        super(CharField, self).validate(value)
         value_length = len(value)
+        if value_length == 0 and not self.required:
+            return 
         if self.max_length is not None and value_length > self.max_length:
             raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
         if self.min_length is not None and value_length < self.min_length:
             raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
-        return value
 
     def widget_attrs(self, widget):
         if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
@@ -158,23 +181,25 @@ class IntegerField(Field):
         self.max_value, self.min_value = max_value, min_value
         super(IntegerField, self).__init__(*args, **kwargs)
 
-    def clean(self, value):
-        """
-        Validates that int() can be called on the input. Returns the result
-        of int(). Returns None for empty values.
-        """
-        super(IntegerField, self).clean(value)
+    def to_python(self, value):
         if value in EMPTY_VALUES:
             return None
         try:
-            value = int(str(value))
+            return int(smart_str(value))
         except (ValueError, TypeError):
-            raise ValidationError(self.error_messages['invalid'])
+            raise TypeCoercionError(self.error_messages['invalid'])
+
+    def validate(self, value):
+        """
+        Validates that int() can be called on the input. Returns the result
+        of int(). Returns None for empty values.
+        """
+        super(IntegerField, self).validate(value)
+        if value is None: return
         if self.max_value is not None and value > self.max_value:
             raise ValidationError(self.error_messages['max_value'] % self.max_value)
         if self.min_value is not None and value < self.min_value:
             raise ValidationError(self.error_messages['min_value'] % self.min_value)
-        return value
 
 class FloatField(Field):
     default_error_messages = {
@@ -187,23 +212,25 @@ class FloatField(Field):
         self.max_value, self.min_value = max_value, min_value
         Field.__init__(self, *args, **kwargs)
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates that float() can be called on the input. Returns a float.
         Returns None for empty values.
         """
-        super(FloatField, self).clean(value)
-        if not self.required and value in EMPTY_VALUES:
+        if value in EMPTY_VALUES:
             return None
         try:
-            value = float(value)
+            return float(value)
         except (ValueError, TypeError):
-            raise ValidationError(self.error_messages['invalid'])
+            raise TypeCoercionError(self.error_messages['invalid'])
+
+    def validate(self, value):
+        super(FloatField, self).validate(value)
+        if value is None: return
         if self.max_value is not None and value > self.max_value:
             raise ValidationError(self.error_messages['max_value'] % self.max_value)
         if self.min_value is not None and value < self.min_value:
             raise ValidationError(self.error_messages['min_value'] % self.min_value)
-        return value
 
 class DecimalField(Field):
     default_error_messages = {
@@ -220,21 +247,24 @@ class DecimalField(Field):
         self.max_digits, self.decimal_places = max_digits, decimal_places
         Field.__init__(self, *args, **kwargs)
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates that the input is a decimal number. Returns a Decimal
         instance. Returns None for empty values. Ensures that there are no more
         than max_digits in the number, and no more than decimal_places digits
         after the decimal point.
         """
-        super(DecimalField, self).clean(value)
-        if not self.required and value in EMPTY_VALUES:
+        if value in EMPTY_VALUES:
             return None
         value = smart_str(value).strip()
         try:
-            value = Decimal(value)
+            return Decimal(value)
         except DecimalException:
-            raise ValidationError(self.error_messages['invalid'])
+            raise TypeCoercionError(self.error_messages['invalid'])
+
+    def validate(self, value):
+        super(DecimalField, self).validate(value)
+        if value is None: return
         pieces = str(value).lstrip("-").split('.')
         decimals = (len(pieces) == 2) and len(pieces[1]) or 0
         digits = len(pieces[0])
@@ -248,7 +278,6 @@ class DecimalField(Field):
             raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
         if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
             raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
-        return value
 
 DEFAULT_DATE_INPUT_FORMATS = (
     '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
@@ -267,12 +296,11 @@ class DateField(Field):
         super(DateField, self).__init__(*args, **kwargs)
         self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates that the input can be converted to a date. Returns a Python
         datetime.date object.
         """
-        super(DateField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         if isinstance(value, datetime.datetime):
@@ -284,7 +312,7 @@ class DateField(Field):
                 return datetime.date(*time.strptime(value, format)[:3])
             except ValueError:
                 continue
-        raise ValidationError(self.error_messages['invalid'])
+        raise TypeCoercionError(self.error_messages['invalid'])
 
 DEFAULT_TIME_INPUT_FORMATS = (
     '%H:%M:%S',     # '14:30:59'
@@ -300,12 +328,11 @@ class TimeField(Field):
         super(TimeField, self).__init__(*args, **kwargs)
         self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates that the input can be converted to a time. Returns a Python
         datetime.time object.
         """
-        super(TimeField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         if isinstance(value, datetime.time):
@@ -315,7 +342,7 @@ class TimeField(Field):
                 return datetime.time(*time.strptime(value, format)[3:6])
             except ValueError:
                 continue
-        raise ValidationError(self.error_messages['invalid'])
+        raise TypeCoercionError(self.error_messages['invalid'])
 
 DEFAULT_DATETIME_INPUT_FORMATS = (
     '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
@@ -339,12 +366,11 @@ class DateTimeField(Field):
         super(DateTimeField, self).__init__(*args, **kwargs)
         self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates that the input can be converted to a datetime. Returns a
         Python datetime.datetime object.
         """
-        super(DateTimeField, self).clean(value)
         if value in EMPTY_VALUES:
             return None
         if isinstance(value, datetime.datetime):
@@ -362,7 +388,7 @@ class DateTimeField(Field):
                 return datetime.datetime(*time.strptime(value, format)[:6])
             except ValueError:
                 continue
-        raise ValidationError(self.error_messages['invalid'])
+        raise TypeCoercionError(self.error_messages['invalid'])
 
 class RegexField(CharField):
     def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
@@ -381,17 +407,16 @@ class RegexField(CharField):
             regex = re.compile(regex)
         self.regex = regex
 
-    def clean(self, value):
+    def validate(self, value):
         """
         Validates that the input matches the regular expression. Returns a
         Unicode object.
         """
-        value = super(RegexField, self).clean(value)
-        if value == u'':
-            return value
+        super(RegexField, self).validate(value)
+        if value in EMPTY_VALUES:
+            return u''
         if not self.regex.search(value):
             raise ValidationError(self.error_messages['invalid'])
-        return value
 
 email_re = re.compile(
     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
@@ -431,58 +456,60 @@ class FileField(Field):
     widget = FileInput
     default_error_messages = {
         'invalid': _(u"No file was submitted. Check the encoding type on the form."),
-        'missing': _(u"No file was submitted."),
         'empty': _(u"The submitted file is empty."),
     }
 
-    def __init__(self, *args, **kwargs):
-        super(FileField, self).__init__(*args, **kwargs)
-
-    def clean(self, data, initial=None):
-        super(FileField, self).clean(initial or data)
+    def to_python(self, data, initial=None):
         if not self.required and data in EMPTY_VALUES:
             return None
         elif not data and initial:
             return initial
+        elif not data:
+            return None
+
         try:
             f = UploadedFile(data['filename'], data['content'])
         except TypeError:
-            raise ValidationError(self.error_messages['invalid'])
+            raise TypeCoercionError(self.error_messages['invalid'])
         except KeyError:
-            raise ValidationError(self.error_messages['missing'])
+            raise ValidationError(self.error_messages['required'])
         if not f.content:
             raise ValidationError(self.error_messages['empty'])
         return f
 
+    def clean(self, data, initial=None):
+        "overriden clean to provide extra argument initial"
+        value = self.to_python(data, initial)
+        self.validate(value)
+        return value
+
 class ImageField(FileField):
     default_error_messages = {
         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
     }
 
-    def clean(self, data, initial=None):
+    def validate(self, value):
         """
         Checks that the file-upload field data contains a valid image (GIF, JPG,
         PNG, possibly others -- whatever the Python Imaging Library supports).
         """
-        f = super(ImageField, self).clean(data, initial)
-        if f is None:
-            return None
-        elif not data and initial:
-            return initial
+        super(ImageField, self).validate(value)
+
+        if value is None:
+            return
         from PIL import Image
         from cStringIO import StringIO
         try:
             # load() is the only method that can spot a truncated JPEG,
             #  but it cannot be called sanely after verify()
-            trial_image = Image.open(StringIO(f.content))
+            trial_image = Image.open(StringIO(value.content))
             trial_image.load()
             # verify() is the only method that can spot a corrupt PNG,
             #  but it must be called immediately after the constructor
-            trial_image = Image.open(StringIO(f.content))
+            trial_image = Image.open(StringIO(value.content))
             trial_image.verify()
         except Exception: # Python Imaging Library doesn't recognize it as an image
             raise ValidationError(self.error_messages['invalid_image'])
-        return f
 
 url_re = re.compile(
     r'^https?://' # http:// or https://
@@ -505,13 +532,17 @@ class URLField(RegexField):
         self.verify_exists = verify_exists
         self.user_agent = validator_user_agent
 
-    def clean(self, value):
-        # If no URL scheme given, assume http://
+    def to_python(self, value):
+        value = super(URLField, self).to_python(value)
         if value and '://' not in value:
             value = u'http://%s' % value
-        value = super(URLField, self).clean(value)
-        if value == u'':
-            return value
+        return value
+
+    def validate(self, value):
+        # If no URL scheme given, assume http://
+        super(URLField, self).validate(value)
+        if value in EMPTY_VALUES:
+            return
         if self.verify_exists:
             import urllib2
             from django.conf import settings
@@ -529,14 +560,14 @@ class URLField(RegexField):
                 raise ValidationError(self.error_messages['invalid'])
             except: # urllib2.URLError, httplib.InvalidURL, etc.
                 raise ValidationError(self.error_messages['invalid_link'])
-        return value
 
 class BooleanField(Field):
     widget = CheckboxInput
 
-    def clean(self, value):
+    def to_python(self, value):
         """Returns a Python boolean object."""
-        super(BooleanField, self).clean(value)
+        if self.required and value in EMPTY_VALUES:
+            raise ValidationError(self.error_messages['required'])
         # 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.
@@ -544,16 +575,23 @@ class BooleanField(Field):
             return False
         return bool(value)
 
+
 class NullBooleanField(BooleanField):
     """
     A field whose valid values are None, True and False. Invalid values are
     cleaned to None.
+
+    Note that validation doesn't apply here.
     """
     widget = NullBooleanSelect
 
-    def clean(self, value):
+    def to_python(self, value):
         return {True: True, False: False}.get(value, None)
 
+    def validate(self, value):
+        pass
+
+
 class ChoiceField(Field):
     widget = Select
     default_error_messages = {
@@ -577,20 +615,13 @@ class ChoiceField(Field):
 
     choices = property(_get_choices, _set_choices)
 
-    def clean(self, value):
-        """
-        Validates that the input is in self.choices.
-        """
-        value = super(ChoiceField, self).clean(value)
-        if value in EMPTY_VALUES:
-            value = u''
-        value = smart_unicode(value)
-        if value == u'':
-            return value
+    def validate(self, value):
+        super(ChoiceField, self).validate(value)
+        if value in EMPTY_VALUES and not self.required:
+            return u''
         valid_values = set([smart_unicode(k) for k, v in self.choices])
         if value not in valid_values:
             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
-        return value
 
 class MultipleChoiceField(ChoiceField):
     hidden_widget = MultipleHiddenInput
@@ -600,23 +631,24 @@ class MultipleChoiceField(ChoiceField):
         'invalid_list': _(u'Enter a list of values.'),
     }
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates that the input is a list or tuple.
         """
-        if self.required and not value:
-            raise ValidationError(self.error_messages['required'])
-        elif not self.required and not value:
+        if not value:
             return []
         if not isinstance(value, (list, tuple)):
-            raise ValidationError(self.error_messages['invalid_list'])
-        new_value = [smart_unicode(val) for val in value]
+            raise TypeCoercionError(self.error_messages['invalid_list'])
+        return [smart_unicode(val) for val in value]
+
+    def validate(self, value):
         # Validate that each value in the value list is in self.choices.
+        if self.required and value == []:
+            raise ValidationError(self.error_messages['required'])
         valid_values = set([smart_unicode(k) for k, v in self.choices])
-        for val in new_value:
+        for val in value:
             if val not in valid_values:
                 raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
-        return new_value
 
 class ComboField(Field):
     """
@@ -631,15 +663,19 @@ class ComboField(Field):
             f.required = False
         self.fields = fields
 
-    def clean(self, value):
+    def to_python(self, value):
+        for field in self.fields:
+            value = field.to_python(value)
+        return value
+
+    def validate(self, value):
         """
         Validates the given value against all of self.fields, which is a
         list of Field instances.
         """
-        super(ComboField, self).clean(value)
+        super(ComboField, self).validate(value)
         for field in self.fields:
-            value = field.clean(value)
-        return value
+            field.validate(value)
 
 class MultiValueField(Field):
     """
@@ -671,7 +707,7 @@ class MultiValueField(Field):
             f.required = False
         self.fields = fields
 
-    def clean(self, value):
+    def to_python(self, value):
         """
         Validates every value in the given list. A value is validated against
         the corresponding Field in self.fields.
@@ -685,11 +721,12 @@ class MultiValueField(Field):
         if not value or isinstance(value, (list, tuple)):
             if not value or not [v for v in value if v not in EMPTY_VALUES]:
                 if self.required:
-                    raise ValidationError(self.error_messages['required'])
+                    return None
                 else:
                     return self.compress([])
         else:
-            raise ValidationError(self.error_messages['invalid'])
+            raise TypeCoercionError(self.error_messages['invalid'])
+
         for i, field in enumerate(self.fields):
             try:
                 field_value = value[i]
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index 2c481e4..1c06f3c 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -8,14 +8,14 @@ from django.utils.datastructures import SortedDict
 from django.utils.html import escape
 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
 from django.utils.safestring import mark_safe
+from django.core.validation import ValidationError, ErrorList, NON_FIELD_ERRORS
 
 from fields import Field, FileField
 from widgets import TextInput, Textarea
-from util import flatatt, ErrorDict, ErrorList, ValidationError
+from util import flatatt, ErrorDict
 
 __all__ = ('BaseForm', 'Form')
 
-NON_FIELD_ERRORS = '__all__'
 
 def pretty_name(name):
     "Converts 'first_name' to 'First name'"
@@ -205,22 +205,32 @@ class BaseForm(StrAndUnicode):
                 else:
                     value = field.clean(value)
                 self.cleaned_data[name] = value
+                # FIXME deprecated - keeping this here for backwards compatibility
                 if hasattr(self, 'clean_%s' % name):
                     value = getattr(self, 'clean_%s' % name)()
                     self.cleaned_data[name] = value
+
+                if hasattr(self, 'validate_%s' % name):
+                    getattr(self, 'validate_%s' % name)(value)
             except ValidationError, e:
                 self._errors[name] = e.messages
                 if name in self.cleaned_data:
                     del self.cleaned_data[name]
         try:
-            self.cleaned_data = self.clean()
+            self.validate()
         except ValidationError, e:
-            self._errors[NON_FIELD_ERRORS] = e.messages
+            if hasattr(e, 'message_dict'):
+                for k, v in e.message_dict.items():
+                    self._errors.setdefault(k, []).extend(v)
+            else:
+                self._errors[NON_FIELD_ERRORS] = e.messages
         if self._errors:
             delattr(self, 'cleaned_data')
 
     def clean(self):
         """
+        FIXME: deprecated, use validate() instead
+
         Hook for doing any extra form-wide cleaning after Field.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
@@ -228,6 +238,9 @@ class BaseForm(StrAndUnicode):
         """
         return self.cleaned_data
 
+    def validate(self):
+        self.cleaned_data = self.clean()
+
     def is_multipart(self):
         """
         Returns True if the form needs to be multipart-encrypted, i.e. it has
diff --git a/django/newforms/models.py b/django/newforms/models.py
index 0590839..1c6e301 100644
--- a/django/newforms/models.py
+++ b/django/newforms/models.py
@@ -9,8 +9,8 @@ from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
 from django.utils.datastructures import SortedDict
 from django.core.exceptions import ImproperlyConfigured
+from django.core.validation import ValidationError, ErrorList, TypeCoercionError
 
-from util import ValidationError, ErrorList
 from forms import BaseForm, get_declared_fields
 from fields import Field, ChoiceField, EMPTY_VALUES
 from widgets import Select, SelectMultiple, MultipleHiddenInput
@@ -257,6 +257,12 @@ class BaseModelForm(BaseForm):
             object_data.update(initial)
         BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
 
+    def validate(self):
+        super(BaseModelForm, self).validate()
+        if self._errors:
+            return
+        self.instance.clean(self.cleaned_data)
+
     def save(self, commit=True):
         """
         Saves this ``form``'s cleaned_data into model instance
@@ -354,9 +360,10 @@ class ModelChoiceField(ChoiceField):
 
     choices = property(_get_choices, _set_choices)
 
-    def clean(self, value):
-        Field.clean(self, value)
-        if value in EMPTY_VALUES:
+    def to_python(self, value):
+        if self.required and value in EMPTY_VALUES:
+            raise ValidationError(self.error_messages['required'])
+        elif value in EMPTY_VALUES:
             return None
         try:
             value = self.queryset.get(pk=value)
@@ -364,6 +371,9 @@ class ModelChoiceField(ChoiceField):
             raise ValidationError(self.error_messages['invalid_choice'])
         return value
 
+    def validate(self, value):
+        pass
+
 class ModelMultipleChoiceField(ModelChoiceField):
     """A MultipleChoiceField whose choices are a model QuerySet."""
     hidden_widget = MultipleHiddenInput
@@ -380,13 +390,13 @@ class ModelMultipleChoiceField(ModelChoiceField):
             cache_choices, required, widget, label, initial, help_text,
             *args, **kwargs)
 
-    def clean(self, value):
+    def to_python(self, value):
         if self.required and not value:
             raise ValidationError(self.error_messages['required'])
         elif not self.required and not value:
             return []
         if not isinstance(value, (list, tuple)):
-            raise ValidationError(self.error_messages['list'])
+            raise TypeCoercionError(self.error_messages['list'])
         final_values = []
         for val in value:
             try:
@@ -396,3 +406,4 @@ class ModelMultipleChoiceField(ModelChoiceField):
             else:
                 final_values.append(obj)
         return final_values
+
diff --git a/django/newforms/util.py b/django/newforms/util.py
index b3edf41..89b93d6 100644
--- a/django/newforms/util.py
+++ b/django/newforms/util.py
@@ -30,40 +30,3 @@ class ErrorDict(dict, StrAndUnicode):
     def as_text(self):
         return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u'  * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
 
-class ErrorList(list, StrAndUnicode):
-    """
-    A collection of errors that knows how to display itself in various formats.
-    """
-    def __unicode__(self):
-        return self.as_ul()
-
-    def as_ul(self):
-        if not self: return u''
-        return mark_safe(u'<ul class="errorlist">%s</ul>'
-                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
-
-    def as_text(self):
-        if not self: return u''
-        return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
-
-    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/django/oldforms/__init__.py b/django/oldforms/__init__.py
index fc87271..b7b0d10 100644
--- a/django/oldforms/__init__.py
+++ b/django/oldforms/__init__.py
@@ -1,4 +1,4 @@
-from django.core import validators
+from django.oldforms import validators
 from django.core.exceptions import PermissionDenied
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
diff --git a/django/oldforms/validators.py b/django/oldforms/validators.py
new file mode 100644
index 0000000..0ff85c7
--- /dev/null
+++ b/django/oldforms/validators.py
@@ -0,0 +1,589 @@
+"""
+A library of validators that return None and raise ValidationError when the
+provided data isn't valid.
+
+Validators may be callable classes, and they may have an 'always_test'
+attribute. If an 'always_test' attribute exists (regardless of value), the
+validator will *always* be run, regardless of whether its associated
+form field is required.
+"""
+
+import urllib2
+import re
+try:
+    from decimal import Decimal, DecimalException
+except ImportError:
+    from django.utils._decimal import Decimal, DecimalException    # Python 2.3
+
+from django.conf import settings
+from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
+from django.utils.functional import Promise, lazy
+from django.utils.encoding import force_unicode, smart_str
+from django.core.validation import ValidationError
+
+_datere = r'\d{4}-\d{1,2}-\d{1,2}'
+_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
+alnum_re = re.compile(r'^\w+$')
+alnumurl_re = re.compile(r'^[-\w/]+$')
+ansi_date_re = re.compile('^%s$' % _datere)
+ansi_time_re = re.compile('^%s$' % _timere)
+ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
+email_re = re.compile(
+    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
+    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
+    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
+integer_re = re.compile(r'^-?\d+$')
+ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
+phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
+slug_re = re.compile(r'^[-\w]+$')
+url_re = re.compile(r'^https?://\S+$')
+
+lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
+
+
+class CriticalValidationError(Exception):
+    def __init__(self, message):
+        "ValidationError can be passed a string or a list."
+        if isinstance(message, list):
+            self.messages = [force_unicode(msg) for msg in message]
+        else:
+            assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
+            self.messages = [force_unicode(message)]
+
+    def __str__(self):
+        return str(self.messages)
+
+
+def isAlphaNumeric(field_data, all_data):
+    if not alnum_re.search(field_data):
+        raise ValidationError, _("This value must contain only letters, numbers and underscores.")
+
+def isAlphaNumericURL(field_data, all_data):
+    if not alnumurl_re.search(field_data):
+        raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
+
+def isSlug(field_data, all_data):
+    if not slug_re.search(field_data):
+        raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
+
+def isLowerCase(field_data, all_data):
+    if field_data.lower() != field_data:
+        raise ValidationError, _("Uppercase letters are not allowed here.")
+
+def isUpperCase(field_data, all_data):
+    if field_data.upper() != field_data:
+        raise ValidationError, _("Lowercase letters are not allowed here.")
+
+def isCommaSeparatedIntegerList(field_data, all_data):
+    for supposed_int in field_data.split(','):
+        try:
+            int(supposed_int)
+        except ValueError:
+            raise ValidationError, _("Enter only digits separated by commas.")
+
+def isCommaSeparatedEmailList(field_data, all_data):
+    """
+    Checks that field_data is a string of e-mail addresses separated by commas.
+    Blank field_data values will not throw a validation error, and whitespace
+    is allowed around the commas.
+    """
+    for supposed_email in field_data.split(','):
+        try:
+            isValidEmail(supposed_email.strip(), '')
+        except ValidationError:
+            raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
+
+def isValidIPAddress4(field_data, all_data):
+    if not ip4_re.search(field_data):
+        raise ValidationError, _("Please enter a valid IP address.")
+
+def isNotEmpty(field_data, all_data):
+    if field_data.strip() == '':
+        raise ValidationError, _("Empty values are not allowed here.")
+
+def isOnlyDigits(field_data, all_data):
+    if not field_data.isdigit():
+        raise ValidationError, _("Non-numeric characters aren't allowed here.")
+
+def isNotOnlyDigits(field_data, all_data):
+    if field_data.isdigit():
+        raise ValidationError, _("This value can't be comprised solely of digits.")
+
+def isInteger(field_data, all_data):
+    # This differs from isOnlyDigits because this accepts the negative sign
+    if not integer_re.search(field_data):
+        raise ValidationError, _("Enter a whole number.")
+
+def isOnlyLetters(field_data, all_data):
+    if not field_data.isalpha():
+        raise ValidationError, _("Only alphabetical characters are allowed here.")
+
+def _isValidDate(date_string):
+    """
+    A helper function used by isValidANSIDate and isValidANSIDatetime to
+    check if the date is valid.  The date string is assumed to already be in
+    YYYY-MM-DD format.
+    """
+    from datetime import date
+    # Could use time.strptime here and catch errors, but datetime.date below
+    # produces much friendlier error messages.
+    year, month, day = map(int, date_string.split('-'))
+    # This check is needed because strftime is used when saving the date
+    # value to the database, and strftime requires that the year be >=1900.
+    if year < 1900:
+        raise ValidationError, _('Year must be 1900 or later.')
+    try:
+        date(year, month, day)
+    except ValueError, e:
+        msg = _('Invalid date: %s') % _(str(e))
+        raise ValidationError, msg
+
+def isValidANSIDate(field_data, all_data):
+    if not ansi_date_re.search(field_data):
+        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
+    _isValidDate(field_data)
+
+def isValidANSITime(field_data, all_data):
+    if not ansi_time_re.search(field_data):
+        raise ValidationError, _('Enter a valid time in HH:MM format.')
+
+def isValidANSIDatetime(field_data, all_data):
+    if not ansi_datetime_re.search(field_data):
+        raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+    _isValidDate(field_data.split()[0])
+
+def isValidEmail(field_data, all_data):
+    if not email_re.search(field_data):
+        raise ValidationError, _('Enter a valid e-mail address.')
+
+def isValidImage(field_data, all_data):
+    """
+    Checks that the file-upload field data contains a valid image (GIF, JPG,
+    PNG, possibly others -- whatever the Python Imaging Library supports).
+    """
+    from PIL import Image
+    from cStringIO import StringIO
+    try:
+        content = field_data['content']
+    except TypeError:
+        raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
+    try:
+        # load() is the only method that can spot a truncated JPEG,
+        #  but it cannot be called sanely after verify()
+        trial_image = Image.open(StringIO(content))
+        trial_image.load()
+        # verify() is the only method that can spot a corrupt PNG,
+        #  but it must be called immediately after the constructor
+        trial_image = Image.open(StringIO(content))
+        trial_image.verify()
+    except Exception: # Python Imaging Library doesn't recognize it as an image
+        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
+
+def isValidImageURL(field_data, all_data):
+    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
+    try:
+        uc(field_data, all_data)
+    except URLMimeTypeCheck.InvalidContentType:
+        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
+
+def isValidPhone(field_data, all_data):
+    if not phone_re.search(field_data):
+        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
+
+def isValidQuicktimeVideoURL(field_data, all_data):
+    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
+    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
+    try:
+        uc(field_data, all_data)
+    except URLMimeTypeCheck.InvalidContentType:
+        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
+
+def isValidURL(field_data, all_data):
+    if not url_re.search(field_data):
+        raise ValidationError, _("A valid URL is required.")
+
+def isValidHTML(field_data, all_data):
+    import urllib, urllib2
+    try:
+        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
+    except:
+        # Validator or Internet connection is unavailable. Fail silently.
+        return
+    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
+    if html_is_valid:
+        return
+    from xml.dom.minidom import parseString
+    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
+    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
+
+def isWellFormedXml(field_data, all_data):
+    from xml.dom.minidom import parseString
+    try:
+        parseString(field_data)
+    except Exception, e: # Naked except because we're not sure what will be thrown
+        raise ValidationError, _("Badly formed XML: %s") % str(e)
+
+def isWellFormedXmlFragment(field_data, all_data):
+    isWellFormedXml('<root>%s</root>' % field_data, all_data)
+
+def isExistingURL(field_data, all_data):
+    try:
+        headers = {
+            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+            "Accept-Language" : "en-us,en;q=0.5",
+            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+            "Connection" : "close",
+            "User-Agent": settings.URL_VALIDATOR_USER_AGENT
+            }
+        req = urllib2.Request(field_data,None, headers)
+        u = urllib2.urlopen(req)
+    except ValueError:
+        raise ValidationError, _("Invalid URL: %s") % field_data
+    except urllib2.HTTPError, e:
+        # 401s are valid; they just mean authorization is required.
+        # 301 and 302 are redirects; they just mean look somewhere else.
+        if str(e.code) not in ('401','301','302'):
+            raise ValidationError, _("The URL %s is a broken link.") % field_data
+    except: # urllib2.URLError, httplib.InvalidURL, etc.
+        raise ValidationError, _("The URL %s is a broken link.") % field_data
+
+def isValidUSState(field_data, all_data):
+    "Checks that the given string is a valid two-letter U.S. state abbreviation"
+    states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
+    if field_data.upper() not in states:
+        raise ValidationError, _("Enter a valid U.S. state abbreviation.")
+
+def hasNoProfanities(field_data, all_data):
+    """
+    Checks that the given string has no profanities in it. This does a simple
+    check for whether each profanity exists within the string, so 'fuck' will
+    catch 'motherfucker' as well. Raises a ValidationError such as:
+        Watch your mouth! The words "f--k" and "s--t" are not allowed here.
+    """
+    field_data = field_data.lower() # normalize
+    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
+    if words_seen:
+        from django.utils.text import get_text_list
+        plural = len(words_seen)
+        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
+            "Watch your mouth! The words %s are not allowed here.", plural) % \
+            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
+
+class AlwaysMatchesOtherField(object):
+    def __init__(self, other_field_name, error_message=None):
+        self.other = other_field_name
+        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
+        self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        if field_data != all_data[self.other]:
+            raise ValidationError, self.error_message
+
+class ValidateIfOtherFieldEquals(object):
+    def __init__(self, other_field, other_value, validator_list):
+        self.other_field, self.other_value = other_field, other_value
+        self.validator_list = validator_list
+        self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        if self.other_field in all_data and all_data[self.other_field] == self.other_value:
+            for v in self.validator_list:
+                v(field_data, all_data)
+
+class RequiredIfOtherFieldNotGiven(object):
+    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
+        self.other, self.error_message = other_field_name, error_message
+        self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        if not all_data.get(self.other, False) and not field_data:
+            raise ValidationError, self.error_message
+
+class RequiredIfOtherFieldsGiven(object):
+    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
+        self.other, self.error_message = other_field_names, error_message
+        self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        for field in self.other:
+            if all_data.get(field, False) and not field_data:
+                raise ValidationError, self.error_message
+
+class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
+    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
+    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
+        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
+
+class RequiredIfOtherFieldEquals(object):
+    def __init__(self, other_field, other_value, error_message=None, other_label=None):
+        self.other_field = other_field
+        self.other_value = other_value
+        other_label = other_label or other_value
+        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
+            'field': other_field, 'value': other_label})
+        self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
+            raise ValidationError(self.error_message)
+
+class RequiredIfOtherFieldDoesNotEqual(object):
+    def __init__(self, other_field, other_value, other_label=None, error_message=None):
+        self.other_field = other_field
+        self.other_value = other_value
+        other_label = other_label or other_value
+        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
+            'field': other_field, 'value': other_label})
+        self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
+            raise ValidationError(self.error_message)
+
+class IsLessThanOtherField(object):
+    def __init__(self, other_field_name, error_message):
+        self.other, self.error_message = other_field_name, error_message
+
+    def __call__(self, field_data, all_data):
+        if field_data > all_data[self.other]:
+            raise ValidationError, self.error_message
+
+class UniqueAmongstFieldsWithPrefix(object):
+    def __init__(self, field_name, prefix, error_message):
+        self.field_name, self.prefix = field_name, prefix
+        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
+
+    def __call__(self, field_data, all_data):
+        for field_name, value in all_data.items():
+            if field_name != self.field_name and value == field_data:
+                raise ValidationError, self.error_message
+
+class NumberIsInRange(object):
+    """
+    Validator that tests if a value is in a range (inclusive).
+    """
+    def __init__(self, lower=None, upper=None, error_message=''):
+        self.lower, self.upper = lower, upper
+        if not error_message:
+            if lower and upper:
+                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
+            elif lower:
+                self.error_message = _("This value must be at least %s.") % lower
+            elif upper:
+                self.error_message = _("This value must be no more than %s.") % upper
+        else:
+            self.error_message = error_message
+
+    def __call__(self, field_data, all_data):
+        # Try to make the value numeric. If this fails, we assume another
+        # validator will catch the problem.
+        try:
+            val = float(field_data)
+        except ValueError:
+            return
+
+        # Now validate
+        if self.lower and self.upper and (val < self.lower or val > self.upper):
+            raise ValidationError(self.error_message)
+        elif self.lower and val < self.lower:
+            raise ValidationError(self.error_message)
+        elif self.upper and val > self.upper:
+            raise ValidationError(self.error_message)
+
+class IsAPowerOf(object):
+    """
+    Usage: If you create an instance of the IsPowerOf validator:
+        v = IsAPowerOf(2)
+    
+    The following calls will succeed:
+        v(4, None) 
+        v(8, None)
+        v(16, None)
+    
+    But this call:
+        v(17, None)
+    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
+    """
+    def __init__(self, power_of):
+        self.power_of = power_of
+
+    def __call__(self, field_data, all_data):
+        from math import log
+        val = log(int(field_data)) / log(self.power_of)
+        if val != int(val):
+            raise ValidationError, _("This value must be a power of %s.") % self.power_of
+
+class IsValidDecimal(object):
+    def __init__(self, max_digits, decimal_places):
+        self.max_digits, self.decimal_places = max_digits, decimal_places
+
+    def __call__(self, field_data, all_data):
+        try:
+            val = Decimal(field_data)
+        except DecimalException:
+            raise ValidationError, _("Please enter a valid decimal number.")
+
+        pieces = str(val).lstrip("-").split('.')
+        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
+        digits = len(pieces[0])
+
+        if digits + decimals > self.max_digits:
+            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
+                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
+        if digits > (self.max_digits - self.decimal_places):
+            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
+                "Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places)
+        if decimals > self.decimal_places:
+            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
+                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
+
+def isValidFloat(field_data, all_data):
+    data = smart_str(field_data)
+    try:
+        float(data)
+    except ValueError:
+        raise ValidationError, _("Please enter a valid floating point number.")
+
+class HasAllowableSize(object):
+    """
+    Checks that the file-upload field data is a certain size. min_size and
+    max_size are measurements in bytes.
+    """
+    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
+        self.min_size, self.max_size = min_size, max_size
+        self.min_error_message = min_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size)
+        self.max_error_message = max_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size)
+
+    def __call__(self, field_data, all_data):
+        try:
+            content = field_data['content']
+        except TypeError:
+            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
+        if self.min_size is not None and len(content) < self.min_size:
+            raise ValidationError, self.min_error_message
+        if self.max_size is not None and len(content) > self.max_size:
+            raise ValidationError, self.max_error_message
+
+class MatchesRegularExpression(object):
+    """
+    Checks that the field matches the given regular-expression. The regex
+    should be in string format, not already compiled.
+    """
+    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
+        self.regexp = re.compile(regexp)
+        self.error_message = error_message
+
+    def __call__(self, field_data, all_data):
+        if not self.regexp.search(field_data):
+            raise ValidationError(self.error_message)
+
+class AnyValidator(object):
+    """
+    This validator tries all given validators. If any one of them succeeds,
+    validation passes. If none of them succeeds, the given message is thrown
+    as a validation error. The message is rather unspecific, so it's best to
+    specify one on instantiation.
+    """
+    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
+        if validator_list is None: validator_list = []
+        self.validator_list = validator_list
+        self.error_message = error_message
+        for v in validator_list:
+            if hasattr(v, 'always_test'):
+                self.always_test = True
+
+    def __call__(self, field_data, all_data):
+        for v in self.validator_list:
+            try:
+                v(field_data, all_data)
+                return
+            except ValidationError, e:
+                pass
+        raise ValidationError(self.error_message)
+
+class URLMimeTypeCheck(object):
+    "Checks that the provided URL points to a document with a listed mime type"
+    class CouldNotRetrieve(ValidationError):
+        pass
+    class InvalidContentType(ValidationError):
+        pass
+
+    def __init__(self, mime_type_list):
+        self.mime_type_list = mime_type_list
+
+    def __call__(self, field_data, all_data):
+        import urllib2
+        try:
+            isValidURL(field_data, all_data)
+        except ValidationError:
+            raise
+        try:
+            info = urllib2.urlopen(field_data).info()
+        except (urllib2.HTTPError, urllib2.URLError):
+            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
+        content_type = info['content-type']
+        if content_type not in self.mime_type_list:
+            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
+                'url': field_data, 'contenttype': content_type}
+
+class RelaxNGCompact(object):
+    "Validate against a Relax NG compact schema"
+    def __init__(self, schema_path, additional_root_element=None):
+        self.schema_path = schema_path
+        self.additional_root_element = additional_root_element
+
+    def __call__(self, field_data, all_data):
+        import os, tempfile
+        if self.additional_root_element:
+            field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
+                'are': self.additional_root_element,
+                'data': field_data
+            }
+        filename = tempfile.mktemp() # Insecure, but nothing else worked
+        fp = open(filename, 'w')
+        fp.write(field_data)
+        fp.close()
+        if not os.path.exists(settings.JING_PATH):
+            raise Exception, "%s not found!" % settings.JING_PATH
+        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
+        errors = [line.strip() for line in p.readlines()]
+        p.close()
+        os.unlink(filename)
+        display_errors = []
+        lines = field_data.split('\n')
+        for error in errors:
+            ignored, line, level, message = error.split(':', 3)
+            # Scrape the Jing error messages to reword them more nicely.
+            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
+            if m:
+                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
+                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
+                continue
+            if message.strip() == 'text not allowed here':
+                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
+                    {'line':line, 'start':lines[int(line) - 1][:30]})
+                continue
+            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
+            if m:
+                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
+                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+                continue
+            m = re.search(r'\s*unknown element "(.*?)"', message)
+            if m:
+                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
+                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+                continue
+            if message.strip() == 'required attributes missing':
+                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
+                    {'line':line, 'start':lines[int(line) - 1][:30]})
+                continue
+            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
+            if m:
+                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
+                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+                continue
+            # Failing all those checks, use the default error message.
+            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
+            display_errors.append(display_error)
+        if len(display_errors) > 0:
+            raise ValidationError, display_errors
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
index 3e52e33..64cfbd3 100644
--- a/tests/modeltests/manipulators/models.py
+++ b/tests/modeltests/manipulators/models.py
@@ -99,7 +99,7 @@ True
 datetime.date(2005, 2, 13)
 
 # Test isValidFloat Unicode coercion
->>> from django.core.validators import isValidFloat, ValidationError
+>>> from django.oldforms.validators import isValidFloat, ValidationError
 >>> try: isValidFloat(u"ä", None)
 ... except ValidationError: pass
 """}
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 470312f..58ca0cf 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -478,6 +478,8 @@ Create a new article, with categories, via the form.
 ...         model = Article
 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
 ...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
+>>> f.is_valid()
+True
 >>> new_art = f.save()
 >>> new_art.id
 2
@@ -705,7 +707,7 @@ ValidationError: [u'Select a valid choice. 100 is not one of the available choic
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a list of values.']
+TypeCoercionError: [u'Enter a list of values.']
 
 # Add a Category object *after* the ModelMultipleChoiceField has already been
 # instantiated. This proves clean() checks the database during clean() rather
@@ -829,14 +831,13 @@ u'...test2.txt'
 >>> instance.delete()
 
 # Test the non-required FileField
-
+# It should fail since the field IS required on the model
 >>> f = TextFileForm(data={'description': u'Assistance'})
 >>> f.fields['file'].required = False
 >>> f.is_valid()
-True
->>> instance = f.save()
->>> instance.file
-''
+False
+>>> f.errors
+{'file': [u'This field is required.']}
 
 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
 >>> f.is_valid()
@@ -899,10 +900,10 @@ u'...test2.png'
 >>> f = ImageFileForm(data={'description': u'Test'})
 >>> f.fields['image'].required = False
 >>> f.is_valid()
-True
->>> instance = f.save()
->>> instance.image
-''
+False
+>>> f.errors
+{'image': [u'This field is required.']}
+
 
 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
 >>> f.is_valid()
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index 63f9f7a..379647a 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -3,7 +3,7 @@
 
 This is an experimental feature!
 
-Each model instance has a validate() method that returns a dictionary of
+Each model instance has a clean() method that returns a dictionary of
 validation errors in the instance's fields. This method has a side effect
 of converting each field to its appropriate Python data type.
 """
@@ -15,8 +15,10 @@ class Person(models.Model):
     name = models.CharField(max_length=20)
     birthdate = models.DateField()
     favorite_moment = models.DateTimeField()
-    email = models.EmailField()
-
+    email = models.EmailField(unique=True)
+    
+    class Meta:
+        unique_together = (('name', 'is_child'),)
     def __unicode__(self):
         return self.name
 
@@ -30,128 +32,132 @@ __test__ = {'API_TESTS':"""
 ...     'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
 ...     'email': 'john@example.com'
 ... }
->>> p = Person(**valid_params)
->>> p.validate()
-{}
-
->>> p = Person(**dict(valid_params, id='23'))
->>> p.validate()
-{}
+>>> p = Person(**dict(valid_params, email='john@e.com', name='Jack'))
+>>> p.clean()
+>>> p.save()
+
+>>> p = Person(**dict(valid_params, email='john@e.com'))
+>>> p.clean()
+Traceback (most recent call last):
+...
+ValidationError: {'email': [u'This field must be unique']}
+
+>>> p = Person(**dict(valid_params, id='23', name='Jack'))
+>>> p.clean()
+Traceback (most recent call last):
+...
+ValidationError: {'__all__': u'Fields name, is_child must be unique.'}
 >>> p.id
 23
 
->>> p = Person(**dict(valid_params, id='foo'))
->>> p.validate()['id']
-[u'This value must be an integer.']
+# when type coercion fails, no other validation is done
+>>> p = Person(**dict(valid_params, email='john@e.com', id='foo'))
+>>> p.clean()
+Traceback (most recent call last):
+...
+ValidationError: {'id': [u'This value must be an integer.']}
 
 >>> p = Person(**dict(valid_params, id=None))
->>> p.validate()
-{}
+>>> p.clean()
 >>> repr(p.id)
 'None'
 
 >>> p = Person(**dict(valid_params, is_child='t'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.is_child
 True
 
 >>> p = Person(**dict(valid_params, is_child='f'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.is_child
 False
 
 >>> p = Person(**dict(valid_params, is_child=True))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.is_child
 True
 
 >>> p = Person(**dict(valid_params, is_child=False))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.is_child
 False
 
 >>> p = Person(**dict(valid_params, is_child='foo'))
->>> p.validate()['is_child']
-[u'This value must be either True or False.']
+>>> p.clean()
+Traceback (most recent call last):
+...
+ValidationError: {'is_child': [u'This value must be either True or False.']}
 
 >>> p = Person(**dict(valid_params, name=u'Jose'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.name
 u'Jose'
 
 >>> p = Person(**dict(valid_params, name=227))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.name
 u'227'
 
 >>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.birthdate
 datetime.date(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.birthdate
 datetime.date(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.birthdate
 datetime.date(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.birthdate
 datetime.date(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate='foo'))
->>> p.validate()['birthdate']
-[u'Enter a valid date in YYYY-MM-DD format.']
+>>> p.clean()
+Traceback (most recent call last):
+...
+ValidationError: {'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
 
 >>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.favorite_moment
 datetime.datetime(2002, 4, 3, 13, 23)
 
 >>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.favorite_moment
 datetime.datetime(2002, 4, 3, 0, 0)
 
 >>> p = Person(**dict(valid_params, email='john@example.com'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.email
 'john@example.com'
 
 >>> p = Person(**dict(valid_params, email=u'john@example.com'))
->>> p.validate()
-{}
+>>> p.clean()
 >>> p.email
 u'john@example.com'
 
 >>> p = Person(**dict(valid_params, email=22))
->>> p.validate()['email']
-[u'Enter a valid e-mail address.']
+>>> p.clean()
+Traceback (most recent call last):
+...
+ValidationError: {'email': [u'Enter a valid e-mail address.']}
 
 # Make sure that Date and DateTime return validation errors and don't raise Python errors.
->>> p = Person(name='John Doe', is_child=True, email='abc@def.com')
->>> errors = p.validate()
->>> errors['favorite_moment']
+>>> from django.core.validation import ValidationError
+>>> try:
+...     Person(name='John Doe', is_child=True, email='abc@def.com').clean()
+... except ValidationError, e:
+...     e.message_dict['favorite_moment']
+...     e.message_dict['birthdate']
 [u'This field is required.']
->>> errors['birthdate']
 [u'This field is required.']
 
 """}
diff --git a/tests/regressiontests/core/__init__.py b/tests/regressiontests/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/regressiontests/core/models.py b/tests/regressiontests/core/models.py
new file mode 100644
index 0000000..e5a7950
--- /dev/null
+++ b/tests/regressiontests/core/models.py
@@ -0,0 +1,2 @@
+# A models.py so that tests run.
+
diff --git a/tests/regressiontests/core/tests.py b/tests/regressiontests/core/tests.py
new file mode 100644
index 0000000..adc72df
--- /dev/null
+++ b/tests/regressiontests/core/tests.py
@@ -0,0 +1,35 @@
+tests = r"""
+###################
+# ValidationError #
+###################
+>>> from django.core.exceptions import ValidationError
+>>> from django.utils.translation import ugettext_lazy
+
+# Can take a string.
+>>> print 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
+<ul class="errorlist"><li>Not π.</li></ul>
+
+# Can take a lazy string.
+>>> print ValidationError(ugettext_lazy("Error.")).messages
+<ul class="errorlist"><li>Error.</li></ul>
+
+# Can take a list.
+>>> print 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
+<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
+<ul class="errorlist"><li>A very bad error.</li></ul>
+"""
+
diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
index 9f972f5..44f5a25 100644
--- a/tests/regressiontests/forms/error_messages.py
+++ b/tests/regressiontests/forms/error_messages.py
@@ -35,7 +35,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 >>> f.clean('4')
 Traceback (most recent call last):
 ...
@@ -59,7 +59,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 >>> f.clean('4')
 Traceback (most recent call last):
 ...
@@ -87,7 +87,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 >>> f.clean('4')
 Traceback (most recent call last):
 ...
@@ -121,7 +121,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 
 # TimeField ###################################################################
 
@@ -135,7 +135,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 
 # DateTimeField ###############################################################
 
@@ -149,7 +149,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 
 # RegexField ##################################################################
 
@@ -203,7 +203,6 @@ ValidationError: [u'LENGTH 11, MAX LENGTH 10']
 
 >>> e = {'required': 'REQUIRED'}
 >>> e['invalid'] = 'INVALID'
->>> e['missing'] = 'MISSING'
 >>> e['empty'] = 'EMPTY FILE'
 >>> f = FileField(error_messages=e)
 >>> f.clean('')
@@ -213,11 +212,11 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('abc')
 Traceback (most recent call last):
 ...
-ValidationError: [u'INVALID']
+TypeCoercionError: [u'INVALID']
 >>> f.clean({})
 Traceback (most recent call last):
 ...
-ValidationError: [u'MISSING']
+ValidationError: [u'REQUIRED']
 >>> f.clean({'filename': 'name', 'content':''})
 Traceback (most recent call last):
 ...
@@ -278,7 +277,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('b')
 Traceback (most recent call last):
 ...
-ValidationError: [u'NOT A LIST']
+TypeCoercionError: [u'NOT A LIST']
 >>> f.clean(['b'])
 Traceback (most recent call last):
 ...
@@ -352,7 +351,7 @@ ValidationError: [u'REQUIRED']
 >>> f.clean('3')
 Traceback (most recent call last):
 ...
-ValidationError: [u'NOT A LIST OF VALUES']
+TypeCoercionError: [u'NOT A LIST OF VALUES']
 >>> f.clean(['4'])
 Traceback (most recent call last):
 ...
diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py
index a8b3697..7e381db 100644
--- a/tests/regressiontests/forms/extra.py
+++ b/tests/regressiontests/forms/extra.py
@@ -424,7 +424,7 @@ u'sirrobin'
 # Test overriding ErrorList in a form #
 #######################################
 
->>> from django.newforms.util import ErrorList
+>>> from django.core.validation import ErrorList
 >>> class DivErrorList(ErrorList):
 ...     def __unicode__(self):
 ...         return self.as_divs()
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
index f3b6a96..2fb77e7 100644
--- a/tests/regressiontests/forms/fields.py
+++ b/tests/regressiontests/forms/fields.py
@@ -31,6 +31,7 @@ Each Field's __init__() takes at least these parameters:
              field name, if the Field is part of a Form.
     initial -- A value to use in this Field's initial display. This value is
                *not* used as a fallback if data isn't given.
+    validators -- Optional list of additional validator functions
 
 Other than that, the Field subclasses have class-specific options for
 __init__(). For example, CharField has a max_length option.
@@ -103,6 +104,30 @@ u'1234567890'
 >>> f.clean('1234567890a')
 u'1234567890a'
 
+# Custom validator functions ##################################################
+
+>>> def validator(value): raise ValidationError('validator failed')
+>>> f = CharField(min_length=10, validators=[validator])
+>>> f.clean('aa')
+Traceback (most recent call last):
+...
+ValidationError: [u'validator failed']
+
+>>> def validator2(value): raise ValidationError('validator2 failed')
+>>> f = CharField(min_length=10, validators=[validator, validator, validator2])
+>>> f.clean('aa')
+Traceback (most recent call last):
+...
+ValidationError: [u'validator failed', u'validator failed', u'validator2 failed']
+
+>>> class MyCharField(CharField):
+...     validators = [validator]
+>>> f = MyCharField()
+>>> f.clean('aa')
+Traceback (most recent call last):
+...
+ValidationError: [u'validator failed']
+
 # IntegerField ################################################################
 
 >>> f = IntegerField()
@@ -123,13 +148,13 @@ True
 >>> f.clean('a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a whole number.']
+TypeCoercionError: [u'Enter a whole number.']
 >>> f.clean(42)
 42
 >>> f.clean(3.14)
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a whole number.']
+TypeCoercionError: [u'Enter a whole number.']
 >>> f.clean('1 ')
 1
 >>> f.clean(' 1')
@@ -139,7 +164,7 @@ ValidationError: [u'Enter a whole number.']
 >>> f.clean('1a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a whole number.']
+TypeCoercionError: [u'Enter a whole number.']
 
 >>> f = IntegerField(required=False)
 >>> f.clean('')
@@ -157,7 +182,7 @@ True
 >>> f.clean('a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a whole number.']
+TypeCoercionError: [u'Enter a whole number.']
 >>> f.clean('1 ')
 1
 >>> f.clean(' 1')
@@ -167,7 +192,7 @@ ValidationError: [u'Enter a whole number.']
 >>> f.clean('1a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a whole number.']
+TypeCoercionError: [u'Enter a whole number.']
 
 IntegerField accepts an optional max_value parameter:
 >>> f = IntegerField(max_value=10)
@@ -260,7 +285,7 @@ True
 >>> f.clean('a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a number.']
+TypeCoercionError: [u'Enter a number.']
 >>> f.clean('1.0 ')
 1.0
 >>> f.clean(' 1.0')
@@ -270,7 +295,7 @@ ValidationError: [u'Enter a number.']
 >>> f.clean('1.0a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a number.']
+TypeCoercionError: [u'Enter a number.']
 
 >>> f = FloatField(required=False)
 >>> f.clean('')
@@ -322,11 +347,11 @@ Decimal("3.14")
 >>> f.clean('a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a number.']
+TypeCoercionError: [u'Enter a number.']
 >>> f.clean(u'łąść')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a number.']
+TypeCoercionError: [u'Enter a number.']
 >>> f.clean('1.0 ')
 Decimal("1.0")
 >>> f.clean(' 1.0')
@@ -336,7 +361,7 @@ Decimal("1.0")
 >>> f.clean('1.0a')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a number.']
+TypeCoercionError: [u'Enter a number.']
 >>> f.clean('123.45')
 Traceback (most recent call last):
 ...
@@ -372,7 +397,7 @@ ValidationError: [u'Ensure that there are no more than 4 digits in total.']
 >>> f.clean('--0.12')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a number.']
+TypeCoercionError: [u'Enter a number.']
 
 >>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
 >>> f.clean('')
@@ -433,15 +458,15 @@ datetime.date(2006, 10, 25)
 >>> f.clean('2006-4-31')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('200a-10-25')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('25/10/06')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean(None)
 Traceback (most recent call last):
 ...
@@ -469,15 +494,15 @@ so the default formats won't work unless you specify them:
 >>> f.clean('2006-10-25')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('10/25/2006')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('10/25/06')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 
 # TimeField ###################################################################
 
@@ -494,11 +519,11 @@ datetime.time(14, 25, 59)
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid time.']
+TypeCoercionError: [u'Enter a valid time.']
 >>> f.clean('1:24 p.m.')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid time.']
+TypeCoercionError: [u'Enter a valid time.']
 
 TimeField accepts an optional input_formats parameter:
 >>> f = TimeField(input_formats=['%I:%M %p'])
@@ -516,7 +541,7 @@ so the default formats won't work unless you specify them:
 >>> f.clean('14:30:45')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid time.']
+TypeCoercionError: [u'Enter a valid time.']
 
 # DateTimeField ###############################################################
 
@@ -557,11 +582,11 @@ datetime.datetime(2006, 10, 25, 0, 0)
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date/time.']
+TypeCoercionError: [u'Enter a valid date/time.']
 >>> f.clean('2006-10-25 4:30 p.m.')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date/time.']
+TypeCoercionError: [u'Enter a valid date/time.']
 
 DateField accepts an optional input_formats parameter:
 >>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
@@ -581,7 +606,7 @@ so the default formats won't work unless you specify them:
 >>> f.clean('2006-10-25 14:30:45')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date/time.']
+TypeCoercionError: [u'Enter a valid date/time.']
 
 >>> f = DateTimeField(required=False)
 >>> f.clean(None)
@@ -773,12 +798,12 @@ ValidationError: [u'This field is required.']
 >>> f.clean({})
 Traceback (most recent call last):
 ...
-ValidationError: [u'No file was submitted.']
+ValidationError: [u'This field is required.']
 
 >>> f.clean({}, '')
 Traceback (most recent call last):
 ...
-ValidationError: [u'No file was submitted.']
+ValidationError: [u'This field is required.']
 
 >>> f.clean({}, 'files/test3.pdf')
 'files/test3.pdf'
@@ -786,7 +811,7 @@ ValidationError: [u'No file was submitted.']
 >>> f.clean('some content that is not a file')
 Traceback (most recent call last):
 ...
-ValidationError: [u'No file was submitted. Check the encoding type on the form.']
+TypeCoercionError: [u'No file was submitted. Check the encoding type on the form.']
 
 >>> f.clean({'filename': 'name', 'content': None})
 Traceback (most recent call last):
@@ -993,9 +1018,7 @@ ValidationError: [u'Select a valid choice. That choice is not one of the availab
 
 >>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
 >>> f.clean('')
-u''
 >>> f.clean(None)
-u''
 >>> f.clean(1)
 u'1'
 >>> f.clean('1')
@@ -1051,7 +1074,7 @@ ValidationError: [u'This field is required.']
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a list of values.']
+TypeCoercionError: [u'Enter a list of values.']
 >>> f.clean([])
 Traceback (most recent call last):
 ...
@@ -1083,7 +1106,7 @@ ValidationError: [u'Select a valid choice. 3 is not one of the available choices
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a list of values.']
+TypeCoercionError: [u'Enter a list of values.']
 >>> f.clean([])
 []
 >>> f.clean(())
@@ -1186,7 +1209,7 @@ ValidationError: [u'This field is required.']
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a list of values.']
+TypeCoercionError: [u'Enter a list of values.']
 >>> f.clean(['hello', 'there'])
 Traceback (most recent call last):
 ...
@@ -1212,7 +1235,7 @@ datetime.datetime(2006, 1, 10, 7, 30)
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a list of values.']
+TypeCoercionError: [u'Enter a list of values.']
 >>> f.clean(['hello', 'there'])
 Traceback (most recent call last):
 ...
diff --git a/tests/regressiontests/forms/localflavor/br.py b/tests/regressiontests/forms/localflavor/br.py
index 757f382..94285c3 100644
--- a/tests/regressiontests/forms/localflavor/br.py
+++ b/tests/regressiontests/forms/localflavor/br.py
@@ -85,11 +85,11 @@ Traceback (most recent call last):
 ...
 ValidationError: [u'Invalid CNPJ number.']
 >>> f.clean('64.132.916/0001-88')
-'64.132.916/0001-88'
+u'64.132.916/0001-88'
 >>> f.clean('64-132-916/0001-88')
-'64-132-916/0001-88'
+u'64-132-916/0001-88'
 >>> f.clean('64132916/0001-88')
-'64132916/0001-88'
+u'64132916/0001-88'
 >>> f.clean('64.132.916/0001-XX')
 Traceback (most recent call last):
 ...
diff --git a/tests/regressiontests/forms/localflavor/generic.py b/tests/regressiontests/forms/localflavor/generic.py
index 0dbe30d..cda4f89 100644
--- a/tests/regressiontests/forms/localflavor/generic.py
+++ b/tests/regressiontests/forms/localflavor/generic.py
@@ -38,15 +38,15 @@ datetime.date(2006, 10, 25)
 >>> f.clean('2006-4-31')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('200a-10-25')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('10/25/06')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean(None)
 Traceback (most recent call last):
 ...
@@ -74,15 +74,15 @@ so the default formats won't work unless you specify them:
 >>> f.clean('2006-10-25')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('25/10/2006')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 >>> f.clean('25/10/06')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date.']
+TypeCoercionError: [u'Enter a valid date.']
 
 ## Generic DateTimeField ######################################################
 
@@ -126,11 +126,11 @@ datetime.datetime(2006, 10, 25, 0, 0)
 >>> f.clean('hello')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date/time.']
+TypeCoercionError: [u'Enter a valid date/time.']
 >>> f.clean('2006-10-25 4:30 p.m.')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date/time.']
+TypeCoercionError: [u'Enter a valid date/time.']
 
 DateField accepts an optional input_formats parameter:
 >>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
@@ -150,7 +150,7 @@ so the default formats won't work unless you specify them:
 >>> f.clean('2006-10-25 14:30:45')
 Traceback (most recent call last):
 ...
-ValidationError: [u'Enter a valid date/time.']
+TypeCoercionError: [u'Enter a valid date/time.']
 
 >>> f = DateTimeField(required=False)
 >>> f.clean(None)
diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
index bfaf73f..9933968 100644
--- a/tests/regressiontests/forms/util.py
+++ b/tests/regressiontests/forms/util.py
@@ -5,7 +5,6 @@ Tests for newforms/util.py module.
 
 tests = r"""
 >>> from django.newforms.util import *
->>> from django.utils.translation import ugettext_lazy
 
 ###########
 # flatatt #
@@ -18,35 +17,4 @@ u' id="header"'
 u' class="news" title="Read this"'
 >>> flatatt({})
 u''
-
-###################
-# ValidationError #
-###################
-
-# Can take a string.
->>> print 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
-<ul class="errorlist"><li>Not π.</li></ul>
-
-# Can take a lazy string.
->>> print ValidationError(ugettext_lazy("Error.")).messages
-<ul class="errorlist"><li>Error.</li></ul>
-
-# Can take a list.
->>> print 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
-<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
-<ul class="errorlist"><li>A very bad error.</li></ul>
 """
