Code

Ticket #6845: 6845-against-7424.diff

File 6845-against-7424.diff, 136.5 KB (added by Honza_Kral, 6 years ago)

new version

Line 
1diff --git a/AUTHORS b/AUTHORS
2index 294ad18..e864b38 100644
3--- a/AUTHORS
4+++ b/AUTHORS
5@@ -381,6 +381,7 @@ answer newbie questions, and generally made Django that much better:
6     ymasuda@ethercube.com
7     Jarek Zgoda <jarek.zgoda@gmail.com>
8     Cheng Zhang
9+    Honza Kral <Honza.Kral@gmail.com>
10 
11 A big THANK YOU goes to:
12 
13diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py
14index a3b4538..89e6952 100644
15--- a/django/contrib/admin/views/template.py
16+++ b/django/contrib/admin/views/template.py
17@@ -1,5 +1,5 @@
18 from django.contrib.admin.views.decorators import staff_member_required
19-from django.core import validators
20+from django.core import validation
21 from django import template, oldforms
22 from django.template import loader
23 from django.shortcuts import render_to_response
24@@ -69,4 +69,4 @@ class TemplateValidator(oldforms.Manipulator):
25             error = e
26         template.builtins.remove(register)
27         if error:
28-            raise validators.ValidationError, e.args
29+            raise validation.ValidationError, e.args
30diff --git a/django/contrib/auth/create_superuser.py b/django/contrib/auth/create_superuser.py
31index 7b6cefd..0d08ff1 100644
32--- a/django/contrib/auth/create_superuser.py
33+++ b/django/contrib/auth/create_superuser.py
34@@ -61,7 +61,7 @@ def createsuperuser(username=None, email=None, password=None):
35             if not email:
36                 email = raw_input('E-mail address: ')
37             try:
38-                validators.isValidEmail(email, None)
39+                validators.validate_email(email)
40             except validators.ValidationError:
41                 sys.stderr.write("Error: That e-mail address is invalid.\n")
42                 email = None
43diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
44index 47a974c..b1af278 100644
45--- a/django/contrib/auth/forms.py
46+++ b/django/contrib/auth/forms.py
47@@ -2,7 +2,7 @@ from django.contrib.auth.models import User
48 from django.contrib.auth import authenticate
49 from django.contrib.sites.models import Site
50 from django.template import Context, loader
51-from django.core import validators
52+from django.oldforms import validators
53 from django import oldforms
54 from django.utils.translation import ugettext as _
55 
56diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
57index c867065..ebe44bc 100644
58--- a/django/contrib/auth/models.py
59+++ b/django/contrib/auth/models.py
60@@ -1,5 +1,5 @@
61 from django.contrib import auth
62-from django.core import validators
63+from django.oldforms import validators
64 from django.core.exceptions import ImproperlyConfigured
65 from django.db import models
66 from django.db.models.manager import EmptyManager
67@@ -128,7 +128,7 @@ class User(models.Model):
68 
69     Username and password are required. Other fields are optional.
70     """
71-    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)."))
72+    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)."))
73     first_name = models.CharField(_('first name'), max_length=30, blank=True)
74     last_name = models.CharField(_('last name'), max_length=30, blank=True)
75     email = models.EmailField(_('e-mail address'), blank=True)
76diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
77index 67da575..9c743bf 100644
78--- a/django/contrib/comments/views/comments.py
79+++ b/django/contrib/comments/views/comments.py
80@@ -1,4 +1,4 @@
81-from django.core import validators
82+from django.oldforms import validators
83 from django import oldforms
84 from django.core.mail import mail_admins, mail_managers
85 from django.http import Http404
86diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
87index 190ffbd..19e3a73 100644
88--- a/django/contrib/flatpages/models.py
89+++ b/django/contrib/flatpages/models.py
90@@ -1,11 +1,11 @@
91-from django.core import validators
92+from django.oldforms import validators
93 from django.db import models
94 from django.contrib.sites.models import Site
95 from django.utils.translation import ugettext_lazy as _
96 
97 
98 class FlatPage(models.Model):
99-    url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
100+    url = models.CharField(_('URL'), max_length=100, validators=[validators.isAlphaNumericURL], db_index=True,
101         help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
102     title = models.CharField(_('title'), max_length=200)
103     content = models.TextField(_('content'))
104diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
105index aa7e3b2..ca9e61b 100644
106--- a/django/contrib/localflavor/br/forms.py
107+++ b/django/contrib/localflavor/br/forms.py
108@@ -31,10 +31,10 @@ class BRPhoneNumberField(Field):
109     }
110 
111     def clean(self, value):
112-        super(BRPhoneNumberField, self).clean(value)
113+        value = super(BRPhoneNumberField, self).clean(value)
114         if value in EMPTY_VALUES:
115             return u''
116-        value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
117+        value = re.sub('(\(|\)|\s+)', '', value)
118         m = phone_digits_re.search(value)
119         if m:
120             return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
121diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py
122index a0274da..c91db42 100644
123--- a/django/contrib/localflavor/fi/forms.py
124+++ b/django/contrib/localflavor/fi/forms.py
125@@ -29,8 +29,11 @@ class FISocialSecurityNumber(Field):
126     }
127 
128     def clean(self, value):
129-        super(FISocialSecurityNumber, self).clean(value)
130-        if value in EMPTY_VALUES:
131+        # changed not to throw UnicodeDecode error when passed invalid data
132+        # I think this SHOULD call super.clean, which would mean ^^^
133+        if self.required and value in EMPTY_VALUES:
134+             raise ValidationError(self.error_messages['required'])
135+        elif value in EMPTY_VALUES:
136             return u''
137 
138         checkmarks = "0123456789ABCDEFHJKLMNPRSTUVWXY"
139diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
140index d726f82..4d334aa 100644
141--- a/django/contrib/localflavor/jp/forms.py
142+++ b/django/contrib/localflavor/jp/forms.py
143@@ -2,7 +2,6 @@
144 JP-specific Form helpers
145 """
146 
147-from django.core import validators
148 from django.newforms import ValidationError
149 from django.utils.translation import ugettext
150 from django.newforms.fields import RegexField, Select
151diff --git a/django/core/exceptions.py b/django/core/exceptions.py
152index d9fc326..06c92ac 100644
153--- a/django/core/exceptions.py
154+++ b/django/core/exceptions.py
155@@ -27,3 +27,4 @@ class MiddlewareNotUsed(Exception):
156 class ImproperlyConfigured(Exception):
157     "Django is somehow improperly configured"
158     pass
159+
160diff --git a/django/core/validation.py b/django/core/validation.py
161new file mode 100644
162index 0000000..4de7c98
163--- /dev/null
164+++ b/django/core/validation.py
165@@ -0,0 +1,50 @@
166+from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
167+from django.utils.safestring import mark_safe
168+
169+NON_FIELD_ERRORS = '__all__'
170+
171+class ErrorList(list, StrAndUnicode):
172+    """
173+    A collection of errors that knows how to display itself in various formats.
174+    """
175+    def __unicode__(self):
176+        return self.as_ul()
177+
178+    def as_ul(self):
179+        if not self: return u''
180+        return mark_safe(u'<ul class="errorlist">%s</ul>'
181+                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
182+
183+    def as_text(self):
184+        if not self: return u''
185+        return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
186+
187+    def __repr__(self):
188+        return repr([force_unicode(e) for e in self])
189+
190+class ValidationError(Exception):
191+    def __init__(self, message):
192+        """
193+        ValidationError can be passed any object that can be printed (usually
194+        a string) or a list of objects.
195+        """
196+        if hasattr(message, '__iter__'):
197+            self.messages = ErrorList([smart_unicode(msg) for msg in message])
198+        else:
199+            message = smart_unicode(message)
200+            self.messages = ErrorList([message])
201+
202+        if isinstance(message, dict):
203+            self.message_dict = message
204+
205+    def __str__(self):
206+        # This is needed because, without a __str__(), printing an exception
207+        # instance would result in this:
208+        # AttributeError: ValidationError instance has no attribute 'args'
209+        # See http://www.python.org/doc/current/tut/node10.html#handling
210+        if hasattr(self, 'message_dict'):
211+            return repr(self.message_dict)
212+        return repr(self.messages)
213+
214+class TypeCoercionError(ValidationError):
215+    pass
216diff --git a/django/core/validators.py b/django/core/validators.py
217index e728dbc..2703455 100644
218--- a/django/core/validators.py
219+++ b/django/core/validators.py
220@@ -1,245 +1,75 @@
221 """
222 A library of validators that return None and raise ValidationError when the
223 provided data isn't valid.
224-
225-Validators may be callable classes, and they may have an 'always_test'
226-attribute. If an 'always_test' attribute exists (regardless of value), the
227-validator will *always* be run, regardless of whether its associated
228-form field is required.
229 """
230 
231-import urllib2
232 import re
233-try:
234-    from decimal import Decimal, DecimalException
235-except ImportError:
236-    from django.utils._decimal import Decimal, DecimalException    # Python 2.3
237+import urllib2
238 
239 from django.conf import settings
240-from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
241-from django.utils.functional import Promise, lazy
242-from django.utils.encoding import force_unicode, smart_str
243+from django.utils.translation import ugettext as _
244+from django.core.validation import ValidationError
245+
246+def regexp_validator(regexp, message):
247+    if isinstance(regexp, basestring):
248+        regexp = re.compile(regexp)
249+
250+    def _regexp_validator(value):
251+        if not regexp.search(value):
252+            raise ValidationError, _(message)
253+    return _regexp_validator
254 
255-_datere = r'\d{4}-\d{1,2}-\d{1,2}'
256-_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
257-alnum_re = re.compile(r'^\w+$')
258-alnumurl_re = re.compile(r'^[-\w/]+$')
259-ansi_date_re = re.compile('^%s$' % _datere)
260-ansi_time_re = re.compile('^%s$' % _timere)
261-ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
262 email_re = re.compile(
263     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
264     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
265     r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
266-integer_re = re.compile(r'^-?\d+$')
267-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}$')
268-phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
269-slug_re = re.compile(r'^[-\w]+$')
270-url_re = re.compile(r'^https?://\S+$')
271+validate_email = regexp_validator(email_re, 'Enter a valid e-mail address.')
272 
273-lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
274+validate_ip_address4 = regexp_validator(
275+        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}$'),
276+        "Please enter a valid IP address."
277+    )
278 
279-class ValidationError(Exception):
280-    def __init__(self, message):
281-        "ValidationError can be passed a string or a list."
282-        if isinstance(message, list):
283-            self.messages = [force_unicode(msg) for msg in message]
284-        else:
285-            assert isinstance(message, (basestring, Promise)), ("%s should be a string" % repr(message))
286-            self.messages = [force_unicode(message)]
287+validate_phone_number = regexp_validator(
288+        re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE),
289+        _('Phone numbers must be in XXX-XXX-XXXX format.')
290+    )
291 
292-    def __str__(self):
293-        # This is needed because, without a __str__(), printing an exception
294-        # instance would result in this:
295-        # AttributeError: ValidationError instance has no attribute 'args'
296-        # See http://www.python.org/doc/current/tut/node10.html#handling
297-        return str(self.messages)
298+validate_slug = regexp_validator(
299+        re.compile(r'^[-\w]+$'),
300+        "This value must contain only letters, numbers, underscores or hyphens."
301+    )
302 
303-class CriticalValidationError(Exception):
304-    def __init__(self, message):
305-        "ValidationError can be passed a string or a list."
306-        if isinstance(message, list):
307-            self.messages = [force_unicode(msg) for msg in message]
308-        else:
309-            assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
310-            self.messages = [force_unicode(message)]
311-
312-    def __str__(self):
313-        return str(self.messages)
314-
315-def isAlphaNumeric(field_data, all_data):
316-    if not alnum_re.search(field_data):
317-        raise ValidationError, _("This value must contain only letters, numbers and underscores.")
318-
319-def isAlphaNumericURL(field_data, all_data):
320-    if not alnumurl_re.search(field_data):
321-        raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
322-
323-def isSlug(field_data, all_data):
324-    if not slug_re.search(field_data):
325-        raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
326-
327-def isLowerCase(field_data, all_data):
328-    if field_data.lower() != field_data:
329-        raise ValidationError, _("Uppercase letters are not allowed here.")
330+_datere = r'\d{4}-\d{1,2}-\d{1,2}'
331+validate_ansi_date = regexp_validator(
332+        re.compile('^%s$' % _datere),
333+        _('Enter a valid date in YYYY-MM-DD format.')
334+    )
335 
336-def isUpperCase(field_data, all_data):
337-    if field_data.upper() != field_data:
338-        raise ValidationError, _("Lowercase letters are not allowed here.")
339+_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
340+validate_ansi_time = regexp_validator(
341+        re.compile('^%s$' % _timere),
342+        _('Enter a valid time in HH:MM format.')
343+    )
344 
345-def isCommaSeparatedIntegerList(field_data, all_data):
346-    for supposed_int in field_data.split(','):
347-        try:
348-            int(supposed_int)
349-        except ValueError:
350-            raise ValidationError, _("Enter only digits separated by commas.")
351+validate_ansi_datetime = regexp_validator(
352+        re.compile('^%s %s$' % (_datere, _timere)),
353+        _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
354+    )
355 
356-def isCommaSeparatedEmailList(field_data, all_data):
357+def validate_comma_separated_email_list(value):
358     """
359-    Checks that field_data is a string of e-mail addresses separated by commas.
360-    Blank field_data values will not throw a validation error, and whitespace
361+    Checks that value is a string of e-mail addresses separated by commas.
362+    Blank value values will not throw a validation error, and whitespace
363     is allowed around the commas.
364     """
365-    for supposed_email in field_data.split(','):
366+    for supposed_email in value.split(','):
367         try:
368-            isValidEmail(supposed_email.strip(), '')
369+            validate_email(supposed_email.strip())
370         except ValidationError:
371             raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
372 
373-def isValidIPAddress4(field_data, all_data):
374-    if not ip4_re.search(field_data):
375-        raise ValidationError, _("Please enter a valid IP address.")
376-
377-def isNotEmpty(field_data, all_data):
378-    if field_data.strip() == '':
379-        raise ValidationError, _("Empty values are not allowed here.")
380-
381-def isOnlyDigits(field_data, all_data):
382-    if not field_data.isdigit():
383-        raise ValidationError, _("Non-numeric characters aren't allowed here.")
384-
385-def isNotOnlyDigits(field_data, all_data):
386-    if field_data.isdigit():
387-        raise ValidationError, _("This value can't be comprised solely of digits.")
388-
389-def isInteger(field_data, all_data):
390-    # This differs from isOnlyDigits because this accepts the negative sign
391-    if not integer_re.search(field_data):
392-        raise ValidationError, _("Enter a whole number.")
393-
394-def isOnlyLetters(field_data, all_data):
395-    if not field_data.isalpha():
396-        raise ValidationError, _("Only alphabetical characters are allowed here.")
397-
398-def _isValidDate(date_string):
399-    """
400-    A helper function used by isValidANSIDate and isValidANSIDatetime to
401-    check if the date is valid.  The date string is assumed to already be in
402-    YYYY-MM-DD format.
403-    """
404-    from datetime import date
405-    # Could use time.strptime here and catch errors, but datetime.date below
406-    # produces much friendlier error messages.
407-    year, month, day = map(int, date_string.split('-'))
408-    # This check is needed because strftime is used when saving the date
409-    # value to the database, and strftime requires that the year be >=1900.
410-    if year < 1900:
411-        raise ValidationError, _('Year must be 1900 or later.')
412-    try:
413-        date(year, month, day)
414-    except ValueError, e:
415-        msg = _('Invalid date: %s') % _(str(e))
416-        raise ValidationError, msg
417-
418-def isValidANSIDate(field_data, all_data):
419-    if not ansi_date_re.search(field_data):
420-        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
421-    _isValidDate(field_data)
422-
423-def isValidANSITime(field_data, all_data):
424-    if not ansi_time_re.search(field_data):
425-        raise ValidationError, _('Enter a valid time in HH:MM format.')
426-
427-def isValidANSIDatetime(field_data, all_data):
428-    if not ansi_datetime_re.search(field_data):
429-        raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
430-    _isValidDate(field_data.split()[0])
431-
432-def isValidEmail(field_data, all_data):
433-    if not email_re.search(field_data):
434-        raise ValidationError, _('Enter a valid e-mail address.')
435-
436-def isValidImage(field_data, all_data):
437-    """
438-    Checks that the file-upload field data contains a valid image (GIF, JPG,
439-    PNG, possibly others -- whatever the Python Imaging Library supports).
440-    """
441-    from PIL import Image
442-    from cStringIO import StringIO
443-    try:
444-        content = field_data['content']
445-    except TypeError:
446-        raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
447-    try:
448-        # load() is the only method that can spot a truncated JPEG,
449-        #  but it cannot be called sanely after verify()
450-        trial_image = Image.open(StringIO(content))
451-        trial_image.load()
452-        # verify() is the only method that can spot a corrupt PNG,
453-        #  but it must be called immediately after the constructor
454-        trial_image = Image.open(StringIO(content))
455-        trial_image.verify()
456-    except Exception: # Python Imaging Library doesn't recognize it as an image
457-        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
458-
459-def isValidImageURL(field_data, all_data):
460-    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
461-    try:
462-        uc(field_data, all_data)
463-    except URLMimeTypeCheck.InvalidContentType:
464-        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
465-
466-def isValidPhone(field_data, all_data):
467-    if not phone_re.search(field_data):
468-        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
469-
470-def isValidQuicktimeVideoURL(field_data, all_data):
471-    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
472-    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
473-    try:
474-        uc(field_data, all_data)
475-    except URLMimeTypeCheck.InvalidContentType:
476-        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
477-
478-def isValidURL(field_data, all_data):
479-    if not url_re.search(field_data):
480-        raise ValidationError, _("A valid URL is required.")
481-
482-def isValidHTML(field_data, all_data):
483-    import urllib, urllib2
484-    try:
485-        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
486-    except:
487-        # Validator or Internet connection is unavailable. Fail silently.
488-        return
489-    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
490-    if html_is_valid:
491-        return
492-    from xml.dom.minidom import parseString
493-    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
494-    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
495-
496-def isWellFormedXml(field_data, all_data):
497-    from xml.dom.minidom import parseString
498-    try:
499-        parseString(field_data)
500-    except Exception, e: # Naked except because we're not sure what will be thrown
501-        raise ValidationError, _("Badly formed XML: %s") % str(e)
502-
503-def isWellFormedXmlFragment(field_data, all_data):
504-    isWellFormedXml('<root>%s</root>' % field_data, all_data)
505-
506-def isExistingURL(field_data, all_data):
507+def validate_existing_url(value):
508     try:
509         headers = {
510             "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
511@@ -248,271 +78,17 @@ def isExistingURL(field_data, all_data):
512             "Connection" : "close",
513             "User-Agent": settings.URL_VALIDATOR_USER_AGENT
514             }
515-        req = urllib2.Request(field_data,None, headers)
516+        req = urllib2.Request(value,None, headers)
517         u = urllib2.urlopen(req)
518     except ValueError:
519-        raise ValidationError, _("Invalid URL: %s") % field_data
520+        raise ValidationError, _("Invalid URL: %s") % value
521     except urllib2.HTTPError, e:
522         # 401s are valid; they just mean authorization is required.
523         # 301 and 302 are redirects; they just mean look somewhere else.
524         if str(e.code) not in ('401','301','302'):
525-            raise ValidationError, _("The URL %s is a broken link.") % field_data
526+            raise ValidationError, _("The URL %s is a broken link.") % value
527     except: # urllib2.URLError, httplib.InvalidURL, etc.
528-        raise ValidationError, _("The URL %s is a broken link.") % field_data
529-
530-def isValidUSState(field_data, all_data):
531-    "Checks that the given string is a valid two-letter U.S. state abbreviation"
532-    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']
533-    if field_data.upper() not in states:
534-        raise ValidationError, _("Enter a valid U.S. state abbreviation.")
535-
536-def hasNoProfanities(field_data, all_data):
537-    """
538-    Checks that the given string has no profanities in it. This does a simple
539-    check for whether each profanity exists within the string, so 'fuck' will
540-    catch 'motherfucker' as well. Raises a ValidationError such as:
541-        Watch your mouth! The words "f--k" and "s--t" are not allowed here.
542-    """
543-    field_data = field_data.lower() # normalize
544-    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
545-    if words_seen:
546-        from django.utils.text import get_text_list
547-        plural = len(words_seen)
548-        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
549-            "Watch your mouth! The words %s are not allowed here.", plural) % \
550-            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
551-
552-class AlwaysMatchesOtherField(object):
553-    def __init__(self, other_field_name, error_message=None):
554-        self.other = other_field_name
555-        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
556-        self.always_test = True
557-
558-    def __call__(self, field_data, all_data):
559-        if field_data != all_data[self.other]:
560-            raise ValidationError, self.error_message
561-
562-class ValidateIfOtherFieldEquals(object):
563-    def __init__(self, other_field, other_value, validator_list):
564-        self.other_field, self.other_value = other_field, other_value
565-        self.validator_list = validator_list
566-        self.always_test = True
567-
568-    def __call__(self, field_data, all_data):
569-        if self.other_field in all_data and all_data[self.other_field] == self.other_value:
570-            for v in self.validator_list:
571-                v(field_data, all_data)
572-
573-class RequiredIfOtherFieldNotGiven(object):
574-    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
575-        self.other, self.error_message = other_field_name, error_message
576-        self.always_test = True
577-
578-    def __call__(self, field_data, all_data):
579-        if not all_data.get(self.other, False) and not field_data:
580-            raise ValidationError, self.error_message
581-
582-class RequiredIfOtherFieldsGiven(object):
583-    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
584-        self.other, self.error_message = other_field_names, error_message
585-        self.always_test = True
586-
587-    def __call__(self, field_data, all_data):
588-        for field in self.other:
589-            if all_data.get(field, False) and not field_data:
590-                raise ValidationError, self.error_message
591-
592-class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
593-    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
594-    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
595-        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
596-
597-class RequiredIfOtherFieldEquals(object):
598-    def __init__(self, other_field, other_value, error_message=None, other_label=None):
599-        self.other_field = other_field
600-        self.other_value = other_value
601-        other_label = other_label or other_value
602-        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
603-            'field': other_field, 'value': other_label})
604-        self.always_test = True
605-
606-    def __call__(self, field_data, all_data):
607-        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
608-            raise ValidationError(self.error_message)
609-
610-class RequiredIfOtherFieldDoesNotEqual(object):
611-    def __init__(self, other_field, other_value, other_label=None, error_message=None):
612-        self.other_field = other_field
613-        self.other_value = other_value
614-        other_label = other_label or other_value
615-        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
616-            'field': other_field, 'value': other_label})
617-        self.always_test = True
618-
619-    def __call__(self, field_data, all_data):
620-        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
621-            raise ValidationError(self.error_message)
622-
623-class IsLessThanOtherField(object):
624-    def __init__(self, other_field_name, error_message):
625-        self.other, self.error_message = other_field_name, error_message
626-
627-    def __call__(self, field_data, all_data):
628-        if field_data > all_data[self.other]:
629-            raise ValidationError, self.error_message
630-
631-class UniqueAmongstFieldsWithPrefix(object):
632-    def __init__(self, field_name, prefix, error_message):
633-        self.field_name, self.prefix = field_name, prefix
634-        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
635-
636-    def __call__(self, field_data, all_data):
637-        for field_name, value in all_data.items():
638-            if field_name != self.field_name and value == field_data:
639-                raise ValidationError, self.error_message
640-
641-class NumberIsInRange(object):
642-    """
643-    Validator that tests if a value is in a range (inclusive).
644-    """
645-    def __init__(self, lower=None, upper=None, error_message=''):
646-        self.lower, self.upper = lower, upper
647-        if not error_message:
648-            if lower and upper:
649-                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
650-            elif lower:
651-                self.error_message = _("This value must be at least %s.") % lower
652-            elif upper:
653-                self.error_message = _("This value must be no more than %s.") % upper
654-        else:
655-            self.error_message = error_message
656-
657-    def __call__(self, field_data, all_data):
658-        # Try to make the value numeric. If this fails, we assume another
659-        # validator will catch the problem.
660-        try:
661-            val = float(field_data)
662-        except ValueError:
663-            return
664-
665-        # Now validate
666-        if self.lower and self.upper and (val < self.lower or val > self.upper):
667-            raise ValidationError(self.error_message)
668-        elif self.lower and val < self.lower:
669-            raise ValidationError(self.error_message)
670-        elif self.upper and val > self.upper:
671-            raise ValidationError(self.error_message)
672-
673-class IsAPowerOf(object):
674-    """
675-    Usage: If you create an instance of the IsPowerOf validator:
676-        v = IsAPowerOf(2)
677-   
678-    The following calls will succeed:
679-        v(4, None)
680-        v(8, None)
681-        v(16, None)
682-   
683-    But this call:
684-        v(17, None)
685-    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
686-    """
687-    def __init__(self, power_of):
688-        self.power_of = power_of
689-
690-    def __call__(self, field_data, all_data):
691-        from math import log
692-        val = log(int(field_data)) / log(self.power_of)
693-        if val != int(val):
694-            raise ValidationError, _("This value must be a power of %s.") % self.power_of
695-
696-class IsValidDecimal(object):
697-    def __init__(self, max_digits, decimal_places):
698-        self.max_digits, self.decimal_places = max_digits, decimal_places
699-
700-    def __call__(self, field_data, all_data):
701-        try:
702-            val = Decimal(field_data)
703-        except DecimalException:
704-            raise ValidationError, _("Please enter a valid decimal number.")
705-
706-        pieces = str(val).lstrip("-").split('.')
707-        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
708-        digits = len(pieces[0])
709-
710-        if digits + decimals > self.max_digits:
711-            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
712-                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
713-        if digits > (self.max_digits - self.decimal_places):
714-            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
715-                "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)
716-        if decimals > self.decimal_places:
717-            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
718-                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
719-
720-def isValidFloat(field_data, all_data):
721-    data = smart_str(field_data)
722-    try:
723-        float(data)
724-    except ValueError:
725-        raise ValidationError, _("Please enter a valid floating point number.")
726-
727-class HasAllowableSize(object):
728-    """
729-    Checks that the file-upload field data is a certain size. min_size and
730-    max_size are measurements in bytes.
731-    """
732-    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
733-        self.min_size, self.max_size = min_size, max_size
734-        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)
735-        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)
736-
737-    def __call__(self, field_data, all_data):
738-        try:
739-            content = field_data['content']
740-        except TypeError:
741-            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
742-        if self.min_size is not None and len(content) < self.min_size:
743-            raise ValidationError, self.min_error_message
744-        if self.max_size is not None and len(content) > self.max_size:
745-            raise ValidationError, self.max_error_message
746-
747-class MatchesRegularExpression(object):
748-    """
749-    Checks that the field matches the given regular-expression. The regex
750-    should be in string format, not already compiled.
751-    """
752-    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
753-        self.regexp = re.compile(regexp)
754-        self.error_message = error_message
755-
756-    def __call__(self, field_data, all_data):
757-        if not self.regexp.search(field_data):
758-            raise ValidationError(self.error_message)
759-
760-class AnyValidator(object):
761-    """
762-    This validator tries all given validators. If any one of them succeeds,
763-    validation passes. If none of them succeeds, the given message is thrown
764-    as a validation error. The message is rather unspecific, so it's best to
765-    specify one on instantiation.
766-    """
767-    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
768-        if validator_list is None: validator_list = []
769-        self.validator_list = validator_list
770-        self.error_message = error_message
771-        for v in validator_list:
772-            if hasattr(v, 'always_test'):
773-                self.always_test = True
774-
775-    def __call__(self, field_data, all_data):
776-        for v in self.validator_list:
777-            try:
778-                v(field_data, all_data)
779-                return
780-            except ValidationError, e:
781-                pass
782-        raise ValidationError(self.error_message)
783+        raise ValidationError, _("The URL %s is a broken link.") % value
784 
785 class URLMimeTypeCheck(object):
786     "Checks that the provided URL points to a document with a listed mime type"
787@@ -524,79 +100,27 @@ class URLMimeTypeCheck(object):
788     def __init__(self, mime_type_list):
789         self.mime_type_list = mime_type_list
790 
791-    def __call__(self, field_data, all_data):
792-        import urllib2
793+    def __call__(self, value):
794+        validate_existing_url(value)
795         try:
796-            isValidURL(field_data, all_data)
797-        except ValidationError:
798-            raise
799-        try:
800-            info = urllib2.urlopen(field_data).info()
801+            info = urllib2.urlopen(value).info()
802         except (urllib2.HTTPError, urllib2.URLError):
803-            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
804+            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % value
805         content_type = info['content-type']
806         if content_type not in self.mime_type_list:
807             raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
808-                'url': field_data, 'contenttype': content_type}
809+                'url': value, 'contenttype': content_type}
810 
811-class RelaxNGCompact(object):
812-    "Validate against a Relax NG compact schema"
813-    def __init__(self, schema_path, additional_root_element=None):
814-        self.schema_path = schema_path
815-        self.additional_root_element = additional_root_element
816+def validate_imaga_url(value):
817+    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
818+    try:
819+        uc(value)
820+    except URLMimeTypeCheck.InvalidContentType:
821+        raise ValidationError, _("The URL %s does not point to a valid image.") % value
822 
823-    def __call__(self, field_data, all_data):
824-        import os, tempfile
825-        if self.additional_root_element:
826-            field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
827-                'are': self.additional_root_element,
828-                'data': field_data
829-            }
830-        filename = tempfile.mktemp() # Insecure, but nothing else worked
831-        fp = open(filename, 'w')
832-        fp.write(field_data)
833-        fp.close()
834-        if not os.path.exists(settings.JING_PATH):
835-            raise Exception, "%s not found!" % settings.JING_PATH
836-        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
837-        errors = [line.strip() for line in p.readlines()]
838-        p.close()
839-        os.unlink(filename)
840-        display_errors = []
841-        lines = field_data.split('\n')
842-        for error in errors:
843-            ignored, line, level, message = error.split(':', 3)
844-            # Scrape the Jing error messages to reword them more nicely.
845-            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
846-            if m:
847-                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
848-                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
849-                continue
850-            if message.strip() == 'text not allowed here':
851-                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
852-                    {'line':line, 'start':lines[int(line) - 1][:30]})
853-                continue
854-            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
855-            if m:
856-                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
857-                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
858-                continue
859-            m = re.search(r'\s*unknown element "(.*?)"', message)
860-            if m:
861-                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
862-                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
863-                continue
864-            if message.strip() == 'required attributes missing':
865-                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
866-                    {'line':line, 'start':lines[int(line) - 1][:30]})
867-                continue
868-            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
869-            if m:
870-                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
871-                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
872-                continue
873-            # Failing all those checks, use the default error message.
874-            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
875-            display_errors.append(display_error)
876-        if len(display_errors) > 0:
877-            raise ValidationError, display_errors
878+def validate_float(value):
879+    data = smart_str(value)
880+    try:
881+        float(data)
882+    except ValueError:
883+        raise ValidationError, _("Please enter a valid floating point number.")
884diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
885index 86763d9..afd9151 100644
886--- a/django/db/models/__init__.py
887+++ b/django/db/models/__init__.py
888@@ -1,6 +1,5 @@
889 from django.conf import settings
890 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
891-from django.core import validators
892 from django.db import connection
893 from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
894 from django.db.models.query import Q
895diff --git a/django/db/models/base.py b/django/db/models/base.py
896index 4f034bc..225fa77 100644
897--- a/django/db/models/base.py
898+++ b/django/db/models/base.py
899@@ -1,6 +1,6 @@
900 import django.db.models.manipulators
901 import django.db.models.manager
902-from django.core import validators
903+from django.core.validation import ValidationError, NON_FIELD_ERRORS
904 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
905 from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
906 from django.db.models.fields.related import OneToOneRel, ManyToOneRel
907@@ -13,6 +13,7 @@ from django.dispatch import dispatcher
908 from django.utils.datastructures import SortedDict
909 from django.utils.functional import curry
910 from django.utils.encoding import smart_str, force_unicode, smart_unicode
911+from django.utils.translation import ugettext as _
912 from django.conf import settings
913 from itertools import izip
914 import types
915@@ -277,27 +278,62 @@ class Model(object):
916 
917     save.alters_data = True
918 
919-    def validate(self):
920+    def clean(self, new_data=None):
921+        self.to_python()
922+        self.validate(new_data)
923+
924+    def to_python(self):
925+        error_dict = {}
926+        for f in self._meta.fields:
927+            try:
928+                value = f.to_python(getattr(self, f.attname, f.get_default()))
929+                setattr(self, f.attname, value)
930+            except ValidationError, e:
931+                error_dict[f.name] = e.messages
932+        if error_dict:
933+            raise ValidationError(error_dict)
934+
935+    def validate(self, new_data=None):
936         """
937         First coerces all fields on this instance to their proper Python types.
938         Then runs validation on every field. Returns a dictionary of
939         field_name -> error_list.
940         """
941+        if new_data is not None:
942+            def get_value(f):
943+                if f.name in new_data:
944+                    return f.to_python(new_data[f.name])
945+                return getattr(self, f.attname, f.get_default())
946+        else:
947+            get_value = lambda f: getattr(self, f.attname, f.get_default())
948         error_dict = {}
949-        invalid_python = {}
950         for f in self._meta.fields:
951             try:
952-                setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default())))
953-            except validators.ValidationError, e:
954+                value = get_value(f)
955+                f.validate(value, instance=self)
956+                if hasattr(self, 'validate_%s' % f.name):
957+                    getattr(self, 'validate_%s' % f.name)(value)
958+            except ValidationError, e:
959                 error_dict[f.name] = e.messages
960-                invalid_python[f.name] = 1
961-        for f in self._meta.fields:
962-            if f.name in invalid_python:
963-                continue
964-            errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__)
965-            if errors:
966-                error_dict[f.name] = errors
967-        return error_dict
968+
969+        for un_together in self._meta.unique_together:
970+            lookup = {}
971+            for name in un_together:
972+                if name in error_dict:
973+                    break
974+                f = self._meta.get_field(name)
975+                lookup['%s__exact' % name] = get_value(f)
976+            try:
977+                qset = self.__class__._default_manager.all()
978+                if self.pk:
979+                    qset = qset.exclude(pk=self.pk)
980+                obj = qset.get(**lookup)
981+                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together)
982+            except self.DoesNotExist:
983+                pass
984+
985+        if error_dict:
986+            raise ValidationError(error_dict)
987 
988     def _collect_sub_objects(self, seen_objs):
989         """
990diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
991index 13a84ec..1233f53 100644
992--- a/django/db/models/fields/__init__.py
993+++ b/django/db/models/fields/__init__.py
994@@ -10,6 +10,7 @@ from django.db import get_creation_module
995 from django.db.models import signals
996 from django.dispatch import dispatcher
997 from django.conf import settings
998+from django.oldforms import validators as oldvalidators
999 from django.core import validators
1000 from django import oldforms
1001 from django import newforms as forms
1002@@ -78,12 +79,14 @@ class Field(object):
1003     # Tracks each time a Field instance is created. Used to retain order.
1004     creation_counter = 0
1005 
1006+    validators = []
1007+
1008     def __init__(self, verbose_name=None, name=None, primary_key=False,
1009         max_length=None, unique=False, blank=False, null=False, db_index=False,
1010         core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
1011         prepopulate_from=None, unique_for_date=None, unique_for_month=None,
1012         unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
1013-        help_text='', db_column=None, db_tablespace=None):
1014+        help_text='', db_column=None, db_tablespace=None, validators=[]):
1015         self.name = name
1016         self.verbose_name = verbose_name
1017         self.primary_key = primary_key
1018@@ -96,6 +99,7 @@ class Field(object):
1019         self.core, self.rel, self.default = core, rel, default
1020         self.editable = editable
1021         self.serialize = serialize
1022+        self.validators = validators + self.validators
1023         self.validator_list = validator_list or []
1024         self.prepopulate_from = prepopulate_from
1025         self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
1026@@ -151,28 +155,27 @@ class Field(object):
1027             return None
1028         return data_types[internal_type] % self.__dict__
1029 
1030-    def validate_full(self, field_data, all_data):
1031-        """
1032-        Returns a list of errors for this field. This is the main interface,
1033-        as it encapsulates some basic validation logic used by all fields.
1034-        Subclasses should implement validate(), not validate_full().
1035-        """
1036-        if not self.blank and not field_data:
1037-            return [_('This field is required.')]
1038-        try:
1039-            self.validate(field_data, all_data)
1040-        except validators.ValidationError, e:
1041-            return e.messages
1042-        return []
1043-
1044-    def validate(self, field_data, all_data):
1045+    def validate(self, value, instance=None):
1046         """
1047-        Raises validators.ValidationError if field_data has any errors.
1048+        Raises validators.ValidationError if value has any errors.
1049         Subclasses should override this to specify field-specific validation
1050-        logic. This method should assume field_data has already been converted
1051+        logic. This method should assume value has already been converted
1052         into the appropriate data type by Field.to_python().
1053         """
1054-        pass
1055+        if not self.blank and self.editable and not value:
1056+            raise validators.ValidationError(_('This field is required.'))
1057+        elist = []
1058+        for validator in self.validators:
1059+            validator(value)   
1060+        if self.unique and instance:
1061+            try:
1062+                qset = instance.__class__._default_manager.all()
1063+                if instance.pk:
1064+                    qset = qset.exclude(pk=instance.pk)
1065+                obj = qset.get(**{'%s__exact' % self.name : value})
1066+                raise validators.ValidationError(_('This field must be unique'))
1067+            except instance.DoesNotExist:
1068+                pass
1069 
1070     def set_attributes_from_name(self, name):
1071         self.name = name
1072@@ -324,7 +327,7 @@ class Field(object):
1073                     core_field_names.extend(f.get_manipulator_field_names(name_prefix))
1074             # Now, if there are any, add the validator to this FormField.
1075             if core_field_names:
1076-                params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
1077+                params['validator_list'].append(oldvalidators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
1078 
1079         # Finally, add the field_names.
1080         field_names = self.get_manipulator_field_names(name_prefix)
1081@@ -520,7 +523,6 @@ class DateField(Field):
1082             return value.date()
1083         if isinstance(value, datetime.date):
1084             return value
1085-        validators.isValidANSIDate(value, None)
1086         try:
1087             return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
1088         except ValueError:
1089@@ -713,6 +715,7 @@ class DecimalField(Field):
1090         return super(DecimalField, self).formfield(**defaults)
1091 
1092 class EmailField(CharField):
1093+    validators = [validators.validate_email]
1094     def __init__(self, *args, **kwargs):
1095         kwargs['max_length'] = kwargs.get('max_length', 75)
1096         CharField.__init__(self, *args, **kwargs)
1097@@ -720,9 +723,6 @@ class EmailField(CharField):
1098     def get_manipulator_field_objs(self):
1099         return [oldforms.EmailField]
1100 
1101-    def validate(self, field_data, all_data):
1102-        validators.isValidEmail(field_data, all_data)
1103-
1104     def formfield(self, **kwargs):
1105         defaults = {'form_class': forms.EmailField}
1106         defaults.update(kwargs)
1107@@ -756,7 +756,7 @@ class FileField(Field):
1108                         self.always_test = True
1109                     def __call__(self, field_data, all_data):
1110                         if not all_data.get(self.other_file_field_name, False):
1111-                            c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
1112+                            c = oldvalidators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
1113                             c(field_data, all_data)
1114                 # First, get the core fields, if any.
1115                 core_field_names = []
1116@@ -767,7 +767,7 @@ class FileField(Field):
1117                 if core_field_names:
1118                     field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
1119             else:
1120-                v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
1121+                v = oldvalidators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
1122                 v.always_test = True
1123                 field_list[0].validator_list.append(v)
1124                 field_list[0].is_required = field_list[1].is_required = False
1125@@ -925,6 +925,8 @@ class IntegerField(Field):
1126 
1127 class IPAddressField(Field):
1128     empty_strings_allowed = False
1129+    validators = [validators.validate_ip_address4]
1130+
1131     def __init__(self, *args, **kwargs):
1132         kwargs['max_length'] = 15
1133         Field.__init__(self, *args, **kwargs)
1134@@ -935,9 +937,6 @@ class IPAddressField(Field):
1135     def get_internal_type(self):
1136         return "IPAddressField"
1137 
1138-    def validate(self, field_data, all_data):
1139-        validators.isValidIPAddress4(field_data, None)
1140-
1141     def formfield(self, **kwargs):
1142         defaults = {'form_class': forms.IPAddressField}
1143         defaults.update(kwargs)
1144@@ -968,15 +967,14 @@ class NullBooleanField(Field):
1145         return super(NullBooleanField, self).formfield(**defaults)
1146 
1147 class PhoneNumberField(IntegerField):
1148+    validators = [validators.validate_phone_number]
1149+
1150     def get_manipulator_field_objs(self):
1151         return [oldforms.PhoneNumberField]
1152 
1153     def get_internal_type(self):
1154         return "PhoneNumberField"
1155 
1156-    def validate(self, field_data, all_data):
1157-        validators.isValidPhone(field_data, all_data)
1158-
1159     def formfield(self, **kwargs):
1160         from django.contrib.localflavor.us.forms import USPhoneNumberField
1161         defaults = {'form_class': USPhoneNumberField}
1162@@ -1008,9 +1006,9 @@ class PositiveSmallIntegerField(IntegerField):
1163         return super(PositiveSmallIntegerField, self).formfield(**defaults)
1164 
1165 class SlugField(CharField):
1166+    validators = [validators.validate_slug]
1167     def __init__(self, *args, **kwargs):
1168         kwargs['max_length'] = kwargs.get('max_length', 50)
1169-        kwargs.setdefault('validator_list', []).append(validators.isSlug)
1170         # Set db_index=True unless it's been set manually.
1171         if 'db_index' not in kwargs:
1172             kwargs['db_index'] = True
1173@@ -1106,7 +1104,7 @@ class URLField(CharField):
1174     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
1175         kwargs['max_length'] = kwargs.get('max_length', 200)
1176         if verify_exists:
1177-            kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
1178+            kwargs.setdefault('validators', []).append(validators.validate_existing_url)
1179         self.verify_exists = verify_exists
1180         CharField.__init__(self, verbose_name, name, **kwargs)
1181 
1182diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
1183index eceb493..c7d9ab3 100644
1184--- a/django/db/models/fields/related.py
1185+++ b/django/db/models/fields/related.py
1186@@ -747,7 +747,7 @@ class ManyToManyField(RelatedField, Field):
1187         objects = mod._default_manager.in_bulk(pks)
1188         if len(objects) != len(pks):
1189             badkeys = [k for k in pks if k not in objects]
1190-            raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
1191+            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
1192                     "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
1193                 'self': self.verbose_name,
1194                 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
1195diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
1196index 0d9c68f..6f881f0 100644
1197--- a/django/newforms/__init__.py
1198+++ b/django/newforms/__init__.py
1199@@ -10,7 +10,7 @@ TODO:
1200     "This form field requires foo.js" and form.js_includes()
1201 """
1202 
1203-from util import ValidationError
1204+from django.core.validation import ValidationError
1205 from widgets import *
1206 from fields import *
1207 from forms import *
1208diff --git a/django/newforms/fields.py b/django/newforms/fields.py
1209index 08e8b84..a7426f7 100644
1210--- a/django/newforms/fields.py
1211+++ b/django/newforms/fields.py
1212@@ -19,8 +19,8 @@ except NameError:
1213 
1214 from django.utils.translation import ugettext_lazy as _
1215 from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
1216+from django.core.validation import ValidationError, ErrorList, TypeCoercionError
1217 
1218-from util import ErrorList, ValidationError
1219 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
1220 
1221 
1222@@ -41,6 +41,7 @@ EMPTY_VALUES = (None, '')
1223 
1224 class Field(object):
1225     widget = TextInput # Default widget to use when rendering this type of Field.
1226+    validators = []
1227     hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
1228     default_error_messages = {
1229         'required': _(u'This field is required.'),
1230@@ -51,7 +52,7 @@ class Field(object):
1231     creation_counter = 0
1232 
1233     def __init__(self, required=True, widget=None, label=None, initial=None,
1234-                 help_text=None, error_messages=None):
1235+                 help_text=None, error_messages=None, validators=[]):
1236         # required -- Boolean that specifies whether the field is required.
1237         #             True by default.
1238         # widget -- A Widget class, or instance of a Widget class, that should
1239@@ -65,8 +66,10 @@ class Field(object):
1240         # initial -- A value to use in this Field's initial display. This value
1241         #            is *not* used as a fallback if data isn't given.
1242         # help_text -- An optional string to use as "help text" for this Field.
1243+        # validators -- Optional list of additional validator functions
1244         if label is not None:
1245             label = smart_unicode(label)
1246+        self.validators = self.validators + validators
1247         self.required, self.label, self.initial = required, label, initial
1248         self.help_text = smart_unicode(help_text or '')
1249         widget = widget or self.widget
1250@@ -94,6 +97,23 @@ class Field(object):
1251         messages.update(error_messages or {})
1252         self.error_messages = messages
1253 
1254+    def to_python(self, value):
1255+        if value in EMPTY_VALUES:
1256+            return None
1257+        return smart_unicode(value)
1258+
1259+    def validate(self, value):
1260+        if self.required and value in EMPTY_VALUES:
1261+            raise ValidationError(self.error_messages['required'])
1262+        elist = ErrorList()
1263+        for validator in self.validators:
1264+            try:
1265+                validator(value)
1266+            except ValidationError, e:
1267+                elist.extend(e.messages)
1268+        if elist:
1269+            raise ValidationError(elist)
1270+
1271     def clean(self, value):
1272         """
1273         Validates the given value and returns its "cleaned" value as an
1274@@ -101,8 +121,8 @@ class Field(object):
1275 
1276         Raises ValidationError for any errors.
1277         """
1278-        if self.required and value in EMPTY_VALUES:
1279-            raise ValidationError(self.error_messages['required'])
1280+        value = self.to_python(value)
1281+        self.validate(value)
1282         return value
1283 
1284     def widget_attrs(self, widget):
1285@@ -129,18 +149,21 @@ class CharField(Field):
1286         self.max_length, self.min_length = max_length, min_length
1287         super(CharField, self).__init__(*args, **kwargs)
1288 
1289-    def clean(self, value):
1290-        "Validates max_length and min_length. Returns a Unicode object."
1291-        super(CharField, self).clean(value)
1292+    def to_python(self, value):
1293         if value in EMPTY_VALUES:
1294             return u''
1295-        value = smart_unicode(value)
1296+        return smart_unicode(value)
1297+
1298+    def validate(self, value):
1299+        "Validates max_length and min_length. Returns a Unicode object."
1300+        super(CharField, self).validate(value)
1301         value_length = len(value)
1302+        if value_length == 0 and not self.required:
1303+            return
1304         if self.max_length is not None and value_length > self.max_length:
1305             raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
1306         if self.min_length is not None and value_length < self.min_length:
1307             raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
1308-        return value
1309 
1310     def widget_attrs(self, widget):
1311         if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
1312@@ -158,23 +181,25 @@ class IntegerField(Field):
1313         self.max_value, self.min_value = max_value, min_value
1314         super(IntegerField, self).__init__(*args, **kwargs)
1315 
1316-    def clean(self, value):
1317-        """
1318-        Validates that int() can be called on the input. Returns the result
1319-        of int(). Returns None for empty values.
1320-        """
1321-        super(IntegerField, self).clean(value)
1322+    def to_python(self, value):
1323         if value in EMPTY_VALUES:
1324             return None
1325         try:
1326-            value = int(str(value))
1327+            return int(smart_str(value))
1328         except (ValueError, TypeError):
1329-            raise ValidationError(self.error_messages['invalid'])
1330+            raise TypeCoercionError(self.error_messages['invalid'])
1331+
1332+    def validate(self, value):
1333+        """
1334+        Validates that int() can be called on the input. Returns the result
1335+        of int(). Returns None for empty values.
1336+        """
1337+        super(IntegerField, self).validate(value)
1338+        if value is None: return
1339         if self.max_value is not None and value > self.max_value:
1340             raise ValidationError(self.error_messages['max_value'] % self.max_value)
1341         if self.min_value is not None and value < self.min_value:
1342             raise ValidationError(self.error_messages['min_value'] % self.min_value)
1343-        return value
1344 
1345 class FloatField(Field):
1346     default_error_messages = {
1347@@ -187,23 +212,25 @@ class FloatField(Field):
1348         self.max_value, self.min_value = max_value, min_value
1349         Field.__init__(self, *args, **kwargs)
1350 
1351-    def clean(self, value):
1352+    def to_python(self, value):
1353         """
1354         Validates that float() can be called on the input. Returns a float.
1355         Returns None for empty values.
1356         """
1357-        super(FloatField, self).clean(value)
1358-        if not self.required and value in EMPTY_VALUES:
1359+        if value in EMPTY_VALUES:
1360             return None
1361         try:
1362-            value = float(value)
1363+            return float(value)
1364         except (ValueError, TypeError):
1365-            raise ValidationError(self.error_messages['invalid'])
1366+            raise TypeCoercionError(self.error_messages['invalid'])
1367+
1368+    def validate(self, value):
1369+        super(FloatField, self).validate(value)
1370+        if value is None: return
1371         if self.max_value is not None and value > self.max_value:
1372             raise ValidationError(self.error_messages['max_value'] % self.max_value)
1373         if self.min_value is not None and value < self.min_value:
1374             raise ValidationError(self.error_messages['min_value'] % self.min_value)
1375-        return value
1376 
1377 class DecimalField(Field):
1378     default_error_messages = {
1379@@ -220,21 +247,24 @@ class DecimalField(Field):
1380         self.max_digits, self.decimal_places = max_digits, decimal_places
1381         Field.__init__(self, *args, **kwargs)
1382 
1383-    def clean(self, value):
1384+    def to_python(self, value):
1385         """
1386         Validates that the input is a decimal number. Returns a Decimal
1387         instance. Returns None for empty values. Ensures that there are no more
1388         than max_digits in the number, and no more than decimal_places digits
1389         after the decimal point.
1390         """
1391-        super(DecimalField, self).clean(value)
1392-        if not self.required and value in EMPTY_VALUES:
1393+        if value in EMPTY_VALUES:
1394             return None
1395         value = smart_str(value).strip()
1396         try:
1397-            value = Decimal(value)
1398+            return Decimal(value)
1399         except DecimalException:
1400-            raise ValidationError(self.error_messages['invalid'])
1401+            raise TypeCoercionError(self.error_messages['invalid'])
1402+
1403+    def validate(self, value):
1404+        super(DecimalField, self).validate(value)
1405+        if value is None: return
1406         pieces = str(value).lstrip("-").split('.')
1407         decimals = (len(pieces) == 2) and len(pieces[1]) or 0
1408         digits = len(pieces[0])
1409@@ -248,7 +278,6 @@ class DecimalField(Field):
1410             raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
1411         if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
1412             raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
1413-        return value
1414 
1415 DEFAULT_DATE_INPUT_FORMATS = (
1416     '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
1417@@ -267,12 +296,11 @@ class DateField(Field):
1418         super(DateField, self).__init__(*args, **kwargs)
1419         self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
1420 
1421-    def clean(self, value):
1422+    def to_python(self, value):
1423         """
1424         Validates that the input can be converted to a date. Returns a Python
1425         datetime.date object.
1426         """
1427-        super(DateField, self).clean(value)
1428         if value in EMPTY_VALUES:
1429             return None
1430         if isinstance(value, datetime.datetime):
1431@@ -284,7 +312,7 @@ class DateField(Field):
1432                 return datetime.date(*time.strptime(value, format)[:3])
1433             except ValueError:
1434                 continue
1435-        raise ValidationError(self.error_messages['invalid'])
1436+        raise TypeCoercionError(self.error_messages['invalid'])
1437 
1438 DEFAULT_TIME_INPUT_FORMATS = (
1439     '%H:%M:%S',     # '14:30:59'
1440@@ -300,12 +328,11 @@ class TimeField(Field):
1441         super(TimeField, self).__init__(*args, **kwargs)
1442         self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
1443 
1444-    def clean(self, value):
1445+    def to_python(self, value):
1446         """
1447         Validates that the input can be converted to a time. Returns a Python
1448         datetime.time object.
1449         """
1450-        super(TimeField, self).clean(value)
1451         if value in EMPTY_VALUES:
1452             return None
1453         if isinstance(value, datetime.time):
1454@@ -315,7 +342,7 @@ class TimeField(Field):
1455                 return datetime.time(*time.strptime(value, format)[3:6])
1456             except ValueError:
1457                 continue
1458-        raise ValidationError(self.error_messages['invalid'])
1459+        raise TypeCoercionError(self.error_messages['invalid'])
1460 
1461 DEFAULT_DATETIME_INPUT_FORMATS = (
1462     '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
1463@@ -339,12 +366,11 @@ class DateTimeField(Field):
1464         super(DateTimeField, self).__init__(*args, **kwargs)
1465         self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
1466 
1467-    def clean(self, value):
1468+    def to_python(self, value):
1469         """
1470         Validates that the input can be converted to a datetime. Returns a
1471         Python datetime.datetime object.
1472         """
1473-        super(DateTimeField, self).clean(value)
1474         if value in EMPTY_VALUES:
1475             return None
1476         if isinstance(value, datetime.datetime):
1477@@ -362,7 +388,7 @@ class DateTimeField(Field):
1478                 return datetime.datetime(*time.strptime(value, format)[:6])
1479             except ValueError:
1480                 continue
1481-        raise ValidationError(self.error_messages['invalid'])
1482+        raise TypeCoercionError(self.error_messages['invalid'])
1483 
1484 class RegexField(CharField):
1485     def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
1486@@ -381,17 +407,16 @@ class RegexField(CharField):
1487             regex = re.compile(regex)
1488         self.regex = regex
1489 
1490-    def clean(self, value):
1491+    def validate(self, value):
1492         """
1493         Validates that the input matches the regular expression. Returns a
1494         Unicode object.
1495         """
1496-        value = super(RegexField, self).clean(value)
1497-        if value == u'':
1498-            return value
1499+        super(RegexField, self).validate(value)
1500+        if value in EMPTY_VALUES:
1501+            return u''
1502         if not self.regex.search(value):
1503             raise ValidationError(self.error_messages['invalid'])
1504-        return value
1505 
1506 email_re = re.compile(
1507     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
1508@@ -431,58 +456,60 @@ class FileField(Field):
1509     widget = FileInput
1510     default_error_messages = {
1511         'invalid': _(u"No file was submitted. Check the encoding type on the form."),
1512-        'missing': _(u"No file was submitted."),
1513         'empty': _(u"The submitted file is empty."),
1514     }
1515 
1516-    def __init__(self, *args, **kwargs):
1517-        super(FileField, self).__init__(*args, **kwargs)
1518-
1519-    def clean(self, data, initial=None):
1520-        super(FileField, self).clean(initial or data)
1521+    def to_python(self, data, initial=None):
1522         if not self.required and data in EMPTY_VALUES:
1523             return None
1524         elif not data and initial:
1525             return initial
1526+        elif not data:
1527+            return None
1528+
1529         try:
1530             f = UploadedFile(data['filename'], data['content'])
1531         except TypeError:
1532-            raise ValidationError(self.error_messages['invalid'])
1533+            raise TypeCoercionError(self.error_messages['invalid'])
1534         except KeyError:
1535-            raise ValidationError(self.error_messages['missing'])
1536+            raise ValidationError(self.error_messages['required'])
1537         if not f.content:
1538             raise ValidationError(self.error_messages['empty'])
1539         return f
1540 
1541+    def clean(self, data, initial=None):
1542+        "overriden clean to provide extra argument initial"
1543+        value = self.to_python(data, initial)
1544+        self.validate(value)
1545+        return value
1546+
1547 class ImageField(FileField):
1548     default_error_messages = {
1549         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
1550     }
1551 
1552-    def clean(self, data, initial=None):
1553+    def validate(self, value):
1554         """
1555         Checks that the file-upload field data contains a valid image (GIF, JPG,
1556         PNG, possibly others -- whatever the Python Imaging Library supports).
1557         """
1558-        f = super(ImageField, self).clean(data, initial)
1559-        if f is None:
1560-            return None
1561-        elif not data and initial:
1562-            return initial
1563+        super(ImageField, self).validate(value)
1564+
1565+        if value is None:
1566+            return
1567         from PIL import Image
1568         from cStringIO import StringIO
1569         try:
1570             # load() is the only method that can spot a truncated JPEG,
1571             #  but it cannot be called sanely after verify()
1572-            trial_image = Image.open(StringIO(f.content))
1573+            trial_image = Image.open(StringIO(value.content))
1574             trial_image.load()
1575             # verify() is the only method that can spot a corrupt PNG,
1576             #  but it must be called immediately after the constructor
1577-            trial_image = Image.open(StringIO(f.content))
1578+            trial_image = Image.open(StringIO(value.content))
1579             trial_image.verify()
1580         except Exception: # Python Imaging Library doesn't recognize it as an image
1581             raise ValidationError(self.error_messages['invalid_image'])
1582-        return f
1583 
1584 url_re = re.compile(
1585     r'^https?://' # http:// or https://
1586@@ -505,13 +532,17 @@ class URLField(RegexField):
1587         self.verify_exists = verify_exists
1588         self.user_agent = validator_user_agent
1589 
1590-    def clean(self, value):
1591-        # If no URL scheme given, assume http://
1592+    def to_python(self, value):
1593+        value = super(URLField, self).to_python(value)
1594         if value and '://' not in value:
1595             value = u'http://%s' % value
1596-        value = super(URLField, self).clean(value)
1597-        if value == u'':
1598-            return value
1599+        return value
1600+
1601+    def validate(self, value):
1602+        # If no URL scheme given, assume http://
1603+        super(URLField, self).validate(value)
1604+        if value in EMPTY_VALUES:
1605+            return
1606         if self.verify_exists:
1607             import urllib2
1608             from django.conf import settings
1609@@ -529,14 +560,14 @@ class URLField(RegexField):
1610                 raise ValidationError(self.error_messages['invalid'])
1611             except: # urllib2.URLError, httplib.InvalidURL, etc.
1612                 raise ValidationError(self.error_messages['invalid_link'])
1613-        return value
1614 
1615 class BooleanField(Field):
1616     widget = CheckboxInput
1617 
1618-    def clean(self, value):
1619+    def to_python(self, value):
1620         """Returns a Python boolean object."""
1621-        super(BooleanField, self).clean(value)
1622+        if self.required and value in EMPTY_VALUES:
1623+            raise ValidationError(self.error_messages['required'])
1624         # Explicitly check for the string 'False', which is what a hidden field
1625         # will submit for False. Because bool("True") == True, we don't need to
1626         # handle that explicitly.
1627@@ -544,16 +575,23 @@ class BooleanField(Field):
1628             return False
1629         return bool(value)
1630 
1631+
1632 class NullBooleanField(BooleanField):
1633     """
1634     A field whose valid values are None, True and False. Invalid values are
1635     cleaned to None.
1636+
1637+    Note that validation doesn't apply here.
1638     """
1639     widget = NullBooleanSelect
1640 
1641-    def clean(self, value):
1642+    def to_python(self, value):
1643         return {True: True, False: False}.get(value, None)
1644 
1645+    def validate(self, value):
1646+        pass
1647+
1648+
1649 class ChoiceField(Field):
1650     widget = Select
1651     default_error_messages = {
1652@@ -577,20 +615,13 @@ class ChoiceField(Field):
1653 
1654     choices = property(_get_choices, _set_choices)
1655 
1656-    def clean(self, value):
1657-        """
1658-        Validates that the input is in self.choices.
1659-        """
1660-        value = super(ChoiceField, self).clean(value)
1661-        if value in EMPTY_VALUES:
1662-            value = u''
1663-        value = smart_unicode(value)
1664-        if value == u'':
1665-            return value
1666+    def validate(self, value):
1667+        super(ChoiceField, self).validate(value)
1668+        if value in EMPTY_VALUES and not self.required:
1669+            return u''
1670         valid_values = set([smart_unicode(k) for k, v in self.choices])
1671         if value not in valid_values:
1672             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
1673-        return value
1674 
1675 class MultipleChoiceField(ChoiceField):
1676     hidden_widget = MultipleHiddenInput
1677@@ -600,23 +631,24 @@ class MultipleChoiceField(ChoiceField):
1678         'invalid_list': _(u'Enter a list of values.'),
1679     }
1680 
1681-    def clean(self, value):
1682+    def to_python(self, value):
1683         """
1684         Validates that the input is a list or tuple.
1685         """
1686-        if self.required and not value:
1687-            raise ValidationError(self.error_messages['required'])
1688-        elif not self.required and not value:
1689+        if not value:
1690             return []
1691         if not isinstance(value, (list, tuple)):
1692-            raise ValidationError(self.error_messages['invalid_list'])
1693-        new_value = [smart_unicode(val) for val in value]
1694+            raise TypeCoercionError(self.error_messages['invalid_list'])
1695+        return [smart_unicode(val) for val in value]
1696+
1697+    def validate(self, value):
1698         # Validate that each value in the value list is in self.choices.
1699+        if self.required and value == []:
1700+            raise ValidationError(self.error_messages['required'])
1701         valid_values = set([smart_unicode(k) for k, v in self.choices])
1702-        for val in new_value:
1703+        for val in value:
1704             if val not in valid_values:
1705                 raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
1706-        return new_value
1707 
1708 class ComboField(Field):
1709     """
1710@@ -631,15 +663,19 @@ class ComboField(Field):
1711             f.required = False
1712         self.fields = fields
1713 
1714-    def clean(self, value):
1715+    def to_python(self, value):
1716+        for field in self.fields:
1717+            value = field.to_python(value)
1718+        return value
1719+
1720+    def validate(self, value):
1721         """
1722         Validates the given value against all of self.fields, which is a
1723         list of Field instances.
1724         """
1725-        super(ComboField, self).clean(value)
1726+        super(ComboField, self).validate(value)
1727         for field in self.fields:
1728-            value = field.clean(value)
1729-        return value
1730+            field.validate(value)
1731 
1732 class MultiValueField(Field):
1733     """
1734@@ -671,7 +707,7 @@ class MultiValueField(Field):
1735             f.required = False
1736         self.fields = fields
1737 
1738-    def clean(self, value):
1739+    def to_python(self, value):
1740         """
1741         Validates every value in the given list. A value is validated against
1742         the corresponding Field in self.fields.
1743@@ -685,11 +721,12 @@ class MultiValueField(Field):
1744         if not value or isinstance(value, (list, tuple)):
1745             if not value or not [v for v in value if v not in EMPTY_VALUES]:
1746                 if self.required:
1747-                    raise ValidationError(self.error_messages['required'])
1748+                    return None
1749                 else:
1750                     return self.compress([])
1751         else:
1752-            raise ValidationError(self.error_messages['invalid'])
1753+            raise TypeCoercionError(self.error_messages['invalid'])
1754+
1755         for i, field in enumerate(self.fields):
1756             try:
1757                 field_value = value[i]
1758diff --git a/django/newforms/forms.py b/django/newforms/forms.py
1759index 2c481e4..1c06f3c 100644
1760--- a/django/newforms/forms.py
1761+++ b/django/newforms/forms.py
1762@@ -8,14 +8,14 @@ from django.utils.datastructures import SortedDict
1763 from django.utils.html import escape
1764 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
1765 from django.utils.safestring import mark_safe
1766+from django.core.validation import ValidationError, ErrorList, NON_FIELD_ERRORS
1767 
1768 from fields import Field, FileField
1769 from widgets import TextInput, Textarea
1770-from util import flatatt, ErrorDict, ErrorList, ValidationError
1771+from util import flatatt, ErrorDict
1772 
1773 __all__ = ('BaseForm', 'Form')
1774 
1775-NON_FIELD_ERRORS = '__all__'
1776 
1777 def pretty_name(name):
1778     "Converts 'first_name' to 'First name'"
1779@@ -205,22 +205,32 @@ class BaseForm(StrAndUnicode):
1780                 else:
1781                     value = field.clean(value)
1782                 self.cleaned_data[name] = value
1783+                # FIXME deprecated - keeping this here for backwards compatibility
1784                 if hasattr(self, 'clean_%s' % name):
1785                     value = getattr(self, 'clean_%s' % name)()
1786                     self.cleaned_data[name] = value
1787+
1788+                if hasattr(self, 'validate_%s' % name):
1789+                    getattr(self, 'validate_%s' % name)(value)
1790             except ValidationError, e:
1791                 self._errors[name] = e.messages
1792                 if name in self.cleaned_data:
1793                     del self.cleaned_data[name]
1794         try:
1795-            self.cleaned_data = self.clean()
1796+            self.validate()
1797         except ValidationError, e:
1798-            self._errors[NON_FIELD_ERRORS] = e.messages
1799+            if hasattr(e, 'message_dict'):
1800+                for k, v in e.message_dict.items():
1801+                    self._errors.setdefault(k, []).extend(v)
1802+            else:
1803+                self._errors[NON_FIELD_ERRORS] = e.messages
1804         if self._errors:
1805             delattr(self, 'cleaned_data')
1806 
1807     def clean(self):
1808         """
1809+        FIXME: deprecated, use validate() instead
1810+
1811         Hook for doing any extra form-wide cleaning after Field.clean() been
1812         called on every field. Any ValidationError raised by this method will
1813         not be associated with a particular field; it will have a special-case
1814@@ -228,6 +238,9 @@ class BaseForm(StrAndUnicode):
1815         """
1816         return self.cleaned_data
1817 
1818+    def validate(self):
1819+        self.cleaned_data = self.clean()
1820+
1821     def is_multipart(self):
1822         """
1823         Returns True if the form needs to be multipart-encrypted, i.e. it has
1824diff --git a/django/newforms/models.py b/django/newforms/models.py
1825index 0590839..1c6e301 100644
1826--- a/django/newforms/models.py
1827+++ b/django/newforms/models.py
1828@@ -9,8 +9,8 @@ from django.utils.translation import ugettext_lazy as _
1829 from django.utils.encoding import smart_unicode
1830 from django.utils.datastructures import SortedDict
1831 from django.core.exceptions import ImproperlyConfigured
1832+from django.core.validation import ValidationError, ErrorList, TypeCoercionError
1833 
1834-from util import ValidationError, ErrorList
1835 from forms import BaseForm, get_declared_fields
1836 from fields import Field, ChoiceField, EMPTY_VALUES
1837 from widgets import Select, SelectMultiple, MultipleHiddenInput
1838@@ -257,6 +257,12 @@ class BaseModelForm(BaseForm):
1839             object_data.update(initial)
1840         BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
1841 
1842+    def validate(self):
1843+        super(BaseModelForm, self).validate()
1844+        if self._errors:
1845+            return
1846+        self.instance.clean(self.cleaned_data)
1847+
1848     def save(self, commit=True):
1849         """
1850         Saves this ``form``'s cleaned_data into model instance
1851@@ -354,9 +360,10 @@ class ModelChoiceField(ChoiceField):
1852 
1853     choices = property(_get_choices, _set_choices)
1854 
1855-    def clean(self, value):
1856-        Field.clean(self, value)
1857-        if value in EMPTY_VALUES:
1858+    def to_python(self, value):
1859+        if self.required and value in EMPTY_VALUES:
1860+            raise ValidationError(self.error_messages['required'])
1861+        elif value in EMPTY_VALUES:
1862             return None
1863         try:
1864             value = self.queryset.get(pk=value)
1865@@ -364,6 +371,9 @@ class ModelChoiceField(ChoiceField):
1866             raise ValidationError(self.error_messages['invalid_choice'])
1867         return value
1868 
1869+    def validate(self, value):
1870+        pass
1871+
1872 class ModelMultipleChoiceField(ModelChoiceField):
1873     """A MultipleChoiceField whose choices are a model QuerySet."""
1874     hidden_widget = MultipleHiddenInput
1875@@ -380,13 +390,13 @@ class ModelMultipleChoiceField(ModelChoiceField):
1876             cache_choices, required, widget, label, initial, help_text,
1877             *args, **kwargs)
1878 
1879-    def clean(self, value):
1880+    def to_python(self, value):
1881         if self.required and not value:
1882             raise ValidationError(self.error_messages['required'])
1883         elif not self.required and not value:
1884             return []
1885         if not isinstance(value, (list, tuple)):
1886-            raise ValidationError(self.error_messages['list'])
1887+            raise TypeCoercionError(self.error_messages['list'])
1888         final_values = []
1889         for val in value:
1890             try:
1891@@ -396,3 +406,4 @@ class ModelMultipleChoiceField(ModelChoiceField):
1892             else:
1893                 final_values.append(obj)
1894         return final_values
1895+
1896diff --git a/django/newforms/util.py b/django/newforms/util.py
1897index b3edf41..89b93d6 100644
1898--- a/django/newforms/util.py
1899+++ b/django/newforms/util.py
1900@@ -30,40 +30,3 @@ class ErrorDict(dict, StrAndUnicode):
1901     def as_text(self):
1902         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()])
1903 
1904-class ErrorList(list, StrAndUnicode):
1905-    """
1906-    A collection of errors that knows how to display itself in various formats.
1907-    """
1908-    def __unicode__(self):
1909-        return self.as_ul()
1910-
1911-    def as_ul(self):
1912-        if not self: return u''
1913-        return mark_safe(u'<ul class="errorlist">%s</ul>'
1914-                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
1915-
1916-    def as_text(self):
1917-        if not self: return u''
1918-        return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
1919-
1920-    def __repr__(self):
1921-        return repr([force_unicode(e) for e in self])
1922-
1923-class ValidationError(Exception):
1924-    def __init__(self, message):
1925-        """
1926-        ValidationError can be passed any object that can be printed (usually
1927-        a string) or a list of objects.
1928-        """
1929-        if isinstance(message, list):
1930-            self.messages = ErrorList([smart_unicode(msg) for msg in message])
1931-        else:
1932-            message = smart_unicode(message)
1933-            self.messages = ErrorList([message])
1934-
1935-    def __str__(self):
1936-        # This is needed because, without a __str__(), printing an exception
1937-        # instance would result in this:
1938-        # AttributeError: ValidationError instance has no attribute 'args'
1939-        # See http://www.python.org/doc/current/tut/node10.html#handling
1940-        return repr(self.messages)
1941diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
1942index fc87271..b7b0d10 100644
1943--- a/django/oldforms/__init__.py
1944+++ b/django/oldforms/__init__.py
1945@@ -1,4 +1,4 @@
1946-from django.core import validators
1947+from django.oldforms import validators
1948 from django.core.exceptions import PermissionDenied
1949 from django.utils.html import escape
1950 from django.utils.safestring import mark_safe
1951diff --git a/django/oldforms/validators.py b/django/oldforms/validators.py
1952new file mode 100644
1953index 0000000..0ff85c7
1954--- /dev/null
1955+++ b/django/oldforms/validators.py
1956@@ -0,0 +1,589 @@
1957+"""
1958+A library of validators that return None and raise ValidationError when the
1959+provided data isn't valid.
1960+
1961+Validators may be callable classes, and they may have an 'always_test'
1962+attribute. If an 'always_test' attribute exists (regardless of value), the
1963+validator will *always* be run, regardless of whether its associated
1964+form field is required.
1965+"""
1966+
1967+import urllib2
1968+import re
1969+try:
1970+    from decimal import Decimal, DecimalException
1971+except ImportError:
1972+    from django.utils._decimal import Decimal, DecimalException    # Python 2.3
1973+
1974+from django.conf import settings
1975+from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
1976+from django.utils.functional import Promise, lazy
1977+from django.utils.encoding import force_unicode, smart_str
1978+from django.core.validation import ValidationError
1979+
1980+_datere = r'\d{4}-\d{1,2}-\d{1,2}'
1981+_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
1982+alnum_re = re.compile(r'^\w+$')
1983+alnumurl_re = re.compile(r'^[-\w/]+$')
1984+ansi_date_re = re.compile('^%s$' % _datere)
1985+ansi_time_re = re.compile('^%s$' % _timere)
1986+ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
1987+email_re = re.compile(
1988+    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
1989+    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
1990+    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
1991+integer_re = re.compile(r'^-?\d+$')
1992+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}$')
1993+phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
1994+slug_re = re.compile(r'^[-\w]+$')
1995+url_re = re.compile(r'^https?://\S+$')
1996+
1997+lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
1998+
1999+
2000+class CriticalValidationError(Exception):
2001+    def __init__(self, message):
2002+        "ValidationError can be passed a string or a list."
2003+        if isinstance(message, list):
2004+            self.messages = [force_unicode(msg) for msg in message]
2005+        else:
2006+            assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
2007+            self.messages = [force_unicode(message)]
2008+
2009+    def __str__(self):
2010+        return str(self.messages)
2011+
2012+
2013+def isAlphaNumeric(field_data, all_data):
2014+    if not alnum_re.search(field_data):
2015+        raise ValidationError, _("This value must contain only letters, numbers and underscores.")
2016+
2017+def isAlphaNumericURL(field_data, all_data):
2018+    if not alnumurl_re.search(field_data):
2019+        raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
2020+
2021+def isSlug(field_data, all_data):
2022+    if not slug_re.search(field_data):
2023+        raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
2024+
2025+def isLowerCase(field_data, all_data):
2026+    if field_data.lower() != field_data:
2027+        raise ValidationError, _("Uppercase letters are not allowed here.")
2028+
2029+def isUpperCase(field_data, all_data):
2030+    if field_data.upper() != field_data:
2031+        raise ValidationError, _("Lowercase letters are not allowed here.")
2032+
2033+def isCommaSeparatedIntegerList(field_data, all_data):
2034+    for supposed_int in field_data.split(','):
2035+        try:
2036+            int(supposed_int)
2037+        except ValueError:
2038+            raise ValidationError, _("Enter only digits separated by commas.")
2039+
2040+def isCommaSeparatedEmailList(field_data, all_data):
2041+    """
2042+    Checks that field_data is a string of e-mail addresses separated by commas.
2043+    Blank field_data values will not throw a validation error, and whitespace
2044+    is allowed around the commas.
2045+    """
2046+    for supposed_email in field_data.split(','):
2047+        try:
2048+            isValidEmail(supposed_email.strip(), '')
2049+        except ValidationError:
2050+            raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
2051+
2052+def isValidIPAddress4(field_data, all_data):
2053+    if not ip4_re.search(field_data):
2054+        raise ValidationError, _("Please enter a valid IP address.")
2055+
2056+def isNotEmpty(field_data, all_data):
2057+    if field_data.strip() == '':
2058+        raise ValidationError, _("Empty values are not allowed here.")
2059+
2060+def isOnlyDigits(field_data, all_data):
2061+    if not field_data.isdigit():
2062+        raise ValidationError, _("Non-numeric characters aren't allowed here.")
2063+
2064+def isNotOnlyDigits(field_data, all_data):
2065+    if field_data.isdigit():
2066+        raise ValidationError, _("This value can't be comprised solely of digits.")
2067+
2068+def isInteger(field_data, all_data):
2069+    # This differs from isOnlyDigits because this accepts the negative sign
2070+    if not integer_re.search(field_data):
2071+        raise ValidationError, _("Enter a whole number.")
2072+
2073+def isOnlyLetters(field_data, all_data):
2074+    if not field_data.isalpha():
2075+        raise ValidationError, _("Only alphabetical characters are allowed here.")
2076+
2077+def _isValidDate(date_string):
2078+    """
2079+    A helper function used by isValidANSIDate and isValidANSIDatetime to
2080+    check if the date is valid.  The date string is assumed to already be in
2081+    YYYY-MM-DD format.
2082+    """
2083+    from datetime import date
2084+    # Could use time.strptime here and catch errors, but datetime.date below
2085+    # produces much friendlier error messages.
2086+    year, month, day = map(int, date_string.split('-'))
2087+    # This check is needed because strftime is used when saving the date
2088+    # value to the database, and strftime requires that the year be >=1900.
2089+    if year < 1900:
2090+        raise ValidationError, _('Year must be 1900 or later.')
2091+    try:
2092+        date(year, month, day)
2093+    except ValueError, e:
2094+        msg = _('Invalid date: %s') % _(str(e))
2095+        raise ValidationError, msg
2096+
2097+def isValidANSIDate(field_data, all_data):
2098+    if not ansi_date_re.search(field_data):
2099+        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
2100+    _isValidDate(field_data)
2101+
2102+def isValidANSITime(field_data, all_data):
2103+    if not ansi_time_re.search(field_data):
2104+        raise ValidationError, _('Enter a valid time in HH:MM format.')
2105+
2106+def isValidANSIDatetime(field_data, all_data):
2107+    if not ansi_datetime_re.search(field_data):
2108+        raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
2109+    _isValidDate(field_data.split()[0])
2110+
2111+def isValidEmail(field_data, all_data):
2112+    if not email_re.search(field_data):
2113+        raise ValidationError, _('Enter a valid e-mail address.')
2114+
2115+def isValidImage(field_data, all_data):
2116+    """
2117+    Checks that the file-upload field data contains a valid image (GIF, JPG,
2118+    PNG, possibly others -- whatever the Python Imaging Library supports).
2119+    """
2120+    from PIL import Image
2121+    from cStringIO import StringIO
2122+    try:
2123+        content = field_data['content']
2124+    except TypeError:
2125+        raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
2126+    try:
2127+        # load() is the only method that can spot a truncated JPEG,
2128+        #  but it cannot be called sanely after verify()
2129+        trial_image = Image.open(StringIO(content))
2130+        trial_image.load()
2131+        # verify() is the only method that can spot a corrupt PNG,
2132+        #  but it must be called immediately after the constructor
2133+        trial_image = Image.open(StringIO(content))
2134+        trial_image.verify()
2135+    except Exception: # Python Imaging Library doesn't recognize it as an image
2136+        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
2137+
2138+def isValidImageURL(field_data, all_data):
2139+    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
2140+    try:
2141+        uc(field_data, all_data)
2142+    except URLMimeTypeCheck.InvalidContentType:
2143+        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
2144+
2145+def isValidPhone(field_data, all_data):
2146+    if not phone_re.search(field_data):
2147+        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
2148+
2149+def isValidQuicktimeVideoURL(field_data, all_data):
2150+    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
2151+    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
2152+    try:
2153+        uc(field_data, all_data)
2154+    except URLMimeTypeCheck.InvalidContentType:
2155+        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
2156+
2157+def isValidURL(field_data, all_data):
2158+    if not url_re.search(field_data):
2159+        raise ValidationError, _("A valid URL is required.")
2160+
2161+def isValidHTML(field_data, all_data):
2162+    import urllib, urllib2
2163+    try:
2164+        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
2165+    except:
2166+        # Validator or Internet connection is unavailable. Fail silently.
2167+        return
2168+    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
2169+    if html_is_valid:
2170+        return
2171+    from xml.dom.minidom import parseString
2172+    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
2173+    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
2174+
2175+def isWellFormedXml(field_data, all_data):
2176+    from xml.dom.minidom import parseString
2177+    try:
2178+        parseString(field_data)
2179+    except Exception, e: # Naked except because we're not sure what will be thrown
2180+        raise ValidationError, _("Badly formed XML: %s") % str(e)
2181+
2182+def isWellFormedXmlFragment(field_data, all_data):
2183+    isWellFormedXml('<root>%s</root>' % field_data, all_data)
2184+
2185+def isExistingURL(field_data, all_data):
2186+    try:
2187+        headers = {
2188+            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
2189+            "Accept-Language" : "en-us,en;q=0.5",
2190+            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
2191+            "Connection" : "close",
2192+            "User-Agent": settings.URL_VALIDATOR_USER_AGENT
2193+            }
2194+        req = urllib2.Request(field_data,None, headers)
2195+        u = urllib2.urlopen(req)
2196+    except ValueError:
2197+        raise ValidationError, _("Invalid URL: %s") % field_data
2198+    except urllib2.HTTPError, e:
2199+        # 401s are valid; they just mean authorization is required.
2200+        # 301 and 302 are redirects; they just mean look somewhere else.
2201+        if str(e.code) not in ('401','301','302'):
2202+            raise ValidationError, _("The URL %s is a broken link.") % field_data
2203+    except: # urllib2.URLError, httplib.InvalidURL, etc.
2204+        raise ValidationError, _("The URL %s is a broken link.") % field_data
2205+
2206+def isValidUSState(field_data, all_data):
2207+    "Checks that the given string is a valid two-letter U.S. state abbreviation"
2208+    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']
2209+    if field_data.upper() not in states:
2210+        raise ValidationError, _("Enter a valid U.S. state abbreviation.")
2211+
2212+def hasNoProfanities(field_data, all_data):
2213+    """
2214+    Checks that the given string has no profanities in it. This does a simple
2215+    check for whether each profanity exists within the string, so 'fuck' will
2216+    catch 'motherfucker' as well. Raises a ValidationError such as:
2217+        Watch your mouth! The words "f--k" and "s--t" are not allowed here.
2218+    """
2219+    field_data = field_data.lower() # normalize
2220+    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
2221+    if words_seen:
2222+        from django.utils.text import get_text_list
2223+        plural = len(words_seen)
2224+        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
2225+            "Watch your mouth! The words %s are not allowed here.", plural) % \
2226+            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
2227+
2228+class AlwaysMatchesOtherField(object):
2229+    def __init__(self, other_field_name, error_message=None):
2230+        self.other = other_field_name
2231+        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
2232+        self.always_test = True
2233+
2234+    def __call__(self, field_data, all_data):
2235+        if field_data != all_data[self.other]:
2236+            raise ValidationError, self.error_message
2237+
2238+class ValidateIfOtherFieldEquals(object):
2239+    def __init__(self, other_field, other_value, validator_list):
2240+        self.other_field, self.other_value = other_field, other_value
2241+        self.validator_list = validator_list
2242+        self.always_test = True
2243+
2244+    def __call__(self, field_data, all_data):
2245+        if self.other_field in all_data and all_data[self.other_field] == self.other_value:
2246+            for v in self.validator_list:
2247+                v(field_data, all_data)
2248+
2249+class RequiredIfOtherFieldNotGiven(object):
2250+    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
2251+        self.other, self.error_message = other_field_name, error_message
2252+        self.always_test = True
2253+
2254+    def __call__(self, field_data, all_data):
2255+        if not all_data.get(self.other, False) and not field_data:
2256+            raise ValidationError, self.error_message
2257+
2258+class RequiredIfOtherFieldsGiven(object):
2259+    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
2260+        self.other, self.error_message = other_field_names, error_message
2261+        self.always_test = True
2262+
2263+    def __call__(self, field_data, all_data):
2264+        for field in self.other:
2265+            if all_data.get(field, False) and not field_data:
2266+                raise ValidationError, self.error_message
2267+
2268+class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
2269+    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
2270+    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
2271+        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
2272+
2273+class RequiredIfOtherFieldEquals(object):
2274+    def __init__(self, other_field, other_value, error_message=None, other_label=None):
2275+        self.other_field = other_field
2276+        self.other_value = other_value
2277+        other_label = other_label or other_value
2278+        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
2279+            'field': other_field, 'value': other_label})
2280+        self.always_test = True
2281+
2282+    def __call__(self, field_data, all_data):
2283+        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
2284+            raise ValidationError(self.error_message)
2285+
2286+class RequiredIfOtherFieldDoesNotEqual(object):
2287+    def __init__(self, other_field, other_value, other_label=None, error_message=None):
2288+        self.other_field = other_field
2289+        self.other_value = other_value
2290+        other_label = other_label or other_value
2291+        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
2292+            'field': other_field, 'value': other_label})
2293+        self.always_test = True
2294+
2295+    def __call__(self, field_data, all_data):
2296+        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
2297+            raise ValidationError(self.error_message)
2298+
2299+class IsLessThanOtherField(object):
2300+    def __init__(self, other_field_name, error_message):
2301+        self.other, self.error_message = other_field_name, error_message
2302+
2303+    def __call__(self, field_data, all_data):
2304+        if field_data > all_data[self.other]:
2305+            raise ValidationError, self.error_message
2306+
2307+class UniqueAmongstFieldsWithPrefix(object):
2308+    def __init__(self, field_name, prefix, error_message):
2309+        self.field_name, self.prefix = field_name, prefix
2310+        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
2311+
2312+    def __call__(self, field_data, all_data):
2313+        for field_name, value in all_data.items():
2314+            if field_name != self.field_name and value == field_data:
2315+                raise ValidationError, self.error_message
2316+
2317+class NumberIsInRange(object):
2318+    """
2319+    Validator that tests if a value is in a range (inclusive).
2320+    """
2321+    def __init__(self, lower=None, upper=None, error_message=''):
2322+        self.lower, self.upper = lower, upper
2323+        if not error_message:
2324+            if lower and upper:
2325+                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
2326+            elif lower:
2327+                self.error_message = _("This value must be at least %s.") % lower
2328+            elif upper:
2329+                self.error_message = _("This value must be no more than %s.") % upper
2330+        else:
2331+            self.error_message = error_message
2332+
2333+    def __call__(self, field_data, all_data):
2334+        # Try to make the value numeric. If this fails, we assume another
2335+        # validator will catch the problem.
2336+        try:
2337+            val = float(field_data)
2338+        except ValueError:
2339+            return
2340+
2341+        # Now validate
2342+        if self.lower and self.upper and (val < self.lower or val > self.upper):
2343+            raise ValidationError(self.error_message)
2344+        elif self.lower and val < self.lower:
2345+            raise ValidationError(self.error_message)
2346+        elif self.upper and val > self.upper:
2347+            raise ValidationError(self.error_message)
2348+
2349+class IsAPowerOf(object):
2350+    """
2351+    Usage: If you create an instance of the IsPowerOf validator:
2352+        v = IsAPowerOf(2)
2353+   
2354+    The following calls will succeed:
2355+        v(4, None)
2356+        v(8, None)
2357+        v(16, None)
2358+   
2359+    But this call:
2360+        v(17, None)
2361+    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
2362+    """
2363+    def __init__(self, power_of):
2364+        self.power_of = power_of
2365+
2366+    def __call__(self, field_data, all_data):
2367+        from math import log
2368+        val = log(int(field_data)) / log(self.power_of)
2369+        if val != int(val):
2370+            raise ValidationError, _("This value must be a power of %s.") % self.power_of
2371+
2372+class IsValidDecimal(object):
2373+    def __init__(self, max_digits, decimal_places):
2374+        self.max_digits, self.decimal_places = max_digits, decimal_places
2375+
2376+    def __call__(self, field_data, all_data):
2377+        try:
2378+            val = Decimal(field_data)
2379+        except DecimalException:
2380+            raise ValidationError, _("Please enter a valid decimal number.")
2381+
2382+        pieces = str(val).lstrip("-").split('.')
2383+        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
2384+        digits = len(pieces[0])
2385+
2386+        if digits + decimals > self.max_digits:
2387+            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
2388+                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
2389+        if digits > (self.max_digits - self.decimal_places):
2390+            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
2391+                "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)
2392+        if decimals > self.decimal_places:
2393+            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
2394+                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
2395+
2396+def isValidFloat(field_data, all_data):
2397+    data = smart_str(field_data)
2398+    try:
2399+        float(data)
2400+    except ValueError:
2401+        raise ValidationError, _("Please enter a valid floating point number.")
2402+
2403+class HasAllowableSize(object):
2404+    """
2405+    Checks that the file-upload field data is a certain size. min_size and
2406+    max_size are measurements in bytes.
2407+    """
2408+    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
2409+        self.min_size, self.max_size = min_size, max_size
2410+        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)
2411+        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)
2412+
2413+    def __call__(self, field_data, all_data):
2414+        try:
2415+            content = field_data['content']
2416+        except TypeError:
2417+            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
2418+        if self.min_size is not None and len(content) < self.min_size:
2419+            raise ValidationError, self.min_error_message
2420+        if self.max_size is not None and len(content) > self.max_size:
2421+            raise ValidationError, self.max_error_message
2422+
2423+class MatchesRegularExpression(object):
2424+    """
2425+    Checks that the field matches the given regular-expression. The regex
2426+    should be in string format, not already compiled.
2427+    """
2428+    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
2429+        self.regexp = re.compile(regexp)
2430+        self.error_message = error_message
2431+
2432+    def __call__(self, field_data, all_data):
2433+        if not self.regexp.search(field_data):
2434+            raise ValidationError(self.error_message)
2435+
2436+class AnyValidator(object):
2437+    """
2438+    This validator tries all given validators. If any one of them succeeds,
2439+    validation passes. If none of them succeeds, the given message is thrown
2440+    as a validation error. The message is rather unspecific, so it's best to
2441+    specify one on instantiation.
2442+    """
2443+    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
2444+        if validator_list is None: validator_list = []
2445+        self.validator_list = validator_list
2446+        self.error_message = error_message
2447+        for v in validator_list:
2448+            if hasattr(v, 'always_test'):
2449+                self.always_test = True
2450+
2451+    def __call__(self, field_data, all_data):
2452+        for v in self.validator_list:
2453+            try:
2454+                v(field_data, all_data)
2455+                return
2456+            except ValidationError, e:
2457+                pass
2458+        raise ValidationError(self.error_message)
2459+
2460+class URLMimeTypeCheck(object):
2461+    "Checks that the provided URL points to a document with a listed mime type"
2462+    class CouldNotRetrieve(ValidationError):
2463+        pass
2464+    class InvalidContentType(ValidationError):
2465+        pass
2466+
2467+    def __init__(self, mime_type_list):
2468+        self.mime_type_list = mime_type_list
2469+
2470+    def __call__(self, field_data, all_data):
2471+        import urllib2
2472+        try:
2473+            isValidURL(field_data, all_data)
2474+        except ValidationError:
2475+            raise
2476+        try:
2477+            info = urllib2.urlopen(field_data).info()
2478+        except (urllib2.HTTPError, urllib2.URLError):
2479+            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
2480+        content_type = info['content-type']
2481+        if content_type not in self.mime_type_list:
2482+            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
2483+                'url': field_data, 'contenttype': content_type}
2484+
2485+class RelaxNGCompact(object):
2486+    "Validate against a Relax NG compact schema"
2487+    def __init__(self, schema_path, additional_root_element=None):
2488+        self.schema_path = schema_path
2489+        self.additional_root_element = additional_root_element
2490+
2491+    def __call__(self, field_data, all_data):
2492+        import os, tempfile
2493+        if self.additional_root_element:
2494+            field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
2495+                'are': self.additional_root_element,
2496+                'data': field_data
2497+            }
2498+        filename = tempfile.mktemp() # Insecure, but nothing else worked
2499+        fp = open(filename, 'w')
2500+        fp.write(field_data)
2501+        fp.close()
2502+        if not os.path.exists(settings.JING_PATH):
2503+            raise Exception, "%s not found!" % settings.JING_PATH
2504+        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
2505+        errors = [line.strip() for line in p.readlines()]
2506+        p.close()
2507+        os.unlink(filename)
2508+        display_errors = []
2509+        lines = field_data.split('\n')
2510+        for error in errors:
2511+            ignored, line, level, message = error.split(':', 3)
2512+            # Scrape the Jing error messages to reword them more nicely.
2513+            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
2514+            if m:
2515+                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
2516+                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
2517+                continue
2518+            if message.strip() == 'text not allowed here':
2519+                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
2520+                    {'line':line, 'start':lines[int(line) - 1][:30]})
2521+                continue
2522+            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
2523+            if m:
2524+                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
2525+                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
2526+                continue
2527+            m = re.search(r'\s*unknown element "(.*?)"', message)
2528+            if m:
2529+                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
2530+                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
2531+                continue
2532+            if message.strip() == 'required attributes missing':
2533+                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
2534+                    {'line':line, 'start':lines[int(line) - 1][:30]})
2535+                continue
2536+            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
2537+            if m:
2538+                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
2539+                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
2540+                continue
2541+            # Failing all those checks, use the default error message.
2542+            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
2543+            display_errors.append(display_error)
2544+        if len(display_errors) > 0:
2545+            raise ValidationError, display_errors
2546diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
2547index 3e52e33..64cfbd3 100644
2548--- a/tests/modeltests/manipulators/models.py
2549+++ b/tests/modeltests/manipulators/models.py
2550@@ -99,7 +99,7 @@ True
2551 datetime.date(2005, 2, 13)
2552 
2553 # Test isValidFloat Unicode coercion
2554->>> from django.core.validators import isValidFloat, ValidationError
2555+>>> from django.oldforms.validators import isValidFloat, ValidationError
2556 >>> try: isValidFloat(u"ä", None)
2557 ... except ValidationError: pass
2558 """}
2559diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
2560index 470312f..58ca0cf 100644
2561--- a/tests/modeltests/model_forms/models.py
2562+++ b/tests/modeltests/model_forms/models.py
2563@@ -478,6 +478,8 @@ Create a new article, with categories, via the form.
2564 ...         model = Article
2565 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
2566 ...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
2567+>>> f.is_valid()
2568+True
2569 >>> new_art = f.save()
2570 >>> new_art.id
2571 2
2572@@ -705,7 +707,7 @@ ValidationError: [u'Select a valid choice. 100 is not one of the available choic
2573 >>> f.clean('hello')
2574 Traceback (most recent call last):
2575 ...
2576-ValidationError: [u'Enter a list of values.']
2577+TypeCoercionError: [u'Enter a list of values.']
2578 
2579 # Add a Category object *after* the ModelMultipleChoiceField has already been
2580 # instantiated. This proves clean() checks the database during clean() rather
2581@@ -829,14 +831,13 @@ u'...test2.txt'
2582 >>> instance.delete()
2583 
2584 # Test the non-required FileField
2585-
2586+# It should fail since the field IS required on the model
2587 >>> f = TextFileForm(data={'description': u'Assistance'})
2588 >>> f.fields['file'].required = False
2589 >>> f.is_valid()
2590-True
2591->>> instance = f.save()
2592->>> instance.file
2593-''
2594+False
2595+>>> f.errors
2596+{'file': [u'This field is required.']}
2597 
2598 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
2599 >>> f.is_valid()
2600@@ -899,10 +900,10 @@ u'...test2.png'
2601 >>> f = ImageFileForm(data={'description': u'Test'})
2602 >>> f.fields['image'].required = False
2603 >>> f.is_valid()
2604-True
2605->>> instance = f.save()
2606->>> instance.image
2607-''
2608+False
2609+>>> f.errors
2610+{'image': [u'This field is required.']}
2611+
2612 
2613 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
2614 >>> f.is_valid()
2615diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
2616index 63f9f7a..379647a 100644
2617--- a/tests/modeltests/validation/models.py
2618+++ b/tests/modeltests/validation/models.py
2619@@ -3,7 +3,7 @@
2620 
2621 This is an experimental feature!
2622 
2623-Each model instance has a validate() method that returns a dictionary of
2624+Each model instance has a clean() method that returns a dictionary of
2625 validation errors in the instance's fields. This method has a side effect
2626 of converting each field to its appropriate Python data type.
2627 """
2628@@ -15,8 +15,10 @@ class Person(models.Model):
2629     name = models.CharField(max_length=20)
2630     birthdate = models.DateField()
2631     favorite_moment = models.DateTimeField()
2632-    email = models.EmailField()
2633-
2634+    email = models.EmailField(unique=True)
2635+   
2636+    class Meta:
2637+        unique_together = (('name', 'is_child'),)
2638     def __unicode__(self):
2639         return self.name
2640 
2641@@ -30,128 +32,132 @@ __test__ = {'API_TESTS':"""
2642 ...     'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
2643 ...     'email': 'john@example.com'
2644 ... }
2645->>> p = Person(**valid_params)
2646->>> p.validate()
2647-{}
2648-
2649->>> p = Person(**dict(valid_params, id='23'))
2650->>> p.validate()
2651-{}
2652+>>> p = Person(**dict(valid_params, email='john@e.com', name='Jack'))
2653+>>> p.clean()
2654+>>> p.save()
2655+
2656+>>> p = Person(**dict(valid_params, email='john@e.com'))
2657+>>> p.clean()
2658+Traceback (most recent call last):
2659+...
2660+ValidationError: {'email': [u'This field must be unique']}
2661+
2662+>>> p = Person(**dict(valid_params, id='23', name='Jack'))
2663+>>> p.clean()
2664+Traceback (most recent call last):
2665+...
2666+ValidationError: {'__all__': u'Fields name, is_child must be unique.'}
2667 >>> p.id
2668 23
2669 
2670->>> p = Person(**dict(valid_params, id='foo'))
2671->>> p.validate()['id']
2672-[u'This value must be an integer.']
2673+# when type coercion fails, no other validation is done
2674+>>> p = Person(**dict(valid_params, email='john@e.com', id='foo'))
2675+>>> p.clean()
2676+Traceback (most recent call last):
2677+...
2678+ValidationError: {'id': [u'This value must be an integer.']}
2679 
2680 >>> p = Person(**dict(valid_params, id=None))
2681->>> p.validate()
2682-{}
2683+>>> p.clean()
2684 >>> repr(p.id)
2685 'None'
2686 
2687 >>> p = Person(**dict(valid_params, is_child='t'))
2688->>> p.validate()
2689-{}
2690+>>> p.clean()
2691 >>> p.is_child
2692 True
2693 
2694 >>> p = Person(**dict(valid_params, is_child='f'))
2695->>> p.validate()
2696-{}
2697+>>> p.clean()
2698 >>> p.is_child
2699 False
2700 
2701 >>> p = Person(**dict(valid_params, is_child=True))
2702->>> p.validate()
2703-{}
2704+>>> p.clean()
2705 >>> p.is_child
2706 True
2707 
2708 >>> p = Person(**dict(valid_params, is_child=False))
2709->>> p.validate()
2710-{}
2711+>>> p.clean()
2712 >>> p.is_child
2713 False
2714 
2715 >>> p = Person(**dict(valid_params, is_child='foo'))
2716->>> p.validate()['is_child']
2717-[u'This value must be either True or False.']
2718+>>> p.clean()
2719+Traceback (most recent call last):
2720+...
2721+ValidationError: {'is_child': [u'This value must be either True or False.']}
2722 
2723 >>> p = Person(**dict(valid_params, name=u'Jose'))
2724->>> p.validate()
2725-{}
2726+>>> p.clean()
2727 >>> p.name
2728 u'Jose'
2729 
2730 >>> p = Person(**dict(valid_params, name=227))
2731->>> p.validate()
2732-{}
2733+>>> p.clean()
2734 >>> p.name
2735 u'227'
2736 
2737 >>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
2738->>> p.validate()
2739-{}
2740+>>> p.clean()
2741 >>> p.birthdate
2742 datetime.date(2000, 5, 3)
2743 
2744 >>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
2745->>> p.validate()
2746-{}
2747+>>> p.clean()
2748 >>> p.birthdate
2749 datetime.date(2000, 5, 3)
2750 
2751 >>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
2752->>> p.validate()
2753-{}
2754+>>> p.clean()
2755 >>> p.birthdate
2756 datetime.date(2000, 5, 3)
2757 
2758 >>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
2759->>> p.validate()
2760-{}
2761+>>> p.clean()
2762 >>> p.birthdate
2763 datetime.date(2000, 5, 3)
2764 
2765 >>> p = Person(**dict(valid_params, birthdate='foo'))
2766->>> p.validate()['birthdate']
2767-[u'Enter a valid date in YYYY-MM-DD format.']
2768+>>> p.clean()
2769+Traceback (most recent call last):
2770+...
2771+ValidationError: {'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
2772 
2773 >>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
2774->>> p.validate()
2775-{}
2776+>>> p.clean()
2777 >>> p.favorite_moment
2778 datetime.datetime(2002, 4, 3, 13, 23)
2779 
2780 >>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
2781->>> p.validate()
2782-{}
2783+>>> p.clean()
2784 >>> p.favorite_moment
2785 datetime.datetime(2002, 4, 3, 0, 0)
2786 
2787 >>> p = Person(**dict(valid_params, email='john@example.com'))
2788->>> p.validate()
2789-{}
2790+>>> p.clean()
2791 >>> p.email
2792 'john@example.com'
2793 
2794 >>> p = Person(**dict(valid_params, email=u'john@example.com'))
2795->>> p.validate()
2796-{}
2797+>>> p.clean()
2798 >>> p.email
2799 u'john@example.com'
2800 
2801 >>> p = Person(**dict(valid_params, email=22))
2802->>> p.validate()['email']
2803-[u'Enter a valid e-mail address.']
2804+>>> p.clean()
2805+Traceback (most recent call last):
2806+...
2807+ValidationError: {'email': [u'Enter a valid e-mail address.']}
2808 
2809 # Make sure that Date and DateTime return validation errors and don't raise Python errors.
2810->>> p = Person(name='John Doe', is_child=True, email='abc@def.com')
2811->>> errors = p.validate()
2812->>> errors['favorite_moment']
2813+>>> from django.core.validation import ValidationError
2814+>>> try:
2815+...     Person(name='John Doe', is_child=True, email='abc@def.com').clean()
2816+... except ValidationError, e:
2817+...     e.message_dict['favorite_moment']
2818+...     e.message_dict['birthdate']
2819 [u'This field is required.']
2820->>> errors['birthdate']
2821 [u'This field is required.']
2822 
2823 """}
2824diff --git a/tests/regressiontests/core/__init__.py b/tests/regressiontests/core/__init__.py
2825new file mode 100644
2826index 0000000..e69de29
2827diff --git a/tests/regressiontests/core/models.py b/tests/regressiontests/core/models.py
2828new file mode 100644
2829index 0000000..e5a7950
2830--- /dev/null
2831+++ b/tests/regressiontests/core/models.py
2832@@ -0,0 +1,2 @@
2833+# A models.py so that tests run.
2834+
2835diff --git a/tests/regressiontests/core/tests.py b/tests/regressiontests/core/tests.py
2836new file mode 100644
2837index 0000000..adc72df
2838--- /dev/null
2839+++ b/tests/regressiontests/core/tests.py
2840@@ -0,0 +1,35 @@
2841+tests = r"""
2842+###################
2843+# ValidationError #
2844+###################
2845+>>> from django.core.exceptions import ValidationError
2846+>>> from django.utils.translation import ugettext_lazy
2847+
2848+# Can take a string.
2849+>>> print ValidationError("There was an error.").messages
2850+<ul class="errorlist"><li>There was an error.</li></ul>
2851+
2852+# Can take a unicode string.
2853+>>> print ValidationError(u"Not \u03C0.").messages
2854+<ul class="errorlist"><li>Not π.</li></ul>
2855+
2856+# Can take a lazy string.
2857+>>> print ValidationError(ugettext_lazy("Error.")).messages
2858+<ul class="errorlist"><li>Error.</li></ul>
2859+
2860+# Can take a list.
2861+>>> print ValidationError(["Error one.", "Error two."]).messages
2862+<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
2863+
2864+# Can take a mixture in a list.
2865+>>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
2866+<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
2867+
2868+>>> class VeryBadError:
2869+...     def __unicode__(self): return u"A very bad error."
2870+
2871+# Can take a non-string.
2872+>>> print ValidationError(VeryBadError()).messages
2873+<ul class="errorlist"><li>A very bad error.</li></ul>
2874+"""
2875+
2876diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
2877index 9f972f5..44f5a25 100644
2878--- a/tests/regressiontests/forms/error_messages.py
2879+++ b/tests/regressiontests/forms/error_messages.py
2880@@ -35,7 +35,7 @@ ValidationError: [u'REQUIRED']
2881 >>> f.clean('abc')
2882 Traceback (most recent call last):
2883 ...
2884-ValidationError: [u'INVALID']
2885+TypeCoercionError: [u'INVALID']
2886 >>> f.clean('4')
2887 Traceback (most recent call last):
2888 ...
2889@@ -59,7 +59,7 @@ ValidationError: [u'REQUIRED']
2890 >>> f.clean('abc')
2891 Traceback (most recent call last):
2892 ...
2893-ValidationError: [u'INVALID']
2894+TypeCoercionError: [u'INVALID']
2895 >>> f.clean('4')
2896 Traceback (most recent call last):
2897 ...
2898@@ -87,7 +87,7 @@ ValidationError: [u'REQUIRED']
2899 >>> f.clean('abc')
2900 Traceback (most recent call last):
2901 ...
2902-ValidationError: [u'INVALID']
2903+TypeCoercionError: [u'INVALID']
2904 >>> f.clean('4')
2905 Traceback (most recent call last):
2906 ...
2907@@ -121,7 +121,7 @@ ValidationError: [u'REQUIRED']
2908 >>> f.clean('abc')
2909 Traceback (most recent call last):
2910 ...
2911-ValidationError: [u'INVALID']
2912+TypeCoercionError: [u'INVALID']
2913 
2914 # TimeField ###################################################################
2915 
2916@@ -135,7 +135,7 @@ ValidationError: [u'REQUIRED']
2917 >>> f.clean('abc')
2918 Traceback (most recent call last):
2919 ...
2920-ValidationError: [u'INVALID']
2921+TypeCoercionError: [u'INVALID']
2922 
2923 # DateTimeField ###############################################################
2924 
2925@@ -149,7 +149,7 @@ ValidationError: [u'REQUIRED']
2926 >>> f.clean('abc')
2927 Traceback (most recent call last):
2928 ...
2929-ValidationError: [u'INVALID']
2930+TypeCoercionError: [u'INVALID']
2931 
2932 # RegexField ##################################################################
2933 
2934@@ -203,7 +203,6 @@ ValidationError: [u'LENGTH 11, MAX LENGTH 10']
2935 
2936 >>> e = {'required': 'REQUIRED'}
2937 >>> e['invalid'] = 'INVALID'
2938->>> e['missing'] = 'MISSING'
2939 >>> e['empty'] = 'EMPTY FILE'
2940 >>> f = FileField(error_messages=e)
2941 >>> f.clean('')
2942@@ -213,11 +212,11 @@ ValidationError: [u'REQUIRED']
2943 >>> f.clean('abc')
2944 Traceback (most recent call last):
2945 ...
2946-ValidationError: [u'INVALID']
2947+TypeCoercionError: [u'INVALID']
2948 >>> f.clean({})
2949 Traceback (most recent call last):
2950 ...
2951-ValidationError: [u'MISSING']
2952+ValidationError: [u'REQUIRED']
2953 >>> f.clean({'filename': 'name', 'content':''})
2954 Traceback (most recent call last):
2955 ...
2956@@ -278,7 +277,7 @@ ValidationError: [u'REQUIRED']
2957 >>> f.clean('b')
2958 Traceback (most recent call last):
2959 ...
2960-ValidationError: [u'NOT A LIST']
2961+TypeCoercionError: [u'NOT A LIST']
2962 >>> f.clean(['b'])
2963 Traceback (most recent call last):
2964 ...
2965@@ -352,7 +351,7 @@ ValidationError: [u'REQUIRED']
2966 >>> f.clean('3')
2967 Traceback (most recent call last):
2968 ...
2969-ValidationError: [u'NOT A LIST OF VALUES']
2970+TypeCoercionError: [u'NOT A LIST OF VALUES']
2971 >>> f.clean(['4'])
2972 Traceback (most recent call last):
2973 ...
2974diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py
2975index a8b3697..7e381db 100644
2976--- a/tests/regressiontests/forms/extra.py
2977+++ b/tests/regressiontests/forms/extra.py
2978@@ -424,7 +424,7 @@ u'sirrobin'
2979 # Test overriding ErrorList in a form #
2980 #######################################
2981 
2982->>> from django.newforms.util import ErrorList
2983+>>> from django.core.validation import ErrorList
2984 >>> class DivErrorList(ErrorList):
2985 ...     def __unicode__(self):
2986 ...         return self.as_divs()
2987diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
2988index f3b6a96..2fb77e7 100644
2989--- a/tests/regressiontests/forms/fields.py
2990+++ b/tests/regressiontests/forms/fields.py
2991@@ -31,6 +31,7 @@ Each Field's __init__() takes at least these parameters:
2992              field name, if the Field is part of a Form.
2993     initial -- A value to use in this Field's initial display. This value is
2994                *not* used as a fallback if data isn't given.
2995+    validators -- Optional list of additional validator functions
2996 
2997 Other than that, the Field subclasses have class-specific options for
2998 __init__(). For example, CharField has a max_length option.
2999@@ -103,6 +104,30 @@ u'1234567890'
3000 >>> f.clean('1234567890a')
3001 u'1234567890a'
3002 
3003+# Custom validator functions ##################################################
3004+
3005+>>> def validator(value): raise ValidationError('validator failed')
3006+>>> f = CharField(min_length=10, validators=[validator])
3007+>>> f.clean('aa')
3008+Traceback (most recent call last):
3009+...
3010+ValidationError: [u'validator failed']
3011+
3012+>>> def validator2(value): raise ValidationError('validator2 failed')
3013+>>> f = CharField(min_length=10, validators=[validator, validator, validator2])
3014+>>> f.clean('aa')
3015+Traceback (most recent call last):
3016+...
3017+ValidationError: [u'validator failed', u'validator failed', u'validator2 failed']
3018+
3019+>>> class MyCharField(CharField):
3020+...     validators = [validator]
3021+>>> f = MyCharField()
3022+>>> f.clean('aa')
3023+Traceback (most recent call last):
3024+...
3025+ValidationError: [u'validator failed']
3026+
3027 # IntegerField ################################################################
3028 
3029 >>> f = IntegerField()
3030@@ -123,13 +148,13 @@ True
3031 >>> f.clean('a')
3032 Traceback (most recent call last):
3033 ...
3034-ValidationError: [u'Enter a whole number.']
3035+TypeCoercionError: [u'Enter a whole number.']
3036 >>> f.clean(42)
3037 42
3038 >>> f.clean(3.14)
3039 Traceback (most recent call last):
3040 ...
3041-ValidationError: [u'Enter a whole number.']
3042+TypeCoercionError: [u'Enter a whole number.']
3043 >>> f.clean('1 ')
3044 1
3045 >>> f.clean(' 1')
3046@@ -139,7 +164,7 @@ ValidationError: [u'Enter a whole number.']
3047 >>> f.clean('1a')
3048 Traceback (most recent call last):
3049 ...
3050-ValidationError: [u'Enter a whole number.']
3051+TypeCoercionError: [u'Enter a whole number.']
3052 
3053 >>> f = IntegerField(required=False)
3054 >>> f.clean('')
3055@@ -157,7 +182,7 @@ True
3056 >>> f.clean('a')
3057 Traceback (most recent call last):
3058 ...
3059-ValidationError: [u'Enter a whole number.']
3060+TypeCoercionError: [u'Enter a whole number.']
3061 >>> f.clean('1 ')
3062 1
3063 >>> f.clean(' 1')
3064@@ -167,7 +192,7 @@ ValidationError: [u'Enter a whole number.']
3065 >>> f.clean('1a')
3066 Traceback (most recent call last):
3067 ...
3068-ValidationError: [u'Enter a whole number.']
3069+TypeCoercionError: [u'Enter a whole number.']
3070 
3071 IntegerField accepts an optional max_value parameter:
3072 >>> f = IntegerField(max_value=10)
3073@@ -260,7 +285,7 @@ True
3074 >>> f.clean('a')
3075 Traceback (most recent call last):
3076 ...
3077-ValidationError: [u'Enter a number.']
3078+TypeCoercionError: [u'Enter a number.']
3079 >>> f.clean('1.0 ')
3080 1.0
3081 >>> f.clean(' 1.0')
3082@@ -270,7 +295,7 @@ ValidationError: [u'Enter a number.']
3083 >>> f.clean('1.0a')
3084 Traceback (most recent call last):
3085 ...
3086-ValidationError: [u'Enter a number.']
3087+TypeCoercionError: [u'Enter a number.']
3088 
3089 >>> f = FloatField(required=False)
3090 >>> f.clean('')
3091@@ -322,11 +347,11 @@ Decimal("3.14")
3092 >>> f.clean('a')
3093 Traceback (most recent call last):
3094 ...
3095-ValidationError: [u'Enter a number.']
3096+TypeCoercionError: [u'Enter a number.']
3097 >>> f.clean(u'łąść')
3098 Traceback (most recent call last):
3099 ...
3100-ValidationError: [u'Enter a number.']
3101+TypeCoercionError: [u'Enter a number.']
3102 >>> f.clean('1.0 ')
3103 Decimal("1.0")
3104 >>> f.clean(' 1.0')
3105@@ -336,7 +361,7 @@ Decimal("1.0")
3106 >>> f.clean('1.0a')
3107 Traceback (most recent call last):
3108 ...
3109-ValidationError: [u'Enter a number.']
3110+TypeCoercionError: [u'Enter a number.']
3111 >>> f.clean('123.45')
3112 Traceback (most recent call last):
3113 ...
3114@@ -372,7 +397,7 @@ ValidationError: [u'Ensure that there are no more than 4 digits in total.']
3115 >>> f.clean('--0.12')
3116 Traceback (most recent call last):
3117 ...
3118-ValidationError: [u'Enter a number.']
3119+TypeCoercionError: [u'Enter a number.']
3120 
3121 >>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
3122 >>> f.clean('')
3123@@ -433,15 +458,15 @@ datetime.date(2006, 10, 25)
3124 >>> f.clean('2006-4-31')
3125 Traceback (most recent call last):
3126 ...
3127-ValidationError: [u'Enter a valid date.']
3128+TypeCoercionError: [u'Enter a valid date.']
3129 >>> f.clean('200a-10-25')
3130 Traceback (most recent call last):
3131 ...
3132-ValidationError: [u'Enter a valid date.']
3133+TypeCoercionError: [u'Enter a valid date.']
3134 >>> f.clean('25/10/06')
3135 Traceback (most recent call last):
3136 ...
3137-ValidationError: [u'Enter a valid date.']
3138+TypeCoercionError: [u'Enter a valid date.']
3139 >>> f.clean(None)
3140 Traceback (most recent call last):
3141 ...
3142@@ -469,15 +494,15 @@ so the default formats won't work unless you specify them:
3143 >>> f.clean('2006-10-25')
3144 Traceback (most recent call last):
3145 ...
3146-ValidationError: [u'Enter a valid date.']
3147+TypeCoercionError: [u'Enter a valid date.']
3148 >>> f.clean('10/25/2006')
3149 Traceback (most recent call last):
3150 ...
3151-ValidationError: [u'Enter a valid date.']
3152+TypeCoercionError: [u'Enter a valid date.']
3153 >>> f.clean('10/25/06')
3154 Traceback (most recent call last):
3155 ...
3156-ValidationError: [u'Enter a valid date.']
3157+TypeCoercionError: [u'Enter a valid date.']
3158 
3159 # TimeField ###################################################################
3160 
3161@@ -494,11 +519,11 @@ datetime.time(14, 25, 59)
3162 >>> f.clean('hello')
3163 Traceback (most recent call last):
3164 ...
3165-ValidationError: [u'Enter a valid time.']
3166+TypeCoercionError: [u'Enter a valid time.']
3167 >>> f.clean('1:24 p.m.')
3168 Traceback (most recent call last):
3169 ...
3170-ValidationError: [u'Enter a valid time.']
3171+TypeCoercionError: [u'Enter a valid time.']
3172 
3173 TimeField accepts an optional input_formats parameter:
3174 >>> f = TimeField(input_formats=['%I:%M %p'])
3175@@ -516,7 +541,7 @@ so the default formats won't work unless you specify them:
3176 >>> f.clean('14:30:45')
3177 Traceback (most recent call last):
3178 ...
3179-ValidationError: [u'Enter a valid time.']
3180+TypeCoercionError: [u'Enter a valid time.']
3181 
3182 # DateTimeField ###############################################################
3183 
3184@@ -557,11 +582,11 @@ datetime.datetime(2006, 10, 25, 0, 0)
3185 >>> f.clean('hello')
3186 Traceback (most recent call last):
3187 ...
3188-ValidationError: [u'Enter a valid date/time.']
3189+TypeCoercionError: [u'Enter a valid date/time.']
3190 >>> f.clean('2006-10-25 4:30 p.m.')
3191 Traceback (most recent call last):
3192 ...
3193-ValidationError: [u'Enter a valid date/time.']
3194+TypeCoercionError: [u'Enter a valid date/time.']
3195 
3196 DateField accepts an optional input_formats parameter:
3197 >>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
3198@@ -581,7 +606,7 @@ so the default formats won't work unless you specify them:
3199 >>> f.clean('2006-10-25 14:30:45')
3200 Traceback (most recent call last):
3201 ...
3202-ValidationError: [u'Enter a valid date/time.']
3203+TypeCoercionError: [u'Enter a valid date/time.']
3204 
3205 >>> f = DateTimeField(required=False)
3206 >>> f.clean(None)
3207@@ -773,12 +798,12 @@ ValidationError: [u'This field is required.']
3208 >>> f.clean({})
3209 Traceback (most recent call last):
3210 ...
3211-ValidationError: [u'No file was submitted.']
3212+ValidationError: [u'This field is required.']
3213 
3214 >>> f.clean({}, '')
3215 Traceback (most recent call last):
3216 ...
3217-ValidationError: [u'No file was submitted.']
3218+ValidationError: [u'This field is required.']
3219 
3220 >>> f.clean({}, 'files/test3.pdf')
3221 'files/test3.pdf'
3222@@ -786,7 +811,7 @@ ValidationError: [u'No file was submitted.']
3223 >>> f.clean('some content that is not a file')
3224 Traceback (most recent call last):
3225 ...
3226-ValidationError: [u'No file was submitted. Check the encoding type on the form.']
3227+TypeCoercionError: [u'No file was submitted. Check the encoding type on the form.']
3228 
3229 >>> f.clean({'filename': 'name', 'content': None})
3230 Traceback (most recent call last):
3231@@ -993,9 +1018,7 @@ ValidationError: [u'Select a valid choice. That choice is not one of the availab
3232 
3233 >>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
3234 >>> f.clean('')
3235-u''
3236 >>> f.clean(None)
3237-u''
3238 >>> f.clean(1)
3239 u'1'
3240 >>> f.clean('1')
3241@@ -1051,7 +1074,7 @@ ValidationError: [u'This field is required.']
3242 >>> f.clean('hello')
3243 Traceback (most recent call last):
3244 ...
3245-ValidationError: [u'Enter a list of values.']
3246+TypeCoercionError: [u'Enter a list of values.']
3247 >>> f.clean([])
3248 Traceback (most recent call last):
3249 ...
3250@@ -1083,7 +1106,7 @@ ValidationError: [u'Select a valid choice. 3 is not one of the available choices
3251 >>> f.clean('hello')
3252 Traceback (most recent call last):
3253 ...
3254-ValidationError: [u'Enter a list of values.']
3255+TypeCoercionError: [u'Enter a list of values.']
3256 >>> f.clean([])
3257 []
3258 >>> f.clean(())
3259@@ -1186,7 +1209,7 @@ ValidationError: [u'This field is required.']
3260 >>> f.clean('hello')
3261 Traceback (most recent call last):
3262 ...
3263-ValidationError: [u'Enter a list of values.']
3264+TypeCoercionError: [u'Enter a list of values.']
3265 >>> f.clean(['hello', 'there'])
3266 Traceback (most recent call last):
3267 ...
3268@@ -1212,7 +1235,7 @@ datetime.datetime(2006, 1, 10, 7, 30)
3269 >>> f.clean('hello')
3270 Traceback (most recent call last):
3271 ...
3272-ValidationError: [u'Enter a list of values.']
3273+TypeCoercionError: [u'Enter a list of values.']
3274 >>> f.clean(['hello', 'there'])
3275 Traceback (most recent call last):
3276 ...
3277diff --git a/tests/regressiontests/forms/localflavor/br.py b/tests/regressiontests/forms/localflavor/br.py
3278index 757f382..94285c3 100644
3279--- a/tests/regressiontests/forms/localflavor/br.py
3280+++ b/tests/regressiontests/forms/localflavor/br.py
3281@@ -85,11 +85,11 @@ Traceback (most recent call last):
3282 ...
3283 ValidationError: [u'Invalid CNPJ number.']
3284 >>> f.clean('64.132.916/0001-88')
3285-'64.132.916/0001-88'
3286+u'64.132.916/0001-88'
3287 >>> f.clean('64-132-916/0001-88')
3288-'64-132-916/0001-88'
3289+u'64-132-916/0001-88'
3290 >>> f.clean('64132916/0001-88')
3291-'64132916/0001-88'
3292+u'64132916/0001-88'
3293 >>> f.clean('64.132.916/0001-XX')
3294 Traceback (most recent call last):
3295 ...
3296diff --git a/tests/regressiontests/forms/localflavor/generic.py b/tests/regressiontests/forms/localflavor/generic.py
3297index 0dbe30d..cda4f89 100644
3298--- a/tests/regressiontests/forms/localflavor/generic.py
3299+++ b/tests/regressiontests/forms/localflavor/generic.py
3300@@ -38,15 +38,15 @@ datetime.date(2006, 10, 25)
3301 >>> f.clean('2006-4-31')
3302 Traceback (most recent call last):
3303 ...
3304-ValidationError: [u'Enter a valid date.']
3305+TypeCoercionError: [u'Enter a valid date.']
3306 >>> f.clean('200a-10-25')
3307 Traceback (most recent call last):
3308 ...
3309-ValidationError: [u'Enter a valid date.']
3310+TypeCoercionError: [u'Enter a valid date.']
3311 >>> f.clean('10/25/06')
3312 Traceback (most recent call last):
3313 ...
3314-ValidationError: [u'Enter a valid date.']
3315+TypeCoercionError: [u'Enter a valid date.']
3316 >>> f.clean(None)
3317 Traceback (most recent call last):
3318 ...
3319@@ -74,15 +74,15 @@ so the default formats won't work unless you specify them:
3320 >>> f.clean('2006-10-25')
3321 Traceback (most recent call last):
3322 ...
3323-ValidationError: [u'Enter a valid date.']
3324+TypeCoercionError: [u'Enter a valid date.']
3325 >>> f.clean('25/10/2006')
3326 Traceback (most recent call last):
3327 ...
3328-ValidationError: [u'Enter a valid date.']
3329+TypeCoercionError: [u'Enter a valid date.']
3330 >>> f.clean('25/10/06')
3331 Traceback (most recent call last):
3332 ...
3333-ValidationError: [u'Enter a valid date.']
3334+TypeCoercionError: [u'Enter a valid date.']
3335 
3336 ## Generic DateTimeField ######################################################
3337 
3338@@ -126,11 +126,11 @@ datetime.datetime(2006, 10, 25, 0, 0)
3339 >>> f.clean('hello')
3340 Traceback (most recent call last):
3341 ...
3342-ValidationError: [u'Enter a valid date/time.']
3343+TypeCoercionError: [u'Enter a valid date/time.']
3344 >>> f.clean('2006-10-25 4:30 p.m.')
3345 Traceback (most recent call last):
3346 ...
3347-ValidationError: [u'Enter a valid date/time.']
3348+TypeCoercionError: [u'Enter a valid date/time.']
3349 
3350 DateField accepts an optional input_formats parameter:
3351 >>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
3352@@ -150,7 +150,7 @@ so the default formats won't work unless you specify them:
3353 >>> f.clean('2006-10-25 14:30:45')
3354 Traceback (most recent call last):
3355 ...
3356-ValidationError: [u'Enter a valid date/time.']
3357+TypeCoercionError: [u'Enter a valid date/time.']
3358 
3359 >>> f = DateTimeField(required=False)
3360 >>> f.clean(None)
3361diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
3362index bfaf73f..9933968 100644
3363--- a/tests/regressiontests/forms/util.py
3364+++ b/tests/regressiontests/forms/util.py
3365@@ -5,7 +5,6 @@ Tests for newforms/util.py module.
3366 
3367 tests = r"""
3368 >>> from django.newforms.util import *
3369->>> from django.utils.translation import ugettext_lazy
3370 
3371 ###########
3372 # flatatt #
3373@@ -18,35 +17,4 @@ u' id="header"'
3374 u' class="news" title="Read this"'
3375 >>> flatatt({})
3376 u''
3377-
3378-###################
3379-# ValidationError #
3380-###################
3381-
3382-# Can take a string.
3383->>> print ValidationError("There was an error.").messages
3384-<ul class="errorlist"><li>There was an error.</li></ul>
3385-
3386-# Can take a unicode string.
3387->>> print ValidationError(u"Not \u03C0.").messages
3388-<ul class="errorlist"><li>Not π.</li></ul>
3389-
3390-# Can take a lazy string.
3391->>> print ValidationError(ugettext_lazy("Error.")).messages
3392-<ul class="errorlist"><li>Error.</li></ul>
3393-
3394-# Can take a list.
3395->>> print ValidationError(["Error one.", "Error two."]).messages
3396-<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
3397-
3398-# Can take a mixture in a list.
3399->>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
3400-<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
3401-
3402->>> class VeryBadError:
3403-...     def __unicode__(self): return u"A very bad error."
3404-
3405-# Can take a non-string.
3406->>> print ValidationError(VeryBadError()).messages
3407-<ul class="errorlist"><li>A very bad error.</li></ul>
3408 """