Ticket #6845: 6845-against-7877.patch

File 6845-against-7877.patch, 142.6 KB (added by Honza_Kral, 7 years ago)
  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 7e91eda..62ea249 100644
    a b answer newbie questions, and generally made Django that much better: 
    217217    Igor Kolar <ike@email.si>
    218218    Gasper Koren
    219219    Martin Kosír <martin@martinkosir.net>
     220    Honza Kral <Honza.Kral@gmail.com>
    220221    Meir Kriheli <http://mksoft.co.il/>
    221222    Bruce Kroeze <http://coderseye.com/>
    222223    krzysiek.pawlik@silvermedia.pl
  • django/contrib/admin/views/template.py

    diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py
    index a3b4538..89e6952 100644
    a b  
    11from django.contrib.admin.views.decorators import staff_member_required
    2 from django.core import validators
     2from django.core import validation
    33from django import template, oldforms
    44from django.template import loader
    55from django.shortcuts import render_to_response
    class TemplateValidator(oldforms.Manipulator): 
    6969            error = e
    7070        template.builtins.remove(register)
    7171        if error:
    72             raise validators.ValidationError, e.args
     72            raise validation.ValidationError, e.args
  • django/contrib/auth/forms.py

    diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
    index 47a974c..b1af278 100644
    a b from django.contrib.auth.models import User 
    22from django.contrib.auth import authenticate
    33from django.contrib.sites.models import Site
    44from django.template import Context, loader
    5 from django.core import validators
     5from django.oldforms import validators
    66from django import oldforms
    77from django.utils.translation import ugettext as _
    88
  • django/contrib/auth/management/commands/createsuperuser.py

    diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
    index 4299762..1e8a775 100644
    a b class Command(BaseCommand): 
    3939            if not RE_VALID_USERNAME.match(username):
    4040                raise CommandError("Invalid username. Use only letters, digits, and underscores")
    4141            try:
    42                 validators.isValidEmail(email, None)
     42                validators.validate_email(email)
    4343            except validators.ValidationError:
    4444                raise CommandError("Invalid email address.")
    4545
  • django/contrib/auth/models.py

    diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
    index 379a9f4..818e33a 100644
    a b class User(models.Model): 
    135135
    136136    Username and password are required. Other fields are optional.
    137137    """
    138     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)."))
     138    username = models.CharField(_('username'), max_length=30, unique=True, validator_list=[validators.validate_alpha_numeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
    139139    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    140140    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    141141    email = models.EmailField(_('e-mail address'), blank=True)
  • django/contrib/comments/views/comments.py

    diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
    index 67da575..9c743bf 100644
    a b  
    1 from django.core import validators
     1from django.oldforms import validators
    22from django import oldforms
    33from django.core.mail import mail_admins, mail_managers
    44from django.http import Http404
  • django/contrib/flatpages/models.py

    diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
    index d61e9a3..e96c913 100644
    a b from django.utils.translation import ugettext_lazy as _ 
    55
    66
    77class FlatPage(models.Model):
    8     url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
     8    url = models.CharField(_('URL'), max_length=100, validator_list=[validators.validate_alpha_numeric], db_index=True,
    99        help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
    1010    title = models.CharField(_('title'), max_length=200)
    1111    content = models.TextField(_('content'), blank=True)
  • django/contrib/localflavor/br/forms.py

    diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
    index 81dcb82..f1162b7 100644
    a b class BRPhoneNumberField(Field): 
    3131    }
    3232
    3333    def clean(self, value):
    34         super(BRPhoneNumberField, self).clean(value)
     34        value = super(BRPhoneNumberField, self).clean(value)
    3535        if value in EMPTY_VALUES:
    3636            return u''
    37         value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
     37        value = re.sub('(\(|\)|\s+)', '', value)
    3838        m = phone_digits_re.search(value)
    3939        if m:
    4040            return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
  • django/contrib/localflavor/fi/forms.py

    diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py
    index e9e0fc1..d13c390 100644
    a b class FISocialSecurityNumber(Field): 
    2929    }
    3030
    3131    def clean(self, value):
    32         super(FISocialSecurityNumber, self).clean(value)
    33         if value in EMPTY_VALUES:
     32        # changed not to throw UnicodeDecode error when passed invalid data
     33        # I think this SHOULD call super.clean, which would mean ^^^
     34        if self.required and value in EMPTY_VALUES:
     35             raise ValidationError(self.error_messages['required'])
     36        elif value in EMPTY_VALUES:
    3437            return u''
    3538
    3639        checkmarks = "0123456789ABCDEFHJKLMNPRSTUVWXY"
  • django/contrib/localflavor/jp/forms.py

    diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
    index 70def85..5509cfd 100644
    a b  
    22JP-specific Form helpers
    33"""
    44
    5 from django.core import validators
    65from django.newforms import ValidationError
    76from django.utils.translation import ugettext_lazy as _
    87from django.newforms.fields import RegexField, Select
  • new file django/core/validation.py

    diff --git a/django/core/validation.py b/django/core/validation.py
    new file mode 100644
    index 0000000..4de7c98
    - +  
     1from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
     2from django.utils.safestring import mark_safe
     3
     4NON_FIELD_ERRORS = '__all__'
     5
     6class ErrorList(list, StrAndUnicode):
     7    """
     8    A collection of errors that knows how to display itself in various formats.
     9    """
     10    def __unicode__(self):
     11        return self.as_ul()
     12
     13    def as_ul(self):
     14        if not self: return u''
     15        return mark_safe(u'<ul class="errorlist">%s</ul>'
     16                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
     17
     18    def as_text(self):
     19        if not self: return u''
     20        return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
     21
     22    def __repr__(self):
     23        return repr([force_unicode(e) for e in self])
     24
     25class ValidationError(Exception):
     26    def __init__(self, message):
     27        """
     28        ValidationError can be passed any object that can be printed (usually
     29        a string) or a list of objects.
     30        """
     31        if hasattr(message, '__iter__'):
     32            self.messages = ErrorList([smart_unicode(msg) for msg in message])
     33        else:
     34            message = smart_unicode(message)
     35            self.messages = ErrorList([message])
     36
     37        if isinstance(message, dict):
     38            self.message_dict = message
     39
     40    def __str__(self):
     41        # This is needed because, without a __str__(), printing an exception
     42        # instance would result in this:
     43        # AttributeError: ValidationError instance has no attribute 'args'
     44        # See http://www.python.org/doc/current/tut/node10.html#handling
     45        if hasattr(self, 'message_dict'):
     46            return repr(self.message_dict)
     47        return repr(self.messages)
     48
     49class TypeCoercionError(ValidationError):
     50    pass
  • django/core/validators.py

    diff --git a/django/core/validators.py b/django/core/validators.py
    index 3ef0ade..9ba7cef 100644
    a b  
    11"""
    22A library of validators that return None and raise ValidationError when the
    33provided data isn't valid.
    4 
    5 Validators may be callable classes, and they may have an 'always_test'
    6 attribute. If an 'always_test' attribute exists (regardless of value), the
    7 validator will *always* be run, regardless of whether its associated
    8 form field is required.
    94"""
    105
    11 import urllib2
    126import re
    13 try:
    14     from decimal import Decimal, DecimalException
    15 except ImportError:
    16     from django.utils._decimal import Decimal, DecimalException    # Python 2.3
     7import urllib2
    178
    189from django.conf import settings
    19 from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
    20 from django.utils.functional import Promise, lazy
    21 from django.utils.encoding import force_unicode, smart_str
     10from django.utils.encoding import smart_str
     11from django.utils.translation import ugettext as _
     12from django.core.validation import ValidationError
     13
     14def regexp_validator(regexp, message_codes, message):
     15    if isinstance(regexp, basestring):
     16        regexp = re.compile(regexp)
     17
     18    def _regexp_validator(value, message_dict={}):
     19        if not regexp.search(value):
     20            for m in message_codes:
     21                if m in message_dict:
     22                    mess = message_dict[m]
     23                    break
     24            else:
     25                mess = _(message)
     26
     27            raise ValidationError, mess
     28    return _regexp_validator
    2229
    23 _datere = r'\d{4}-\d{1,2}-\d{1,2}'
    24 _timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
    25 alnum_re = re.compile(r'^\w+$')
    26 alnumurl_re = re.compile(r'^[-\w/]+$')
    27 ansi_date_re = re.compile('^%s$' % _datere)
    28 ansi_time_re = re.compile('^%s$' % _timere)
    29 ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
    3030email_re = re.compile(
    3131    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
    3232    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
    3333    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
    34 integer_re = re.compile(r'^-?\d+$')
    35 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}$')
    36 phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
    37 slug_re = re.compile(r'^[-\w]+$')
    38 url_re = re.compile(r'^https?://\S+$')
    39 
    40 lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
    41 
    42 class ValidationError(Exception):
    43     def __init__(self, message):
    44         "ValidationError can be passed a string or a list."
    45         if isinstance(message, list):
    46             self.messages = [force_unicode(msg) for msg in message]
    47         else:
    48             assert isinstance(message, (basestring, Promise)), ("%s should be a string" % repr(message))
    49             self.messages = [force_unicode(message)]
    50 
    51     def __str__(self):
    52         # This is needed because, without a __str__(), printing an exception
    53         # instance would result in this:
    54         # AttributeError: ValidationError instance has no attribute 'args'
    55         # See http://www.python.org/doc/current/tut/node10.html#handling
    56         return str(self.messages)
    57 
    58 class CriticalValidationError(Exception):
    59     def __init__(self, message):
    60         "ValidationError can be passed a string or a list."
    61         if isinstance(message, list):
    62             self.messages = [force_unicode(msg) for msg in message]
    63         else:
    64             assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
    65             self.messages = [force_unicode(message)]
    66 
    67     def __str__(self):
    68         return str(self.messages)
    69 
    70 def isAlphaNumeric(field_data, all_data):
    71     if not alnum_re.search(field_data):
    72         raise ValidationError, _("This value must contain only letters, numbers and underscores.")
    73 
    74 def isAlphaNumericURL(field_data, all_data):
    75     if not alnumurl_re.search(field_data):
    76         raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
    77 
    78 def isSlug(field_data, all_data):
    79     if not slug_re.search(field_data):
    80         raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
    81 
    82 def isLowerCase(field_data, all_data):
    83     if field_data.lower() != field_data:
    84         raise ValidationError, _("Uppercase letters are not allowed here.")
    8534
    86 def isUpperCase(field_data, all_data):
    87     if field_data.upper() != field_data:
    88         raise ValidationError, _("Lowercase letters are not allowed here.")
     35validate_email = regexp_validator(
     36        email_re, ('invalid_email','invalid',),
     37        'Enter a valid e-mail address.'
     38    )
    8939
    90 def isCommaSeparatedIntegerList(field_data, all_data):
    91     for supposed_int in field_data.split(','):
    92         try:
    93             int(supposed_int)
    94         except ValueError:
    95             raise ValidationError, _("Enter only digits separated by commas.")
     40validate_alpha_numeric_URL = regexp_validator(
     41        re.compile(r'^[-\w/]+$'), ('invalid',),
     42        "This value must contain only letters, numbers, underscores, dashes or slashes."
     43    )
    9644
    97 def isCommaSeparatedEmailList(field_data, all_data):
    98     """
    99     Checks that field_data is a string of e-mail addresses separated by commas.
    100     Blank field_data values will not throw a validation error, and whitespace
    101     is allowed around the commas.
    102     """
    103     for supposed_email in field_data.split(','):
    104         try:
    105             isValidEmail(supposed_email.strip(), '')
    106         except ValidationError:
    107             raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
    108 
    109 def isValidIPAddress4(field_data, all_data):
    110     if not ip4_re.search(field_data):
    111         raise ValidationError, _("Please enter a valid IP address.")
    11245
    113 def isNotEmpty(field_data, all_data):
    114     if field_data.strip() == '':
    115         raise ValidationError, _("Empty values are not allowed here.")
     46validate_alpha_numeric = regexp_validator(
     47        re.compile(r'^\w+$'), ('invalid',),
     48        "This value must contain only letters, numbers and underscores."
     49    )
    11650
    117 def isOnlyDigits(field_data, all_data):
    118     if not field_data.isdigit():
    119         raise ValidationError, _("Non-numeric characters aren't allowed here.")
     51validate_ip_address4 = regexp_validator(
     52        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}$'), ('invalid',),
     53        "Enter a valid IPv4 address."
     54    )
    12055
    121 def isNotOnlyDigits(field_data, all_data):
    122     if field_data.isdigit():
    123         raise ValidationError, _("This value can't be comprised solely of digits.")
     56validate_phone_number = regexp_validator(
     57        re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE), ('invalid',),
     58        'Phone numbers must be in XXX-XXX-XXXX format.'
     59    )
    12460
    125 def isInteger(field_data, all_data):
    126     # This differs from isOnlyDigits because this accepts the negative sign
    127     if not integer_re.search(field_data):
    128         raise ValidationError, _("Enter a whole number.")
     61validate_slug = regexp_validator(
     62        re.compile(r'^[-\w]+$'), ('invalid',),
     63        "This value must contain only letters, numbers, underscores or hyphens."
     64    )
    12965
    130 def isOnlyLetters(field_data, all_data):
    131     if not field_data.isalpha():
    132         raise ValidationError, _("Only alphabetical characters are allowed here.")
     66_datere = r'\d{4}-\d{1,2}-\d{1,2}'
     67validate_ansi_date_format = regexp_validator(
     68        re.compile('^%s$' % _datere), ('invalid',),
     69        'Enter a valid date in YYYY-MM-DD format.'
     70    )
    13371
    134 def _isValidDate(date_string):
    135     """
    136     A helper function used by isValidANSIDate and isValidANSIDatetime to
    137     check if the date is valid.  The date string is assumed to already be in
    138     YYYY-MM-DD format.
    139     """
     72_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
     73validate_ansi_time = regexp_validator(
     74        re.compile('^%s$' % _timere), ('invalid',),
     75        'Enter a valid time in HH:MM format.'
     76    )
     77
     78validate_ansi_datetime_format = regexp_validator(
     79        re.compile('^%s %s$' % (_datere, _timere)), ('invalid',),
     80        'Enter a valid date/time in YYYY-MM-DD HH:MM format.'
     81    )
     82
     83validate_integer = regexp_validator(
     84        re.compile(r'^-?\d+$'), ('invalid',),
     85        "Enter a whole number."
     86    )
     87
     88def validate_correct_ansi_date(value, message_dict={}):
    14089    from datetime import date
    14190    # Could use time.strptime here and catch errors, but datetime.date below
    14291    # produces much friendlier error messages.
    143     year, month, day = map(int, date_string.split('-'))
     92    year, month, day = map(int, value.split('-'))
    14493    # This check is needed because strftime is used when saving the date
    14594    # value to the database, and strftime requires that the year be >=1900.
    14695    if year < 1900:
    def _isValidDate(date_string): 
    14998        date(year, month, day)
    15099    except ValueError, e:
    151100        msg = _('Invalid date: %s') % _(str(e))
    152         raise ValidationError, msg
    153101
    154 def isValidANSIDate(field_data, all_data):
    155     if not ansi_date_re.search(field_data):
    156         raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
    157     _isValidDate(field_data)
     102def validate_ansi_date(value, message_dict={}):
     103    validate_ansi_date_format(value, message_dict)
     104    validate_correct_ansi_date(value, message_dict)
    158105
    159 def isValidANSITime(field_data, all_data):
    160     if not ansi_time_re.search(field_data):
    161         raise ValidationError, _('Enter a valid time in HH:MM format.')
     106def validate_ansi_datetime(value, message_dict={}):
     107    validate_ansi_datetime_format(value, message_dict)
     108    validate_correct_ansi_date(value.split()[0], message_dict)
    162109
    163 def isValidANSIDatetime(field_data, all_data):
    164     if not ansi_datetime_re.search(field_data):
    165         raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
    166     _isValidDate(field_data.split()[0])
     110def validate_not_empty(value, message_dict={}):
     111    if value.strip() == '':
     112        raise ValidationError, _("Empty values are not allowed here.")
    167113
    168 def isValidEmail(field_data, all_data):
    169     if not email_re.search(field_data):
    170         raise ValidationError, _('Enter a valid e-mail address.')
     114def validate_digits_only(value, message_dict={}):
     115    if not value.isdigit():
     116        raise ValidationError, _("Non-numeric characters aren't allowed here.")
    171117
    172 def isValidImage(field_data, all_data):
    173     """
    174     Checks that the file-upload field data contains a valid image (GIF, JPG,
    175     PNG, possibly others -- whatever the Python Imaging Library supports).
    176     """
    177     from PIL import Image
    178     from cStringIO import StringIO
    179     try:
    180         content = field_data.read()
    181     except TypeError:
    182         raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
    183     try:
    184         # load() is the only method that can spot a truncated JPEG,
    185         #  but it cannot be called sanely after verify()
    186         trial_image = Image.open(StringIO(content))
    187         trial_image.load()
    188         # verify() is the only method that can spot a corrupt PNG,
    189         #  but it must be called immediately after the constructor
    190         trial_image = Image.open(StringIO(content))
    191         trial_image.verify()
    192     except Exception: # Python Imaging Library doesn't recognize it as an image
    193         raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
    194 
    195 def isValidImageURL(field_data, all_data):
    196     uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
    197     try:
    198         uc(field_data, all_data)
    199     except URLMimeTypeCheck.InvalidContentType:
    200         raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
     118def validate_not_digits_only(value, message_dict={}):
     119    if value.isdigit():
     120        raise ValidationError, _("This value can't be comprised solely of digits.")
    201121
    202 def isValidPhone(field_data, all_data):
    203     if not phone_re.search(field_data):
    204         raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
     122def validate_letters_only(value, message_dict={}):
     123    if not value.isalpha():
     124        raise ValidationError, _("Only alphabetical characters are allowed here.")
    205125
    206 def isValidQuicktimeVideoURL(field_data, all_data):
    207     "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
    208     uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
    209     try:
    210         uc(field_data, all_data)
    211     except URLMimeTypeCheck.InvalidContentType:
    212         raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
     126def validate_lower(value, message_dict={}):
     127    if value.lower() != value:
     128        raise ValidationError, _("Uppercase letters are not allowed here.")
    213129
    214 def isValidURL(field_data, all_data):
    215     if not url_re.search(field_data):
    216         raise ValidationError, _("A valid URL is required.")
     130def validate_upper(value, message_dict={}):
     131    if value.upper() != value:
     132        raise ValidationError, _("Lowercase letters are not allowed here.")
    217133
    218 def isValidHTML(field_data, all_data):
    219     import urllib, urllib2
    220     try:
    221         u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
    222     except:
    223         # Validator or Internet connection is unavailable. Fail silently.
    224         return
    225     html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
    226     if html_is_valid:
    227         return
    228     from xml.dom.minidom import parseString
    229     error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
    230     raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
    231 
    232 def isWellFormedXml(field_data, all_data):
    233     from xml.dom.minidom import parseString
    234     try:
    235         parseString(field_data)
    236     except Exception, e: # Naked except because we're not sure what will be thrown
    237         raise ValidationError, _("Badly formed XML: %s") % str(e)
     134def validate_comma_separated_integer_list(value, message_dict={}):
     135    for supposed_integer in value.split(','):
     136        try:
     137            validate_integer(supposed_integer.strip())
     138        except ValidationError:
     139            raise ValidationError, _("Enter valid integers separated by commas.")
    238140
    239 def isWellFormedXmlFragment(field_data, all_data):
    240     isWellFormedXml('<root>%s</root>' % field_data, all_data)
     141def validate_comma_separated_email_list(value, message_dict={}):
     142    """
     143    Checks that value is a string of e-mail addresses separated by commas.
     144    Blank value values will not throw a validation error, and whitespace
     145    is allowed around the commas.
     146    """
     147    for supposed_email in value.split(','):
     148        try:
     149            validate_email(supposed_email.strip())
     150        except ValidationError:
     151            raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
    241152
    242 def isExistingURL(field_data, all_data):
     153def validate_existing_url(value, message_dict={}):
    243154    try:
    244155        headers = {
    245156            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
    def isExistingURL(field_data, all_data): 
    248159            "Connection" : "close",
    249160            "User-Agent": settings.URL_VALIDATOR_USER_AGENT
    250161            }
    251         req = urllib2.Request(field_data,None, headers)
     162        req = urllib2.Request(value,None, headers)
    252163        u = urllib2.urlopen(req)
    253164    except ValueError:
    254         raise ValidationError, _("Invalid URL: %s") % field_data
     165        raise ValidationError, message_dict.get('invalid', _("Invalid URL: %s") % value)
    255166    except urllib2.HTTPError, e:
    256167        # 401s are valid; they just mean authorization is required.
    257168        # 301 and 302 are redirects; they just mean look somewhere else.
    258169        if str(e.code) not in ('401','301','302'):
    259             raise ValidationError, _("The URL %s is a broken link.") % field_data
     170            raise ValidationError, message_dict.get('invalid_link', _("This URL appears to be a broken link."))
    260171    except: # urllib2.URLError, httplib.InvalidURL, etc.
    261         raise ValidationError, _("The URL %s is a broken link.") % field_data
    262 
    263 def isValidUSState(field_data, all_data):
    264     "Checks that the given string is a valid two-letter U.S. state abbreviation"
    265     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']
    266     if field_data.upper() not in states:
    267         raise ValidationError, _("Enter a valid U.S. state abbreviation.")
    268 
    269 def hasNoProfanities(field_data, all_data):
    270     """
    271     Checks that the given string has no profanities in it. This does a simple
    272     check for whether each profanity exists within the string, so 'fuck' will
    273     catch 'motherfucker' as well. Raises a ValidationError such as:
    274         Watch your mouth! The words "f--k" and "s--t" are not allowed here.
    275     """
    276     field_data = field_data.lower() # normalize
    277     words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
    278     if words_seen:
    279         from django.utils.text import get_text_list
    280         plural = len(words_seen)
    281         raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
    282             "Watch your mouth! The words %s are not allowed here.", plural) % \
    283             get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
    284 
    285 class AlwaysMatchesOtherField(object):
    286     def __init__(self, other_field_name, error_message=None):
    287         self.other = other_field_name
    288         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
    289         self.always_test = True
    290 
    291     def __call__(self, field_data, all_data):
    292         if field_data != all_data[self.other]:
    293             raise ValidationError, self.error_message
    294 
    295 class ValidateIfOtherFieldEquals(object):
    296     def __init__(self, other_field, other_value, validator_list):
    297         self.other_field, self.other_value = other_field, other_value
    298         self.validator_list = validator_list
    299         self.always_test = True
    300 
    301     def __call__(self, field_data, all_data):
    302         if self.other_field in all_data and all_data[self.other_field] == self.other_value:
    303             for v in self.validator_list:
    304                 v(field_data, all_data)
    305 
    306 class RequiredIfOtherFieldNotGiven(object):
    307     def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
    308         self.other, self.error_message = other_field_name, error_message
    309         self.always_test = True
    310 
    311     def __call__(self, field_data, all_data):
    312         if not all_data.get(self.other, False) and not field_data:
    313             raise ValidationError, self.error_message
    314 
    315 class RequiredIfOtherFieldsGiven(object):
    316     def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
    317         self.other, self.error_message = other_field_names, error_message
    318         self.always_test = True
    319 
    320     def __call__(self, field_data, all_data):
    321         for field in self.other:
    322             if all_data.get(field, False) and not field_data:
    323                 raise ValidationError, self.error_message
    324 
    325 class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
    326     "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
    327     def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
    328         RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
    329 
    330 class RequiredIfOtherFieldEquals(object):
    331     def __init__(self, other_field, other_value, error_message=None, other_label=None):
    332         self.other_field = other_field
    333         self.other_value = other_value
    334         other_label = other_label or other_value
    335         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
    336             'field': other_field, 'value': other_label})
    337         self.always_test = True
    338 
    339     def __call__(self, field_data, all_data):
    340         if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
    341             raise ValidationError(self.error_message)
    342 
    343 class RequiredIfOtherFieldDoesNotEqual(object):
    344     def __init__(self, other_field, other_value, other_label=None, error_message=None):
    345         self.other_field = other_field
    346         self.other_value = other_value
    347         other_label = other_label or other_value
    348         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
    349             'field': other_field, 'value': other_label})
    350         self.always_test = True
    351 
    352     def __call__(self, field_data, all_data):
    353         if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
    354             raise ValidationError(self.error_message)
    355 
    356 class IsLessThanOtherField(object):
    357     def __init__(self, other_field_name, error_message):
    358         self.other, self.error_message = other_field_name, error_message
    359 
    360     def __call__(self, field_data, all_data):
    361         if field_data > all_data[self.other]:
    362             raise ValidationError, self.error_message
    363 
    364 class UniqueAmongstFieldsWithPrefix(object):
    365     def __init__(self, field_name, prefix, error_message):
    366         self.field_name, self.prefix = field_name, prefix
    367         self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
    368 
    369     def __call__(self, field_data, all_data):
    370         for field_name, value in all_data.items():
    371             if field_name != self.field_name and value == field_data:
    372                 raise ValidationError, self.error_message
    373 
    374 class NumberIsInRange(object):
    375     """
    376     Validator that tests if a value is in a range (inclusive).
    377     """
    378     def __init__(self, lower=None, upper=None, error_message=''):
    379         self.lower, self.upper = lower, upper
    380         if not error_message:
    381             if lower and upper:
    382                  self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
    383             elif lower:
    384                 self.error_message = _("This value must be at least %s.") % lower
    385             elif upper:
    386                 self.error_message = _("This value must be no more than %s.") % upper
    387         else:
    388             self.error_message = error_message
    389 
    390     def __call__(self, field_data, all_data):
    391         # Try to make the value numeric. If this fails, we assume another
    392         # validator will catch the problem.
    393         try:
    394             val = float(field_data)
    395         except ValueError:
    396             return
    397 
    398         # Now validate
    399         if self.lower and self.upper and (val < self.lower or val > self.upper):
    400             raise ValidationError(self.error_message)
    401         elif self.lower and val < self.lower:
    402             raise ValidationError(self.error_message)
    403         elif self.upper and val > self.upper:
    404             raise ValidationError(self.error_message)
    405 
    406 class IsAPowerOf(object):
    407     """
    408     Usage: If you create an instance of the IsPowerOf validator:
    409         v = IsAPowerOf(2)
    410    
    411     The following calls will succeed:
    412         v(4, None)
    413         v(8, None)
    414         v(16, None)
    415    
    416     But this call:
    417         v(17, None)
    418     will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
    419     """
    420     def __init__(self, power_of):
    421         self.power_of = power_of
    422 
    423     def __call__(self, field_data, all_data):
    424         from math import log
    425         val = log(int(field_data)) / log(self.power_of)
    426         if val != int(val):
    427             raise ValidationError, _("This value must be a power of %s.") % self.power_of
    428 
    429 class IsValidDecimal(object):
    430     def __init__(self, max_digits, decimal_places):
    431         self.max_digits, self.decimal_places = max_digits, decimal_places
    432 
    433     def __call__(self, field_data, all_data):
    434         try:
    435             val = Decimal(field_data)
    436         except DecimalException:
    437             raise ValidationError, _("Please enter a valid decimal number.")
    438 
    439         pieces = str(val).lstrip("-").split('.')
    440         decimals = (len(pieces) == 2) and len(pieces[1]) or 0
    441         digits = len(pieces[0])
    442 
    443         if digits + decimals > self.max_digits:
    444             raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
    445                 "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
    446         if digits > (self.max_digits - self.decimal_places):
    447             raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
    448                 "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)
    449         if decimals > self.decimal_places:
    450             raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
    451                 "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
    452 
    453 def isValidFloat(field_data, all_data):
    454     data = smart_str(field_data)
    455     try:
    456         float(data)
    457     except ValueError:
    458         raise ValidationError, _("Please enter a valid floating point number.")
    459 
    460 class HasAllowableSize(object):
    461     """
    462     Checks that the file-upload field data is a certain size. min_size and
    463     max_size are measurements in bytes.
    464     """
    465     def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
    466         self.min_size, self.max_size = min_size, max_size
    467         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)
    468         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)
    469 
    470     def __call__(self, field_data, all_data):
    471         try:
    472             content = field_data.read()
    473         except TypeError:
    474             raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
    475         if self.min_size is not None and len(content) < self.min_size:
    476             raise ValidationError, self.min_error_message
    477         if self.max_size is not None and len(content) > self.max_size:
    478             raise ValidationError, self.max_error_message
    479 
    480 class MatchesRegularExpression(object):
    481     """
    482     Checks that the field matches the given regular-expression. The regex
    483     should be in string format, not already compiled.
    484     """
    485     def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
    486         self.regexp = re.compile(regexp)
    487         self.error_message = error_message
    488 
    489     def __call__(self, field_data, all_data):
    490         if not self.regexp.search(field_data):
    491             raise ValidationError(self.error_message)
    492 
    493 class AnyValidator(object):
    494     """
    495     This validator tries all given validators. If any one of them succeeds,
    496     validation passes. If none of them succeeds, the given message is thrown
    497     as a validation error. The message is rather unspecific, so it's best to
    498     specify one on instantiation.
    499     """
    500     def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
    501         if validator_list is None: validator_list = []
    502         self.validator_list = validator_list
    503         self.error_message = error_message
    504         for v in validator_list:
    505             if hasattr(v, 'always_test'):
    506                 self.always_test = True
    507 
    508     def __call__(self, field_data, all_data):
    509         for v in self.validator_list:
    510             try:
    511                 v(field_data, all_data)
    512                 return
    513             except ValidationError, e:
    514                 pass
    515         raise ValidationError(self.error_message)
     172        raise ValidationError, message_dict.get('invalid_link', _("This URL appears to be a broken link."))
    516173
    517174class URLMimeTypeCheck(object):
    518175    "Checks that the provided URL points to a document with a listed mime type"
    class URLMimeTypeCheck(object): 
    524181    def __init__(self, mime_type_list):
    525182        self.mime_type_list = mime_type_list
    526183
    527     def __call__(self, field_data, all_data):
    528         import urllib2
    529         try:
    530             isValidURL(field_data, all_data)
    531         except ValidationError:
    532             raise
     184    def __call__(self, value, message_dict={}):
     185        validate_existing_url(value)
    533186        try:
    534             info = urllib2.urlopen(field_data).info()
     187            info = urllib2.urlopen(value).info()
    535188        except (urllib2.HTTPError, urllib2.URLError):
    536             raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
     189            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % value
    537190        content_type = info['content-type']
    538191        if content_type not in self.mime_type_list:
    539192            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
    540                 'url': field_data, 'contenttype': content_type}
    541 
    542 class RelaxNGCompact(object):
    543     "Validate against a Relax NG compact schema"
    544     def __init__(self, schema_path, additional_root_element=None):
    545         self.schema_path = schema_path
    546         self.additional_root_element = additional_root_element
    547 
    548     def __call__(self, field_data, all_data):
    549         import os, tempfile
    550         if self.additional_root_element:
    551             field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
    552                 'are': self.additional_root_element,
    553                 'data': field_data
    554             }
    555         filename = tempfile.mktemp() # Insecure, but nothing else worked
    556         fp = open(filename, 'w')
    557         fp.write(field_data)
    558         fp.close()
    559         if not os.path.exists(settings.JING_PATH):
    560             raise Exception, "%s not found!" % settings.JING_PATH
    561         p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
    562         errors = [line.strip() for line in p.readlines()]
    563         p.close()
    564         os.unlink(filename)
    565         display_errors = []
    566         lines = field_data.split('\n')
    567         for error in errors:
    568             ignored, line, level, message = error.split(':', 3)
    569             # Scrape the Jing error messages to reword them more nicely.
    570             m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
    571             if m:
    572                 display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
    573                     {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
    574                 continue
    575             if message.strip() == 'text not allowed here':
    576                 display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
    577                     {'line':line, 'start':lines[int(line) - 1][:30]})
    578                 continue
    579             m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
    580             if m:
    581                 display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
    582                     {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
    583                 continue
    584             m = re.search(r'\s*unknown element "(.*?)"', message)
    585             if m:
    586                 display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
    587                     {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
    588                 continue
    589             if message.strip() == 'required attributes missing':
    590                 display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
    591                     {'line':line, 'start':lines[int(line) - 1][:30]})
    592                 continue
    593             m = re.search(r'\s*bad value for attribute "(.*?)"', message)
    594             if m:
    595                 display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
    596                     {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
    597                 continue
    598             # Failing all those checks, use the default error message.
    599             display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
    600             display_errors.append(display_error)
    601         if len(display_errors) > 0:
    602             raise ValidationError, display_errors
     193                'url': value, 'contenttype': content_type}
     194
     195def validate_image_url(value, message_dict={}):
     196    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
     197    try:
     198        uc(value, message_dict)
     199    except URLMimeTypeCheck.InvalidContentType:
     200        raise ValidationError, _("The URL %s does not point to a valid image.") % value
     201
     202
     203def validate_float(value, message_dict={}):
     204    data = smart_str(value)
     205    try:
     206        float(data)
     207    except ValueError:
     208        raise ValidationError, _("Please enter a valid floating point number.")
  • django/db/models/__init__.py

    diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
    index 86763d9..afd9151 100644
    a b  
    11from django.conf import settings
    22from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
    3 from django.core import validators
    43from django.db import connection
    54from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
    65from django.db.models.query import Q
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 338b061..47ac6f9 100644
    a b except NameError: 
    1111import django.db.models.manipulators    # Imported to register signal handler.
    1212import django.db.models.manager         # Ditto.
    1313from django.core import validators
     14from django.core.validation import ValidationError, NON_FIELD_ERRORS
    1415from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
    1516from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
    1617from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
    from django.db.models.loading import register_models, get_model 
    2223from django.dispatch import dispatcher
    2324from django.utils.datastructures import SortedDict
    2425from django.utils.functional import curry
     26from django.utils.translation import ugettext_lazy as _
    2527from django.utils.encoding import smart_str, force_unicode, smart_unicode
    2628from django.core.files.move import file_move_safe
    2729from django.core.files import locks
    class Model(object): 
    352354
    353355    save_base.alters_data = True
    354356
    355     def validate(self):
     357    def clean(self, new_data=None):
     358        self.to_python()
     359        self.validate(new_data)
     360
     361    def to_python(self):
     362        error_dict = {}
     363        for f in self._meta.fields:
     364            try:
     365                value = f.to_python(getattr(self, f.attname, f.get_default()))
     366                setattr(self, f.attname, value)
     367            except ValidationError, e:
     368                error_dict[f.name] = e.messages
     369        if error_dict:
     370            raise ValidationError(error_dict)
     371
     372    def validate(self, new_data=None):
    356373        """
    357         First coerces all fields on this instance to their proper Python types.
    358         Then runs validation on every field. Returns a dictionary of
    359         field_name -> error_list.
     374        Validate the data on the model, if new_data is supplied, try and use those instead of
     375        actual values on the fields. Note that the fields are validated separately.
    360376        """
     377        if new_data is not None:
     378            def get_value(f):
     379                if f.name in new_data:
     380                    return f.to_python(new_data[f.name])
     381                return getattr(self, f.attname, f.get_default())
     382        else:
     383            get_value = lambda f: getattr(self, f.attname, f.get_default())
    361384        error_dict = {}
    362         invalid_python = {}
    363385        for f in self._meta.fields:
    364386            try:
    365                 setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default())))
    366             except validators.ValidationError, e:
     387                value = get_value(f)
     388                f.validate(value, instance=self)
     389                if hasattr(self, 'validate_%s' % f.name):
     390                    getattr(self, 'validate_%s' % f.name)(value)
     391            except ValidationError, e:
    367392                error_dict[f.name] = e.messages
    368                 invalid_python[f.name] = 1
    369         for f in self._meta.fields:
    370             if f.name in invalid_python:
    371                 continue
    372             errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__)
    373             if errors:
    374                 error_dict[f.name] = errors
    375         return error_dict
     393
     394        for un_together in self._meta.unique_together:
     395            lookup = {}
     396            for name in un_together:
     397                if name in error_dict:
     398                    break
     399                f = self._meta.get_field(name)
     400                lookup['%s__exact' % name] = get_value(f)
     401            try:
     402                qset = self.__class__._default_manager.all()
     403                if self.pk:
     404                    qset = qset.exclude(pk=self.pk)
     405                obj = qset.get(**lookup)
     406                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together)
     407            except self.DoesNotExist:
     408                pass
     409
     410        if error_dict:
     411            raise ValidationError(error_dict)
    376412
    377413    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
    378414        """
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index a69cc5a..bef615b 100644
    a b from django.db.models import signals 
    1212from django.db.models.query_utils import QueryWrapper
    1313from django.dispatch import dispatcher
    1414from django.conf import settings
     15from django.oldforms import validators as oldvalidators
    1516from django.core import validators
    1617from django import oldforms
    1718from django import newforms as forms
    class Field(object): 
    8182    creation_counter = 0
    8283    auto_creation_counter = -1
    8384
     85    validators = []
     86
    8487    def __init__(self, verbose_name=None, name=None, primary_key=False,
    8588            max_length=None, unique=False, blank=False, null=False,
    8689            db_index=False, core=False, rel=None, default=NOT_PROVIDED,
    8790            editable=True, serialize=True, prepopulate_from=None,
    8891            unique_for_date=None, unique_for_month=None, unique_for_year=None,
    8992            validator_list=None, choices=None, radio_admin=None, help_text='',
    90             db_column=None, db_tablespace=None, auto_created=False):
     93            db_column=None, db_tablespace=None, auto_created=False, validators=[]):
    9194        self.name = name
    9295        self.verbose_name = verbose_name
    9396        self.primary_key = primary_key
    class Field(object): 
    100103        self.core, self.rel, self.default = core, rel, default
    101104        self.editable = editable
    102105        self.serialize = serialize
     106        self.validators = validators + self.validators
    103107        self.validator_list = validator_list or []
    104108        self.prepopulate_from = prepopulate_from
    105109        self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
    class Field(object): 
    167171            return get_creation_module().DATA_TYPES[self.get_internal_type()] % data
    168172        except KeyError:
    169173            return None
    170 
    171174    def unique(self):
    172175        return self._unique or self.primary_key
    173176    unique = property(unique)
    174177
    175     def validate_full(self, field_data, all_data):
     178    def validate(self, value, instance=None):
    176179        """
    177         Returns a list of errors for this field. This is the main interface,
    178         as it encapsulates some basic validation logic used by all fields.
    179         Subclasses should implement validate(), not validate_full().
    180         """
    181         if not self.blank and not field_data:
    182             return [_('This field is required.')]
    183         try:
    184             self.validate(field_data, all_data)
    185         except validators.ValidationError, e:
    186             return e.messages
    187         return []
    188 
    189     def validate(self, field_data, all_data):
    190         """
    191         Raises validators.ValidationError if field_data has any errors.
     180        Raises validators.ValidationError if value has any errors.
    192181        Subclasses should override this to specify field-specific validation
    193         logic. This method should assume field_data has already been converted
     182        logic. This method should assume value has already been converted
    194183        into the appropriate data type by Field.to_python().
    195184        """
    196         pass
     185        if not self.blank and self.editable and not value:
     186            raise validators.ValidationError(_('This field is required.'))
     187        elist = []
     188        for validator in self.validators:
     189            validator(value)   
     190        if self.unique and instance:
     191            try:
     192                qset = instance.__class__._default_manager.all()
     193                if instance.pk:
     194                    qset = qset.exclude(pk=instance.pk)
     195                obj = qset.get(**{'%s__exact' % self.name : value})
     196                raise validators.ValidationError(_('This field must be unique'))
     197            except instance.DoesNotExist:
     198                pass
    197199
    198200    def set_attributes_from_name(self, name):
    199201        self.name = name
    class Field(object): 
    351353                    core_field_names.extend(f.get_manipulator_field_names(name_prefix))
    352354            # Now, if there are any, add the validator to this FormField.
    353355            if core_field_names:
    354                 params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
     356                params['validator_list'].append(oldvalidators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
    355357
    356358        # Finally, add the field_names.
    357359        field_names = self.get_manipulator_field_names(name_prefix)
    class DateField(Field): 
    547549            return value.date()
    548550        if isinstance(value, datetime.date):
    549551            return value
    550         validators.isValidANSIDate(value, None)
    551552        try:
    552553            return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
    553554        except ValueError:
    class DecimalField(Field): 
    739740        return super(DecimalField, self).formfield(**defaults)
    740741
    741742class EmailField(CharField):
     743    validators = [validators.validate_email]
    742744    def __init__(self, *args, **kwargs):
    743745        kwargs['max_length'] = kwargs.get('max_length', 75)
    744746        CharField.__init__(self, *args, **kwargs)
    class EmailField(CharField): 
    746748    def get_manipulator_field_objs(self):
    747749        return [oldforms.EmailField]
    748750
    749     def validate(self, field_data, all_data):
    750         validators.isValidEmail(field_data, all_data)
    751 
    752751    def formfield(self, **kwargs):
    753752        defaults = {'form_class': forms.EmailField}
    754753        defaults.update(kwargs)
    class FileField(Field): 
    785784                        self.always_test = True
    786785                    def __call__(self, field_data, all_data):
    787786                        if not all_data.get(self.other_file_field_name, False):
    788                             c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
     787                            c = oldvalidators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
    789788                            c(field_data, all_data)
    790789                # First, get the core fields, if any.
    791790                core_field_names = []
    class FileField(Field): 
    796795                if core_field_names:
    797796                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
    798797            else:
    799                 v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
     798                v = oldvalidators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
    800799                v.always_test = True
    801800                field_list[0].validator_list.append(v)
    802801                field_list[0].is_required = field_list[1].is_required = False
    class IntegerField(Field): 
    964963
    965964class IPAddressField(Field):
    966965    empty_strings_allowed = False
     966    validators = [validators.validate_ip_address4]
     967
    967968    def __init__(self, *args, **kwargs):
    968969        kwargs['max_length'] = 15
    969970        Field.__init__(self, *args, **kwargs)
    class IPAddressField(Field): 
    974975    def get_internal_type(self):
    975976        return "IPAddressField"
    976977
    977     def validate(self, field_data, all_data):
    978         validators.isValidIPAddress4(field_data, None)
    979 
    980978    def formfield(self, **kwargs):
    981979        defaults = {'form_class': forms.IPAddressField}
    982980        defaults.update(kwargs)
    class NullBooleanField(Field): 
    10071005        return super(NullBooleanField, self).formfield(**defaults)
    10081006
    10091007class PhoneNumberField(IntegerField):
     1008    validators = [validators.validate_phone_number]
     1009
    10101010    def get_manipulator_field_objs(self):
    10111011        return [oldforms.PhoneNumberField]
    10121012
    10131013    def get_internal_type(self):
    10141014        return "PhoneNumberField"
    10151015
    1016     def validate(self, field_data, all_data):
    1017         validators.isValidPhone(field_data, all_data)
    1018 
    10191016    def formfield(self, **kwargs):
    10201017        from django.contrib.localflavor.us.forms import USPhoneNumberField
    10211018        defaults = {'form_class': USPhoneNumberField}
    class PositiveSmallIntegerField(IntegerField): 
    10471044        return super(PositiveSmallIntegerField, self).formfield(**defaults)
    10481045
    10491046class SlugField(CharField):
     1047    validators = [validators.validate_slug]
    10501048    def __init__(self, *args, **kwargs):
    10511049        kwargs['max_length'] = kwargs.get('max_length', 50)
    1052         kwargs.setdefault('validator_list', []).append(validators.isSlug)
    10531050        # Set db_index=True unless it's been set manually.
    10541051        if 'db_index' not in kwargs:
    10551052            kwargs['db_index'] = True
    class URLField(CharField): 
    11451142    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
    11461143        kwargs['max_length'] = kwargs.get('max_length', 200)
    11471144        if verify_exists:
    1148             kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
     1145            kwargs.setdefault('validators', []).append(validators.validate_existing_url)
    11491146        self.verify_exists = verify_exists
    11501147        CharField.__init__(self, verbose_name, name, **kwargs)
    11511148
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index a1977c0..cc79030 100644
    a b class ManyToManyField(RelatedField, Field): 
    802802        objects = mod._default_manager.in_bulk(pks)
    803803        if len(objects) != len(pks):
    804804            badkeys = [k for k in pks if k not in objects]
    805             raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
     805            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
    806806                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
    807807                'self': self.verbose_name,
    808808                'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
  • django/newforms/__init__.py

    diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
    index 0d9c68f..6f881f0 100644
    a b TODO: 
    1010    "This form field requires foo.js" and form.js_includes()
    1111"""
    1212
    13 from util import ValidationError
     13from django.core.validation import ValidationError
    1414from widgets import *
    1515from fields import *
    1616from forms import *
  • django/newforms/fields.py

    diff --git a/django/newforms/fields.py b/django/newforms/fields.py
    index ad46d78..0e1e866 100644
    a b except NameError: 
    2424
    2525from django.utils.translation import ugettext_lazy as _
    2626from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
     27from django.core.validation import ValidationError, ErrorList, TypeCoercionError
     28from django.core import validators
    2729
    28 from util import ErrorList, ValidationError
    2930from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
    3031from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
    3132
    EMPTY_VALUES = (None, '') 
    4647
    4748class Field(object):
    4849    widget = TextInput # Default widget to use when rendering this type of Field.
     50    validators = []
    4951    hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
    5052    default_error_messages = {
    5153        'required': _(u'This field is required.'),
    class Field(object): 
    5658    creation_counter = 0
    5759
    5860    def __init__(self, required=True, widget=None, label=None, initial=None,
    59                  help_text=None, error_messages=None):
     61                 help_text=None, error_messages=None, validators=[]):
    6062        # required -- Boolean that specifies whether the field is required.
    6163        #             True by default.
    6264        # widget -- A Widget class, or instance of a Widget class, that should
    class Field(object): 
    7072        # initial -- A value to use in this Field's initial display. This value
    7173        #            is *not* used as a fallback if data isn't given.
    7274        # help_text -- An optional string to use as "help text" for this Field.
     75        # validators -- Optional list of additional validator functions
    7376        if label is not None:
    7477            label = smart_unicode(label)
     78        self.validators = self.validators + validators
    7579        self.required, self.label, self.initial = required, label, initial
    7680        self.help_text = smart_unicode(help_text or '')
    7781        widget = widget or self.widget
    class Field(object): 
    99103        messages.update(error_messages or {})
    100104        self.error_messages = messages
    101105
     106    def to_python(self, value):
     107        if value in EMPTY_VALUES:
     108            return None
     109        return smart_unicode(value)
     110
     111    def validate(self, value):
     112        if self.required and value in EMPTY_VALUES:
     113            raise ValidationError(self.error_messages['required'])
     114        elif value in EMPTY_VALUES:
     115            return
     116        elist = ErrorList()
     117        for validator in self.validators:
     118            try:
     119                validator(value, self.error_messages)
     120            except ValidationError, e:
     121                elist.extend(e.messages)
     122        if elist:
     123            raise ValidationError(elist)
     124
    102125    def clean(self, value):
    103126        """
    104127        Validates the given value and returns its "cleaned" value as an
    class Field(object): 
    106129
    107130        Raises ValidationError for any errors.
    108131        """
    109         if self.required and value in EMPTY_VALUES:
    110             raise ValidationError(self.error_messages['required'])
     132        value = self.to_python(value)
     133        self.validate(value)
    111134        return value
    112135
    113136    def widget_attrs(self, widget):
    class CharField(Field): 
    134157        self.max_length, self.min_length = max_length, min_length
    135158        super(CharField, self).__init__(*args, **kwargs)
    136159
    137     def clean(self, value):
    138         "Validates max_length and min_length. Returns a Unicode object."
    139         super(CharField, self).clean(value)
     160    def to_python(self, value):
    140161        if value in EMPTY_VALUES:
    141162            return u''
    142         value = smart_unicode(value)
     163        return smart_unicode(value)
     164
     165    def validate(self, value):
     166        "Validates max_length and min_length. Returns a Unicode object."
     167        super(CharField, self).validate(value)
    143168        value_length = len(value)
     169        if value_length == 0 and not self.required:
     170            return
    144171        if self.max_length is not None and value_length > self.max_length:
    145172            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
    146173        if self.min_length is not None and value_length < self.min_length:
    147174            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
    148         return value
    149175
    150176    def widget_attrs(self, widget):
    151177        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
    class IntegerField(Field): 
    163189        self.max_value, self.min_value = max_value, min_value
    164190        super(IntegerField, self).__init__(*args, **kwargs)
    165191
    166     def clean(self, value):
    167         """
    168         Validates that int() can be called on the input. Returns the result
    169         of int(). Returns None for empty values.
    170         """
    171         super(IntegerField, self).clean(value)
     192    def to_python(self, value):
    172193        if value in EMPTY_VALUES:
    173194            return None
    174195        try:
    175             value = int(str(value))
     196            return int(smart_str(value))
    176197        except (ValueError, TypeError):
    177             raise ValidationError(self.error_messages['invalid'])
     198            raise TypeCoercionError(self.error_messages['invalid'])
     199
     200    def validate(self, value):
     201        """
     202        Validates that int() can be called on the input. Returns the result
     203        of int(). Returns None for empty values.
     204        """
     205        super(IntegerField, self).validate(value)
     206        if value is None: return
    178207        if self.max_value is not None and value > self.max_value:
    179208            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    180209        if self.min_value is not None and value < self.min_value:
    181210            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    182         return value
    183211
    184212class FloatField(Field):
    185213    default_error_messages = {
    class FloatField(Field): 
    192220        self.max_value, self.min_value = max_value, min_value
    193221        Field.__init__(self, *args, **kwargs)
    194222
    195     def clean(self, value):
     223    def to_python(self, value):
    196224        """
    197225        Validates that float() can be called on the input. Returns a float.
    198226        Returns None for empty values.
    199227        """
    200         super(FloatField, self).clean(value)
    201         if not self.required and value in EMPTY_VALUES:
     228        if value in EMPTY_VALUES:
    202229            return None
    203230        try:
    204             value = float(value)
     231            return float(value)
    205232        except (ValueError, TypeError):
    206             raise ValidationError(self.error_messages['invalid'])
     233            raise TypeCoercionError(self.error_messages['invalid'])
     234
     235    def validate(self, value):
     236        super(FloatField, self).validate(value)
     237        if value is None: return
    207238        if self.max_value is not None and value > self.max_value:
    208239            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    209240        if self.min_value is not None and value < self.min_value:
    210241            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    211         return value
    212242
    213243class DecimalField(Field):
    214244    default_error_messages = {
    class DecimalField(Field): 
    225255        self.max_digits, self.decimal_places = max_digits, decimal_places
    226256        Field.__init__(self, *args, **kwargs)
    227257
    228     def clean(self, value):
     258    def to_python(self, value):
    229259        """
    230260        Validates that the input is a decimal number. Returns a Decimal
    231261        instance. Returns None for empty values. Ensures that there are no more
    232262        than max_digits in the number, and no more than decimal_places digits
    233263        after the decimal point.
    234264        """
    235         super(DecimalField, self).clean(value)
    236         if not self.required and value in EMPTY_VALUES:
     265        if value in EMPTY_VALUES:
    237266            return None
    238267        value = smart_str(value).strip()
    239268        try:
    240             value = Decimal(value)
     269            return Decimal(value)
    241270        except DecimalException:
    242             raise ValidationError(self.error_messages['invalid'])
     271            raise TypeCoercionError(self.error_messages['invalid'])
     272
     273    def validate(self, value):
     274        super(DecimalField, self).validate(value)
     275        if value is None: return
    243276        pieces = str(value).lstrip("-").split('.')
    244277        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
    245278        digits = len(pieces[0])
    class DecimalField(Field): 
    253286            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
    254287        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
    255288            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
    256         return value
    257289
    258290DEFAULT_DATE_INPUT_FORMATS = (
    259291    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
    class DateField(Field): 
    272304        super(DateField, self).__init__(*args, **kwargs)
    273305        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
    274306
    275     def clean(self, value):
     307    def to_python(self, value):
    276308        """
    277309        Validates that the input can be converted to a date. Returns a Python
    278310        datetime.date object.
    279311        """
    280         super(DateField, self).clean(value)
    281312        if value in EMPTY_VALUES:
    282313            return None
    283314        if isinstance(value, datetime.datetime):
    class DateField(Field): 
    289320                return datetime.date(*time.strptime(value, format)[:3])
    290321            except ValueError:
    291322                continue
    292         raise ValidationError(self.error_messages['invalid'])
     323        raise TypeCoercionError(self.error_messages['invalid'])
    293324
    294325DEFAULT_TIME_INPUT_FORMATS = (
    295326    '%H:%M:%S',     # '14:30:59'
    class TimeField(Field): 
    305336        super(TimeField, self).__init__(*args, **kwargs)
    306337        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
    307338
    308     def clean(self, value):
     339    def to_python(self, value):
    309340        """
    310341        Validates that the input can be converted to a time. Returns a Python
    311342        datetime.time object.
    312343        """
    313         super(TimeField, self).clean(value)
    314344        if value in EMPTY_VALUES:
    315345            return None
    316346        if isinstance(value, datetime.time):
    class TimeField(Field): 
    320350                return datetime.time(*time.strptime(value, format)[3:6])
    321351            except ValueError:
    322352                continue
    323         raise ValidationError(self.error_messages['invalid'])
     353        raise TypeCoercionError(self.error_messages['invalid'])
    324354
    325355DEFAULT_DATETIME_INPUT_FORMATS = (
    326356    '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
    class DateTimeField(Field): 
    344374        super(DateTimeField, self).__init__(*args, **kwargs)
    345375        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
    346376
    347     def clean(self, value):
     377    def to_python(self, value):
    348378        """
    349379        Validates that the input can be converted to a datetime. Returns a
    350380        Python datetime.datetime object.
    351381        """
    352         super(DateTimeField, self).clean(value)
    353382        if value in EMPTY_VALUES:
    354383            return None
    355384        if isinstance(value, datetime.datetime):
    class DateTimeField(Field): 
    367396                return datetime.datetime(*time.strptime(value, format)[:6])
    368397            except ValueError:
    369398                continue
    370         raise ValidationError(self.error_messages['invalid'])
     399        raise TypeCoercionError(self.error_messages['invalid'])
    371400
    372401class RegexField(CharField):
    373402    def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
    class RegexField(CharField): 
    386415            regex = re.compile(regex)
    387416        self.regex = regex
    388417
    389     def clean(self, value):
     418    def validate(self, value):
    390419        """
    391420        Validates that the input matches the regular expression. Returns a
    392421        Unicode object.
    393422        """
    394         value = super(RegexField, self).clean(value)
     423        super(RegexField, self).validate(value)
    395424        if value == u'':
    396             return value
     425            return
    397426        if not self.regex.search(value):
    398427            raise ValidationError(self.error_messages['invalid'])
    399         return value
    400428
    401429email_re = re.compile(
    402430    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
    403431    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
    404432    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
    405433
    406 class EmailField(RegexField):
     434class EmailField(CharField):
    407435    default_error_messages = {
    408436        'invalid': _(u'Enter a valid e-mail address.'),
    409437    }
    410 
    411     def __init__(self, max_length=None, min_length=None, *args, **kwargs):
    412         RegexField.__init__(self, email_re, max_length, min_length, *args,
    413                             **kwargs)
     438    validators = [validators.validate_email]
    414439
    415440try:
    416441    from django.conf import settings
    class FileField(Field): 
    424449    widget = FileInput
    425450    default_error_messages = {
    426451        'invalid': _(u"No file was submitted. Check the encoding type on the form."),
    427         'missing': _(u"No file was submitted."),
    428452        'empty': _(u"The submitted file is empty."),
    429453    }
    430454
    431     def __init__(self, *args, **kwargs):
    432         super(FileField, self).__init__(*args, **kwargs)
    433 
    434     def clean(self, data, initial=None):
    435         super(FileField, self).clean(initial or data)
     455    def to_python(self, data, initial=None):
    436456        if not self.required and data in EMPTY_VALUES:
    437457            return None
    438458        elif not data and initial:
    class FileField(Field): 
    448468                category = DeprecationWarning,
    449469                stacklevel = 2
    450470            )
     471            if not data:
     472                raise ValidationError(self.error_messages['invalid'])
    451473            data = UploadedFile(data['filename'], data['content'])
    452474
    453475        try:
    class FileField(Field): 
    463485
    464486        return data
    465487
     488    def clean(self, value, initial=None):
     489        "overriden clean to provide extra argument initial"
     490        value = self.to_python(value, initial)
     491        self.validate(value)
     492        return value
     493
    466494class ImageField(FileField):
    467495    default_error_messages = {
    468496        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
    469497    }
    470498
    471     def clean(self, data, initial=None):
    472         """
    473         Checks that the file-upload field data contains a valid image (GIF, JPG,
    474         PNG, possibly others -- whatever the Python Imaging Library supports).
    475         """
    476         f = super(ImageField, self).clean(data, initial)
    477         if f is None:
    478             return None
    479         elif not data and initial:
    480             return initial
     499    def validate(self, data):
     500        super(ImageField, self).validate(data)
     501        if data is None:
     502            return
    481503        from PIL import Image
    482 
    483504        # We need to get a file object for PIL. We might have a path or we might
    484505        # have to read the data into memory.
    485506        if hasattr(data, 'temporary_file_path'):
    class ImageField(FileField): 
    487508        else:
    488509            if hasattr(data, 'read'):
    489510                file = StringIO(data.read())
     511            elif isinstance(data, UploadedFile):
     512                file = StringIO(data.data.read())
    490513            else:
    491                 file = StringIO(data['content'])
     514                file = data
    492515
    493516        try:
    494517            # load() is the only method that can spot a truncated JPEG,
    class ImageField(FileField): 
    507530            trial_image.verify()
    508531        except Exception: # Python Imaging Library doesn't recognize it as an image
    509532            raise ValidationError(self.error_messages['invalid_image'])
    510         return f
    511533
    512534url_re = re.compile(
    513535    r'^https?://' # http:// or https://
    class URLField(RegexField): 
    530552        self.verify_exists = verify_exists
    531553        self.user_agent = validator_user_agent
    532554
    533     def clean(self, value):
    534         # If no URL scheme given, assume http://
     555    def to_python(self, value):
     556        value = super(URLField, self).to_python(value)
    535557        if value and '://' not in value:
    536558            value = u'http://%s' % value
    537         value = super(URLField, self).clean(value)
     559        return value
     560
     561    def validate(self, value):
     562        # If no URL scheme given, assume http://
     563        super(URLField, self).validate(value)
    538564        if value == u'':
    539             return value
     565            return
    540566        if self.verify_exists:
    541             import urllib2
    542             headers = {
    543                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
    544                 "Accept-Language": "en-us,en;q=0.5",
    545                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
    546                 "Connection": "close",
    547                 "User-Agent": self.user_agent,
    548             }
    549             try:
    550                 req = urllib2.Request(value, None, headers)
    551                 u = urllib2.urlopen(req)
    552             except ValueError:
    553                 raise ValidationError(self.error_messages['invalid'])
    554             except: # urllib2.URLError, httplib.InvalidURL, etc.
    555                 raise ValidationError(self.error_messages['invalid_link'])
    556         return value
     567            # we cannot put this in self.validators because its conditional
     568            validators.validate_existing_url(value, self.error_messages)
    557569
    558570class BooleanField(Field):
    559571    widget = CheckboxInput
    560572
    561     def clean(self, value):
     573    def to_python(self, value):
    562574        """Returns a Python boolean object."""
    563575        # Explicitly check for the string 'False', which is what a hidden field
    564576        # will submit for False. Because bool("True") == True, we don't need to
    class BooleanField(Field): 
    567579            value = False
    568580        else:
    569581            value = bool(value)
    570         super(BooleanField, self).clean(value)
    571         if not value and self.required:
    572             raise ValidationError(self.error_messages['required'])
    573582        return value
    574583
     584    def validate(self, value):
     585        if self.required and not value:
     586            raise ValidationError(self.error_messages['required'])
     587
     588
    575589class NullBooleanField(BooleanField):
    576590    """
    577591    A field whose valid values are None, True and False. Invalid values are
    578592    cleaned to None.
     593
     594    Note that validation doesn't apply here.
    579595    """
    580596    widget = NullBooleanSelect
    581597
    582     def clean(self, value):
     598    def to_python(self, value):
    583599        return {True: True, False: False}.get(value, None)
    584600
     601    def validate(self, value):
     602        pass
     603
     604
    585605class ChoiceField(Field):
    586606    widget = Select
    587607    default_error_messages = {
    class ChoiceField(Field): 
    605625
    606626    choices = property(_get_choices, _set_choices)
    607627
    608     def clean(self, value):
    609         """
    610         Validates that the input is in self.choices.
    611         """
    612         value = super(ChoiceField, self).clean(value)
    613         if value in EMPTY_VALUES:
    614             value = u''
    615         value = smart_unicode(value)
    616         if value == u'':
    617             return value
     628    def validate(self, value):
     629        super(ChoiceField, self).validate(value)
     630        if value is None and not self.required:
     631            return u''
    618632        valid_values = set([smart_unicode(k) for k, v in self.choices])
    619633        if value not in valid_values:
    620634            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
    621         return value
    622635
    623636class MultipleChoiceField(ChoiceField):
    624637    hidden_widget = MultipleHiddenInput
    class MultipleChoiceField(ChoiceField): 
    628641        'invalid_list': _(u'Enter a list of values.'),
    629642    }
    630643
    631     def clean(self, value):
     644    def to_python(self, value):
    632645        """
    633646        Validates that the input is a list or tuple.
    634647        """
    635         if self.required and not value:
    636             raise ValidationError(self.error_messages['required'])
    637         elif not self.required and not value:
     648        if not value:
    638649            return []
    639650        if not isinstance(value, (list, tuple)):
    640             raise ValidationError(self.error_messages['invalid_list'])
    641         new_value = [smart_unicode(val) for val in value]
     651            raise TypeCoercionError(self.error_messages['invalid_list'])
     652        return [smart_unicode(val) for val in value]
     653
     654    def validate(self, value):
    642655        # Validate that each value in the value list is in self.choices.
     656        if self.required and value == []:
     657            raise ValidationError(self.error_messages['required'])
    643658        valid_values = set([smart_unicode(k) for k, v in self.choices])
    644         for val in new_value:
     659        for val in value:
    645660            if val not in valid_values:
    646661                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
    647         return new_value
    648662
    649663class ComboField(Field):
    650664    """
    class ComboField(Field): 
    659673            f.required = False
    660674        self.fields = fields
    661675
    662     def clean(self, value):
     676    def to_python(self, value):
     677        for field in self.fields:
     678            value = field.to_python(value)
     679        return value
     680
     681    def validate(self, value):
    663682        """
    664683        Validates the given value against all of self.fields, which is a
    665684        list of Field instances.
    666685        """
    667         super(ComboField, self).clean(value)
     686        super(ComboField, self).validate(value)
    668687        for field in self.fields:
    669             value = field.clean(value)
    670         return value
     688            field.validate(value)
    671689
    672690class MultiValueField(Field):
    673691    """
    class MultiValueField(Field): 
    699717            f.required = False
    700718        self.fields = fields
    701719
    702     def clean(self, value):
     720    def to_python(self, value):
    703721        """
    704722        Validates every value in the given list. A value is validated against
    705723        the corresponding Field in self.fields.
    class MultiValueField(Field): 
    713731        if not value or isinstance(value, (list, tuple)):
    714732            if not value or not [v for v in value if v not in EMPTY_VALUES]:
    715733                if self.required:
    716                     raise ValidationError(self.error_messages['required'])
     734                    return None
    717735                else:
    718736                    return self.compress([])
    719737        else:
    720             raise ValidationError(self.error_messages['invalid'])
     738            raise TypeCoercionError(self.error_messages['invalid'])
     739
    721740        for i, field in enumerate(self.fields):
    722741            try:
    723742                field_value = value[i]
    class SplitDateTimeField(MultiValueField): 
    801820            return datetime.datetime.combine(*data_list)
    802821        return None
    803822
    804 ipv4_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}$')
    805 
    806 class IPAddressField(RegexField):
     823class IPAddressField(CharField):
    807824    default_error_messages = {
    808825        'invalid': _(u'Enter a valid IPv4 address.'),
    809826    }
    810 
    811     def __init__(self, *args, **kwargs):
    812         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
     827    validators = [validators.validate_ip_address4]
  • django/newforms/forms.py

    diff --git a/django/newforms/forms.py b/django/newforms/forms.py
    index fc203f3..69d0d2a 100644
    a b from django.utils.datastructures import SortedDict 
    88from django.utils.html import escape
    99from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
    1010from django.utils.safestring import mark_safe
     11from django.core.validation import ValidationError, ErrorList, NON_FIELD_ERRORS
    1112
    1213from fields import Field, FileField
    1314from widgets import TextInput, Textarea
    14 from util import flatatt, ErrorDict, ErrorList, ValidationError
     15from util import flatatt, ErrorDict
    1516
    1617__all__ = ('BaseForm', 'Form')
    1718
    18 NON_FIELD_ERRORS = '__all__'
    1919
    2020def pretty_name(name):
    2121    "Converts 'first_name' to 'First name'"
    class BaseForm(StrAndUnicode): 
    206206                else:
    207207                    value = field.clean(value)
    208208                self.cleaned_data[name] = value
     209                # FIXME deprecated - keeping this here for backwards compatibility
    209210                if hasattr(self, 'clean_%s' % name):
    210211                    value = getattr(self, 'clean_%s' % name)()
    211212                    self.cleaned_data[name] = value
     213
     214                if hasattr(self, 'validate_%s' % name):
     215                    getattr(self, 'validate_%s' % name)(value)
    212216            except ValidationError, e:
    213217                self._errors[name] = e.messages
    214218                if name in self.cleaned_data:
    215219                    del self.cleaned_data[name]
    216220        try:
    217             self.cleaned_data = self.clean()
     221            self.validate()
    218222        except ValidationError, e:
    219             self._errors[NON_FIELD_ERRORS] = e.messages
     223            if hasattr(e, 'message_dict'):
     224                for k, v in e.message_dict.items():
     225                    self._errors.setdefault(k, []).extend(v)
     226            else:
     227                self._errors[NON_FIELD_ERRORS] = e.messages
    220228        if self._errors:
    221229            delattr(self, 'cleaned_data')
    222230
    223231    def clean(self):
    224232        """
     233        FIXME: deprecated, use validate() instead
     234
    225235        Hook for doing any extra form-wide cleaning after Field.clean() been
    226236        called on every field. Any ValidationError raised by this method will
    227237        not be associated with a particular field; it will have a special-case
    class BaseForm(StrAndUnicode): 
    229239        """
    230240        return self.cleaned_data
    231241
     242    def validate(self):
     243        self.cleaned_data = self.clean()
     244
    232245    def is_multipart(self):
    233246        """
    234247        Returns True if the form needs to be multipart-encrypted, i.e. it has
  • django/newforms/models.py

    diff --git a/django/newforms/models.py b/django/newforms/models.py
    index c3938d9..cf2a7ff 100644
    a b from django.utils.translation import ugettext_lazy as _ 
    99from django.utils.encoding import smart_unicode
    1010from django.utils.datastructures import SortedDict
    1111from django.core.exceptions import ImproperlyConfigured
     12from django.core.validation import ValidationError, ErrorList, TypeCoercionError
    1213
    13 from util import ValidationError, ErrorList
    1414from forms import BaseForm, get_declared_fields
    1515from fields import Field, ChoiceField, EMPTY_VALUES
    1616from widgets import Select, SelectMultiple, MultipleHiddenInput
    class BaseModelForm(BaseForm): 
    258258            object_data.update(initial)
    259259        BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
    260260
     261    def validate(self):
     262        super(BaseModelForm, self).validate()
     263        if self._errors:
     264            return
     265        self.instance.clean(self.cleaned_data)
     266
    261267    def save(self, commit=True):
    262268        """
    263269        Saves this ``form``'s cleaned_data into model instance
    class ModelChoiceField(ChoiceField): 
    356362
    357363    choices = property(_get_choices, ChoiceField._set_choices)
    358364
    359     def clean(self, value):
    360         Field.clean(self, value)
    361         if value in EMPTY_VALUES:
     365    def to_python(self, value):
     366        if self.required and value in EMPTY_VALUES:
     367            raise ValidationError(self.error_messages['required'])
     368        elif value in EMPTY_VALUES:
    362369            return None
    363370        try:
    364371            value = self.queryset.get(pk=value)
    class ModelChoiceField(ChoiceField): 
    366373            raise ValidationError(self.error_messages['invalid_choice'])
    367374        return value
    368375
     376    def validate(self, value):
     377        pass
     378
    369379class ModelMultipleChoiceField(ModelChoiceField):
    370380    """A MultipleChoiceField whose choices are a model QuerySet."""
    371381    hidden_widget = MultipleHiddenInput
    class ModelMultipleChoiceField(ModelChoiceField): 
    382392            cache_choices, required, widget, label, initial, help_text,
    383393            *args, **kwargs)
    384394
    385     def clean(self, value):
     395    def to_python(self, value):
    386396        if self.required and not value:
    387397            raise ValidationError(self.error_messages['required'])
    388398        elif not self.required and not value:
    389399            return []
    390400        if not isinstance(value, (list, tuple)):
    391             raise ValidationError(self.error_messages['list'])
     401            raise TypeCoercionError(self.error_messages['list'])
    392402        final_values = []
    393403        for val in value:
    394404            try:
    class ModelMultipleChoiceField(ModelChoiceField): 
    398408            else:
    399409                final_values.append(obj)
    400410        return final_values
     411
  • django/newforms/util.py

    diff --git a/django/newforms/util.py b/django/newforms/util.py
    index b3edf41..89b93d6 100644
    a b class ErrorDict(dict, StrAndUnicode): 
    3030    def as_text(self):
    3131        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()])
    3232
    33 class ErrorList(list, StrAndUnicode):
    34     """
    35     A collection of errors that knows how to display itself in various formats.
    36     """
    37     def __unicode__(self):
    38         return self.as_ul()
    39 
    40     def as_ul(self):
    41         if not self: return u''
    42         return mark_safe(u'<ul class="errorlist">%s</ul>'
    43                 % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
    44 
    45     def as_text(self):
    46         if not self: return u''
    47         return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
    48 
    49     def __repr__(self):
    50         return repr([force_unicode(e) for e in self])
    51 
    52 class ValidationError(Exception):
    53     def __init__(self, message):
    54         """
    55         ValidationError can be passed any object that can be printed (usually
    56         a string) or a list of objects.
    57         """
    58         if isinstance(message, list):
    59             self.messages = ErrorList([smart_unicode(msg) for msg in message])
    60         else:
    61             message = smart_unicode(message)
    62             self.messages = ErrorList([message])
    63 
    64     def __str__(self):
    65         # This is needed because, without a __str__(), printing an exception
    66         # instance would result in this:
    67         # AttributeError: ValidationError instance has no attribute 'args'
    68         # See http://www.python.org/doc/current/tut/node10.html#handling
    69         return repr(self.messages)
  • django/oldforms/__init__.py

    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index 2a300df..2fd510e 100644
    a b  
    1 from django.core import validators
     1from django.oldforms import validators
    22from django.core.exceptions import PermissionDenied
    33from django.utils.html import escape
    44from django.utils.safestring import mark_safe
  • new file django/oldforms/validators.py

    diff --git a/django/oldforms/validators.py b/django/oldforms/validators.py
    new file mode 100644
    index 0000000..ad50eb8
    - +  
     1"""
     2A library of validators that return None and raise ValidationError when the
     3provided data isn't valid.
     4
     5Validators may be callable classes, and they may have an 'always_test'
     6attribute. If an 'always_test' attribute exists (regardless of value), the
     7validator will *always* be run, regardless of whether its associated
     8form field is required.
     9"""
     10
     11import urllib2
     12import re
     13try:
     14    from decimal import Decimal, DecimalException
     15except ImportError:
     16    from django.utils._decimal import Decimal, DecimalException    # Python 2.3
     17
     18from django.conf import settings
     19from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
     20from django.utils.functional import Promise, lazy
     21from django.utils.encoding import force_unicode, smart_str
     22from django.core.validation import ValidationError
     23
     24_datere = r'\d{4}-\d{1,2}-\d{1,2}'
     25_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
     26alnum_re = re.compile(r'^\w+$')
     27alnumurl_re = re.compile(r'^[-\w/]+$')
     28ansi_date_re = re.compile('^%s$' % _datere)
     29ansi_time_re = re.compile('^%s$' % _timere)
     30ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
     31email_re = re.compile(
     32    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
     33    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
     34    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
     35integer_re = re.compile(r'^-?\d+$')
     36ip4_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}$')
     37phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
     38slug_re = re.compile(r'^[-\w]+$')
     39url_re = re.compile(r'^https?://\S+$')
     40
     41lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
     42
     43
     44class CriticalValidationError(Exception):
     45    def __init__(self, message):
     46        "ValidationError can be passed a string or a list."
     47        if isinstance(message, list):
     48            self.messages = [force_unicode(msg) for msg in message]
     49        else:
     50            assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
     51            self.messages = [force_unicode(message)]
     52
     53    def __str__(self):
     54        return str(self.messages)
     55
     56
     57def isAlphaNumeric(field_data, all_data):
     58    # DONE
     59    if not alnum_re.search(field_data):
     60        raise ValidationError, _("This value must contain only letters, numbers and underscores.")
     61
     62def isAlphaNumericURL(field_data, all_data):
     63    # DONE
     64    if not alnumurl_re.search(field_data):
     65        raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
     66
     67def isSlug(field_data, all_data):
     68    # DONE
     69    if not slug_re.search(field_data):
     70        raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
     71
     72def isLowerCase(field_data, all_data):
     73    # DONE
     74    if field_data.lower() != field_data:
     75        raise ValidationError, _("Uppercase letters are not allowed here.")
     76
     77def isUpperCase(field_data, all_data):
     78    # DONE
     79    if field_data.upper() != field_data:
     80        raise ValidationError, _("Lowercase letters are not allowed here.")
     81
     82def isCommaSeparatedIntegerList(field_data, all_data):
     83    # DONE
     84    for supposed_int in field_data.split(','):
     85        try:
     86            int(supposed_int)
     87        except ValueError:
     88            raise ValidationError, _("Enter only digits separated by commas.")
     89
     90def isCommaSeparatedEmailList(field_data, all_data):
     91    # DONE
     92    """
     93    Checks that field_data is a string of e-mail addresses separated by commas.
     94    Blank field_data values will not throw a validation error, and whitespace
     95    is allowed around the commas.
     96    """
     97    for supposed_email in field_data.split(','):
     98        try:
     99            isValidEmail(supposed_email.strip(), '')
     100        except ValidationError:
     101            raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
     102
     103def isValidIPAddress4(field_data, all_data):
     104    # DONE
     105    if not ip4_re.search(field_data):
     106        raise ValidationError, _("Please enter a valid IP address.")
     107
     108def isNotEmpty(field_data, all_data):
     109    # DONE
     110    if field_data.strip() == '':
     111        raise ValidationError, _("Empty values are not allowed here.")
     112
     113def isOnlyDigits(field_data, all_data):
     114    # DONE
     115    if not field_data.isdigit():
     116        raise ValidationError, _("Non-numeric characters aren't allowed here.")
     117
     118def isNotOnlyDigits(field_data, all_data):
     119    # DONE
     120    if field_data.isdigit():
     121        raise ValidationError, _("This value can't be comprised solely of digits.")
     122
     123def isInteger(field_data, all_data):
     124    # DONE
     125    # This differs from isOnlyDigits because this accepts the negative sign
     126    if not integer_re.search(field_data):
     127        raise ValidationError, _("Enter a whole number.")
     128
     129def isOnlyLetters(field_data, all_data):
     130    # DONE
     131    if not field_data.isalpha():
     132        raise ValidationError, _("Only alphabetical characters are allowed here.")
     133
     134def _isValidDate(date_string):
     135    # DONE
     136    """
     137    A helper function used by isValidANSIDate and isValidANSIDatetime to
     138    check if the date is valid.  The date string is assumed to already be in
     139    YYYY-MM-DD format.
     140    """
     141    from datetime import date
     142    # Could use time.strptime here and catch errors, but datetime.date below
     143    # produces much friendlier error messages.
     144    year, month, day = map(int, date_string.split('-'))
     145    # This check is needed because strftime is used when saving the date
     146    # value to the database, and strftime requires that the year be >=1900.
     147    if year < 1900:
     148        raise ValidationError, _('Year must be 1900 or later.')
     149    try:
     150        date(year, month, day)
     151    except ValueError, e:
     152        msg = _('Invalid date: %s') % _(str(e))
     153        raise ValidationError, msg
     154
     155def isValidANSIDate(field_data, all_data):
     156    # DONE
     157    if not ansi_date_re.search(field_data):
     158        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
     159    _isValidDate(field_data)
     160
     161def isValidANSITime(field_data, all_data):
     162    # DONE
     163    if not ansi_time_re.search(field_data):
     164        raise ValidationError, _('Enter a valid time in HH:MM format.')
     165
     166def isValidANSIDatetime(field_data, all_data):
     167    # DONE
     168    if not ansi_datetime_re.search(field_data):
     169        raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
     170    _isValidDate(field_data.split()[0])
     171
     172def isValidEmail(field_data, all_data):
     173    # DONE
     174    if not email_re.search(field_data):
     175        raise ValidationError, _('Enter a valid e-mail address.')
     176
     177def isValidImage(field_data, all_data):
     178    """
     179    Checks that the file-upload field data contains a valid image (GIF, JPG,
     180    PNG, possibly others -- whatever the Python Imaging Library supports).
     181    """
     182    from PIL import Image
     183    from cStringIO import StringIO
     184    try:
     185        content = field_data.read()
     186    except TypeError:
     187        raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
     188    try:
     189        # load() is the only method that can spot a truncated JPEG,
     190        #  but it cannot be called sanely after verify()
     191        trial_image = Image.open(StringIO(content))
     192        trial_image.load()
     193        # verify() is the only method that can spot a corrupt PNG,
     194        #  but it must be called immediately after the constructor
     195        trial_image = Image.open(StringIO(content))
     196        trial_image.verify()
     197    except Exception: # Python Imaging Library doesn't recognize it as an image
     198        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
     199
     200def isValidImageURL(field_data, all_data):
     201    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
     202    try:
     203        uc(field_data, all_data)
     204    except URLMimeTypeCheck.InvalidContentType:
     205        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
     206
     207def isValidPhone(field_data, all_data):
     208    if not phone_re.search(field_data):
     209        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
     210
     211def isValidQuicktimeVideoURL(field_data, all_data):
     212    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
     213    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
     214    try:
     215        uc(field_data, all_data)
     216    except URLMimeTypeCheck.InvalidContentType:
     217        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
     218
     219def isValidURL(field_data, all_data):
     220    if not url_re.search(field_data):
     221        raise ValidationError, _("A valid URL is required.")
     222
     223def isValidHTML(field_data, all_data):
     224    import urllib, urllib2
     225    try:
     226        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
     227    except:
     228        # Validator or Internet connection is unavailable. Fail silently.
     229        return
     230    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
     231    if html_is_valid:
     232        return
     233    from xml.dom.minidom import parseString
     234    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
     235    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
     236
     237def isWellFormedXml(field_data, all_data):
     238    from xml.dom.minidom import parseString
     239    try:
     240        parseString(field_data)
     241    except Exception, e: # Naked except because we're not sure what will be thrown
     242        raise ValidationError, _("Badly formed XML: %s") % str(e)
     243
     244def isWellFormedXmlFragment(field_data, all_data):
     245    isWellFormedXml('<root>%s</root>' % field_data, all_data)
     246
     247def isExistingURL(field_data, all_data):
     248    try:
     249        headers = {
     250            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
     251            "Accept-Language" : "en-us,en;q=0.5",
     252            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
     253            "Connection" : "close",
     254            "User-Agent": settings.URL_VALIDATOR_USER_AGENT
     255            }
     256        req = urllib2.Request(field_data,None, headers)
     257        u = urllib2.urlopen(req)
     258    except ValueError:
     259        raise ValidationError, _("Invalid URL: %s") % field_data
     260    except urllib2.HTTPError, e:
     261        # 401s are valid; they just mean authorization is required.
     262        # 301 and 302 are redirects; they just mean look somewhere else.
     263        if str(e.code) not in ('401','301','302'):
     264            raise ValidationError, _("The URL %s is a broken link.") % field_data
     265    except: # urllib2.URLError, httplib.InvalidURL, etc.
     266        raise ValidationError, _("The URL %s is a broken link.") % field_data
     267
     268def isValidUSState(field_data, all_data):
     269    "Checks that the given string is a valid two-letter U.S. state abbreviation"
     270    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']
     271    if field_data.upper() not in states:
     272        raise ValidationError, _("Enter a valid U.S. state abbreviation.")
     273
     274def hasNoProfanities(field_data, all_data):
     275    """
     276    Checks that the given string has no profanities in it. This does a simple
     277    check for whether each profanity exists within the string, so 'fuck' will
     278    catch 'motherfucker' as well. Raises a ValidationError such as:
     279        Watch your mouth! The words "f--k" and "s--t" are not allowed here.
     280    """
     281    field_data = field_data.lower() # normalize
     282    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
     283    if words_seen:
     284        from django.utils.text import get_text_list
     285        plural = len(words_seen)
     286        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
     287            "Watch your mouth! The words %s are not allowed here.", plural) % \
     288            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
     289
     290class AlwaysMatchesOtherField(object):
     291    def __init__(self, other_field_name, error_message=None):
     292        self.other = other_field_name
     293        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
     294        self.always_test = True
     295
     296    def __call__(self, field_data, all_data):
     297        if field_data != all_data[self.other]:
     298            raise ValidationError, self.error_message
     299
     300class ValidateIfOtherFieldEquals(object):
     301    def __init__(self, other_field, other_value, validator_list):
     302        self.other_field, self.other_value = other_field, other_value
     303        self.validator_list = validator_list
     304        self.always_test = True
     305
     306    def __call__(self, field_data, all_data):
     307        if self.other_field in all_data and all_data[self.other_field] == self.other_value:
     308            for v in self.validator_list:
     309                v(field_data, all_data)
     310
     311class RequiredIfOtherFieldNotGiven(object):
     312    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
     313        self.other, self.error_message = other_field_name, error_message
     314        self.always_test = True
     315
     316    def __call__(self, field_data, all_data):
     317        if not all_data.get(self.other, False) and not field_data:
     318            raise ValidationError, self.error_message
     319
     320class RequiredIfOtherFieldsGiven(object):
     321    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
     322        self.other, self.error_message = other_field_names, error_message
     323        self.always_test = True
     324
     325    def __call__(self, field_data, all_data):
     326        for field in self.other:
     327            if all_data.get(field, False) and not field_data:
     328                raise ValidationError, self.error_message
     329
     330class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
     331    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
     332    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
     333        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
     334
     335class RequiredIfOtherFieldEquals(object):
     336    def __init__(self, other_field, other_value, error_message=None, other_label=None):
     337        self.other_field = other_field
     338        self.other_value = other_value
     339        other_label = other_label or other_value
     340        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
     341            'field': other_field, 'value': other_label})
     342        self.always_test = True
     343
     344    def __call__(self, field_data, all_data):
     345        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
     346            raise ValidationError(self.error_message)
     347
     348class RequiredIfOtherFieldDoesNotEqual(object):
     349    def __init__(self, other_field, other_value, other_label=None, error_message=None):
     350        self.other_field = other_field
     351        self.other_value = other_value
     352        other_label = other_label or other_value
     353        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
     354            'field': other_field, 'value': other_label})
     355        self.always_test = True
     356
     357    def __call__(self, field_data, all_data):
     358        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
     359            raise ValidationError(self.error_message)
     360
     361class IsLessThanOtherField(object):
     362    def __init__(self, other_field_name, error_message):
     363        self.other, self.error_message = other_field_name, error_message
     364
     365    def __call__(self, field_data, all_data):
     366        if field_data > all_data[self.other]:
     367            raise ValidationError, self.error_message
     368
     369class UniqueAmongstFieldsWithPrefix(object):
     370    def __init__(self, field_name, prefix, error_message):
     371        self.field_name, self.prefix = field_name, prefix
     372        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
     373
     374    def __call__(self, field_data, all_data):
     375        for field_name, value in all_data.items():
     376            if field_name != self.field_name and value == field_data:
     377                raise ValidationError, self.error_message
     378
     379class NumberIsInRange(object):
     380    """
     381    Validator that tests if a value is in a range (inclusive).
     382    """
     383    def __init__(self, lower=None, upper=None, error_message=''):
     384        self.lower, self.upper = lower, upper
     385        if not error_message:
     386            if lower and upper:
     387                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
     388            elif lower:
     389                self.error_message = _("This value must be at least %s.") % lower
     390            elif upper:
     391                self.error_message = _("This value must be no more than %s.") % upper
     392        else:
     393            self.error_message = error_message
     394
     395    def __call__(self, field_data, all_data):
     396        # Try to make the value numeric. If this fails, we assume another
     397        # validator will catch the problem.
     398        try:
     399            val = float(field_data)
     400        except ValueError:
     401            return
     402
     403        # Now validate
     404        if self.lower and self.upper and (val < self.lower or val > self.upper):
     405            raise ValidationError(self.error_message)
     406        elif self.lower and val < self.lower:
     407            raise ValidationError(self.error_message)
     408        elif self.upper and val > self.upper:
     409            raise ValidationError(self.error_message)
     410
     411class IsAPowerOf(object):
     412    """
     413    Usage: If you create an instance of the IsPowerOf validator:
     414        v = IsAPowerOf(2)
     415   
     416    The following calls will succeed:
     417        v(4, None)
     418        v(8, None)
     419        v(16, None)
     420   
     421    But this call:
     422        v(17, None)
     423    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
     424    """
     425    def __init__(self, power_of):
     426        self.power_of = power_of
     427
     428    def __call__(self, field_data, all_data):
     429        from math import log
     430        val = log(int(field_data)) / log(self.power_of)
     431        if val != int(val):
     432            raise ValidationError, _("This value must be a power of %s.") % self.power_of
     433
     434class IsValidDecimal(object):
     435    def __init__(self, max_digits, decimal_places):
     436        self.max_digits, self.decimal_places = max_digits, decimal_places
     437
     438    def __call__(self, field_data, all_data):
     439        try:
     440            val = Decimal(field_data)
     441        except DecimalException:
     442            raise ValidationError, _("Please enter a valid decimal number.")
     443
     444        pieces = str(val).lstrip("-").split('.')
     445        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
     446        digits = len(pieces[0])
     447
     448        if digits + decimals > self.max_digits:
     449            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
     450                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
     451        if digits > (self.max_digits - self.decimal_places):
     452            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
     453                "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)
     454        if decimals > self.decimal_places:
     455            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
     456                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
     457
     458def isValidFloat(field_data, all_data):
     459    data = smart_str(field_data)
     460    try:
     461        float(data)
     462    except ValueError:
     463        raise ValidationError, _("Please enter a valid floating point number.")
     464
     465class HasAllowableSize(object):
     466    """
     467    Checks that the file-upload field data is a certain size. min_size and
     468    max_size are measurements in bytes.
     469    """
     470    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
     471        self.min_size, self.max_size = min_size, max_size
     472        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)
     473        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)
     474
     475    def __call__(self, field_data, all_data):
     476        try:
     477            content = field_data.read()
     478        except TypeError:
     479            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
     480        if self.min_size is not None and len(content) < self.min_size:
     481            raise ValidationError, self.min_error_message
     482        if self.max_size is not None and len(content) > self.max_size:
     483            raise ValidationError, self.max_error_message
     484
     485class MatchesRegularExpression(object):
     486    """
     487    Checks that the field matches the given regular-expression. The regex
     488    should be in string format, not already compiled.
     489    """
     490    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
     491        self.regexp = re.compile(regexp)
     492        self.error_message = error_message
     493
     494    def __call__(self, field_data, all_data):
     495        if not self.regexp.search(field_data):
     496            raise ValidationError(self.error_message)
     497
     498class AnyValidator(object):
     499    """
     500    This validator tries all given validators. If any one of them succeeds,
     501    validation passes. If none of them succeeds, the given message is thrown
     502    as a validation error. The message is rather unspecific, so it's best to
     503    specify one on instantiation.
     504    """
     505    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
     506        if validator_list is None: validator_list = []
     507        self.validator_list = validator_list
     508        self.error_message = error_message
     509        for v in validator_list:
     510            if hasattr(v, 'always_test'):
     511                self.always_test = True
     512
     513    def __call__(self, field_data, all_data):
     514        for v in self.validator_list:
     515            try:
     516                v(field_data, all_data)
     517                return
     518            except ValidationError, e:
     519                pass
     520        raise ValidationError(self.error_message)
     521
     522class URLMimeTypeCheck(object):
     523    "Checks that the provided URL points to a document with a listed mime type"
     524    class CouldNotRetrieve(ValidationError):
     525        pass
     526    class InvalidContentType(ValidationError):
     527        pass
     528
     529    def __init__(self, mime_type_list):
     530        self.mime_type_list = mime_type_list
     531
     532    def __call__(self, field_data, all_data):
     533        import urllib2
     534        try:
     535            isValidURL(field_data, all_data)
     536        except ValidationError:
     537            raise
     538        try:
     539            info = urllib2.urlopen(field_data).info()
     540        except (urllib2.HTTPError, urllib2.URLError):
     541            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
     542        content_type = info['content-type']
     543        if content_type not in self.mime_type_list:
     544            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
     545                'url': field_data, 'contenttype': content_type}
     546
     547class RelaxNGCompact(object):
     548    "Validate against a Relax NG compact schema"
     549    def __init__(self, schema_path, additional_root_element=None):
     550        self.schema_path = schema_path
     551        self.additional_root_element = additional_root_element
     552
     553    def __call__(self, field_data, all_data):
     554        import os, tempfile
     555        if self.additional_root_element:
     556            field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
     557                'are': self.additional_root_element,
     558                'data': field_data
     559            }
     560        filename = tempfile.mktemp() # Insecure, but nothing else worked
     561        fp = open(filename, 'w')
     562        fp.write(field_data)
     563        fp.close()
     564        if not os.path.exists(settings.JING_PATH):
     565            raise Exception, "%s not found!" % settings.JING_PATH
     566        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
     567        errors = [line.strip() for line in p.readlines()]
     568        p.close()
     569        os.unlink(filename)
     570        display_errors = []
     571        lines = field_data.split('\n')
     572        for error in errors:
     573            ignored, line, level, message = error.split(':', 3)
     574            # Scrape the Jing error messages to reword them more nicely.
     575            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
     576            if m:
     577                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
     578                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
     579                continue
     580            if message.strip() == 'text not allowed here':
     581                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
     582                    {'line':line, 'start':lines[int(line) - 1][:30]})
     583                continue
     584            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
     585            if m:
     586                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
     587                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
     588                continue
     589            m = re.search(r'\s*unknown element "(.*?)"', message)
     590            if m:
     591                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
     592                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
     593                continue
     594            if message.strip() == 'required attributes missing':
     595                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
     596                    {'line':line, 'start':lines[int(line) - 1][:30]})
     597                continue
     598            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
     599            if m:
     600                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
     601                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
     602                continue
     603            # Failing all those checks, use the default error message.
     604            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
     605            display_errors.append(display_error)
     606        if len(display_errors) > 0:
     607            raise ValidationError, display_errors
  • tests/modeltests/manipulators/models.py

    diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
    index 3e52e33..64cfbd3 100644
    a b True 
    9999datetime.date(2005, 2, 13)
    100100
    101101# Test isValidFloat Unicode coercion
    102 >>> from django.core.validators import isValidFloat, ValidationError
     102>>> from django.oldforms.validators import isValidFloat, ValidationError
    103103>>> try: isValidFloat(u"ä", None)
    104104... except ValidationError: pass
    105105"""}
  • tests/modeltests/model_forms/models.py

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index f2b0e05..9da25de 100644
    a b Create a new article, with categories, via the form. 
    485485...         model = Article
    486486>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
    487487...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
     488>>> f.is_valid()
     489True
    488490>>> new_art = f.save()
    489491>>> new_art.id
    4904922
    ValidationError: [u'Select a valid choice. 100 is not one of the available choic 
    712714>>> f.clean('hello')
    713715Traceback (most recent call last):
    714716...
    715 ValidationError: [u'Enter a list of values.']
     717TypeCoercionError: [u'Enter a list of values.']
    716718
    717719# Add a Category object *after* the ModelMultipleChoiceField has already been
    718720# instantiated. This proves clean() checks the database during clean() rather
    u'...test2.txt' 
    859861>>> instance.delete()
    860862
    861863# Test the non-required FileField
    862 
     864# It should fail since the field IS required on the model
    863865>>> f = TextFileForm(data={'description': u'Assistance'})
    864866>>> f.fields['file'].required = False
    865867>>> f.is_valid()
    866 True
    867 >>> instance = f.save()
    868 >>> instance.file
    869 ''
     868False
     869>>> f.errors
     870{'file': [u'This field is required.']}
    870871
    871872>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
    872873>>> f.is_valid()
    u'...test2.png' 
    968969>>> f = ImageFileForm(data={'description': u'Test'})
    969970>>> f.fields['image'].required = False
    970971>>> f.is_valid()
    971 True
    972 >>> instance = f.save()
    973 >>> instance.image
    974 ''
     972False
     973>>> f.errors
     974{'image': [u'This field is required.']}
     975
    975976
    976977>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
    977978>>> f.is_valid()
  • tests/modeltests/validation/models.py

    diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
    index 63f9f7a..379647a 100644
    a b  
    33
    44This is an experimental feature!
    55
    6 Each model instance has a validate() method that returns a dictionary of
     6Each model instance has a clean() method that returns a dictionary of
    77validation errors in the instance's fields. This method has a side effect
    88of converting each field to its appropriate Python data type.
    99"""
    class Person(models.Model): 
    1515    name = models.CharField(max_length=20)
    1616    birthdate = models.DateField()
    1717    favorite_moment = models.DateTimeField()
    18     email = models.EmailField()
    19 
     18    email = models.EmailField(unique=True)
     19   
     20    class Meta:
     21        unique_together = (('name', 'is_child'),)
    2022    def __unicode__(self):
    2123        return self.name
    2224
    __test__ = {'API_TESTS':""" 
    3032...     'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
    3133...     'email': 'john@example.com'
    3234... }
    33 >>> p = Person(**valid_params)
    34 >>> p.validate()
    35 {}
    36 
    37 >>> p = Person(**dict(valid_params, id='23'))
    38 >>> p.validate()
    39 {}
     35>>> p = Person(**dict(valid_params, email='john@e.com', name='Jack'))
     36>>> p.clean()
     37>>> p.save()
     38
     39>>> p = Person(**dict(valid_params, email='john@e.com'))
     40>>> p.clean()
     41Traceback (most recent call last):
     42...
     43ValidationError: {'email': [u'This field must be unique']}
     44
     45>>> p = Person(**dict(valid_params, id='23', name='Jack'))
     46>>> p.clean()
     47Traceback (most recent call last):
     48...
     49ValidationError: {'__all__': u'Fields name, is_child must be unique.'}
    4050>>> p.id
    415123
    4252
    43 >>> p = Person(**dict(valid_params, id='foo'))
    44 >>> p.validate()['id']
    45 [u'This value must be an integer.']
     53# when type coercion fails, no other validation is done
     54>>> p = Person(**dict(valid_params, email='john@e.com', id='foo'))
     55>>> p.clean()
     56Traceback (most recent call last):
     57...
     58ValidationError: {'id': [u'This value must be an integer.']}
    4659
    4760>>> p = Person(**dict(valid_params, id=None))
    48 >>> p.validate()
    49 {}
     61>>> p.clean()
    5062>>> repr(p.id)
    5163'None'
    5264
    5365>>> p = Person(**dict(valid_params, is_child='t'))
    54 >>> p.validate()
    55 {}
     66>>> p.clean()
    5667>>> p.is_child
    5768True
    5869
    5970>>> p = Person(**dict(valid_params, is_child='f'))
    60 >>> p.validate()
    61 {}
     71>>> p.clean()
    6272>>> p.is_child
    6373False
    6474
    6575>>> p = Person(**dict(valid_params, is_child=True))
    66 >>> p.validate()
    67 {}
     76>>> p.clean()
    6877>>> p.is_child
    6978True
    7079
    7180>>> p = Person(**dict(valid_params, is_child=False))
    72 >>> p.validate()
    73 {}
     81>>> p.clean()
    7482>>> p.is_child
    7583False
    7684
    7785>>> p = Person(**dict(valid_params, is_child='foo'))
    78 >>> p.validate()['is_child']
    79 [u'This value must be either True or False.']
     86>>> p.clean()
     87Traceback (most recent call last):
     88...
     89ValidationError: {'is_child': [u'This value must be either True or False.']}
    8090
    8191>>> p = Person(**dict(valid_params, name=u'Jose'))
    82 >>> p.validate()
    83 {}
     92>>> p.clean()
    8493>>> p.name
    8594u'Jose'
    8695
    8796>>> p = Person(**dict(valid_params, name=227))
    88 >>> p.validate()
    89 {}
     97>>> p.clean()
    9098>>> p.name
    9199u'227'
    92100
    93101>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
    94 >>> p.validate()
    95 {}
     102>>> p.clean()
    96103>>> p.birthdate
    97104datetime.date(2000, 5, 3)
    98105
    99106>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
    100 >>> p.validate()
    101 {}
     107>>> p.clean()
    102108>>> p.birthdate
    103109datetime.date(2000, 5, 3)
    104110
    105111>>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
    106 >>> p.validate()
    107 {}
     112>>> p.clean()
    108113>>> p.birthdate
    109114datetime.date(2000, 5, 3)
    110115
    111116>>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
    112 >>> p.validate()
    113 {}
     117>>> p.clean()
    114118>>> p.birthdate
    115119datetime.date(2000, 5, 3)
    116120
    117121>>> p = Person(**dict(valid_params, birthdate='foo'))
    118 >>> p.validate()['birthdate']
    119 [u'Enter a valid date in YYYY-MM-DD format.']
     122>>> p.clean()
     123Traceback (most recent call last):
     124...
     125ValidationError: {'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
    120126
    121127>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
    122 >>> p.validate()
    123 {}
     128>>> p.clean()
    124129>>> p.favorite_moment
    125130datetime.datetime(2002, 4, 3, 13, 23)
    126131
    127132>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
    128 >>> p.validate()
    129 {}
     133>>> p.clean()
    130134>>> p.favorite_moment
    131135datetime.datetime(2002, 4, 3, 0, 0)
    132136
    133137>>> p = Person(**dict(valid_params, email='john@example.com'))
    134 >>> p.validate()
    135 {}
     138>>> p.clean()
    136139>>> p.email
    137140'john@example.com'
    138141
    139142>>> p = Person(**dict(valid_params, email=u'john@example.com'))
    140 >>> p.validate()
    141 {}
     143>>> p.clean()
    142144>>> p.email
    143145u'john@example.com'
    144146
    145147>>> p = Person(**dict(valid_params, email=22))
    146 >>> p.validate()['email']
    147 [u'Enter a valid e-mail address.']
     148>>> p.clean()
     149Traceback (most recent call last):
     150...
     151ValidationError: {'email': [u'Enter a valid e-mail address.']}
    148152
    149153# Make sure that Date and DateTime return validation errors and don't raise Python errors.
    150 >>> p = Person(name='John Doe', is_child=True, email='abc@def.com')
    151 >>> errors = p.validate()
    152 >>> errors['favorite_moment']
     154>>> from django.core.validation import ValidationError
     155>>> try:
     156...     Person(name='John Doe', is_child=True, email='abc@def.com').clean()
     157... except ValidationError, e:
     158...     e.message_dict['favorite_moment']
     159...     e.message_dict['birthdate']
    153160[u'This field is required.']
    154 >>> errors['birthdate']
    155161[u'This field is required.']
    156162
    157163"""}
  • new file tests/regressiontests/core/models.py

    diff --git a/tests/regressiontests/core/__init__.py b/tests/regressiontests/core/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/core/models.py b/tests/regressiontests/core/models.py
    new file mode 100644
    index 0000000..e5a7950
    - +  
     1# A models.py so that tests run.
     2
  • new file tests/regressiontests/core/tests.py

    diff --git a/tests/regressiontests/core/tests.py b/tests/regressiontests/core/tests.py
    new file mode 100644
    index 0000000..adc72df
    - +  
     1tests = r"""
     2###################
     3# ValidationError #
     4###################
     5>>> from django.core.exceptions import ValidationError
     6>>> from django.utils.translation import ugettext_lazy
     7
     8# Can take a string.
     9>>> print ValidationError("There was an error.").messages
     10<ul class="errorlist"><li>There was an error.</li></ul>
     11
     12# Can take a unicode string.
     13>>> print ValidationError(u"Not \u03C0.").messages
     14<ul class="errorlist"><li>Not π.</li></ul>
     15
     16# Can take a lazy string.
     17>>> print ValidationError(ugettext_lazy("Error.")).messages
     18<ul class="errorlist"><li>Error.</li></ul>
     19
     20# Can take a list.
     21>>> print ValidationError(["Error one.", "Error two."]).messages
     22<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
     23
     24# Can take a mixture in a list.
     25>>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
     26<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
     27
     28>>> class VeryBadError:
     29...     def __unicode__(self): return u"A very bad error."
     30
     31# Can take a non-string.
     32>>> print ValidationError(VeryBadError()).messages
     33<ul class="errorlist"><li>A very bad error.</li></ul>
     34"""
     35
  • tests/regressiontests/forms/error_messages.py

    diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
    index 580326f..2a2c32a 100644
    a b ValidationError: [u'REQUIRED'] 
    3636>>> f.clean('abc')
    3737Traceback (most recent call last):
    3838...
    39 ValidationError: [u'INVALID']
     39TypeCoercionError: [u'INVALID']
    4040>>> f.clean('4')
    4141Traceback (most recent call last):
    4242...
    ValidationError: [u'REQUIRED'] 
    6060>>> f.clean('abc')
    6161Traceback (most recent call last):
    6262...
    63 ValidationError: [u'INVALID']
     63TypeCoercionError: [u'INVALID']
    6464>>> f.clean('4')
    6565Traceback (most recent call last):
    6666...
    ValidationError: [u'REQUIRED'] 
    8888>>> f.clean('abc')
    8989Traceback (most recent call last):
    9090...
    91 ValidationError: [u'INVALID']
     91TypeCoercionError: [u'INVALID']
    9292>>> f.clean('4')
    9393Traceback (most recent call last):
    9494...
    ValidationError: [u'REQUIRED'] 
    122122>>> f.clean('abc')
    123123Traceback (most recent call last):
    124124...
    125 ValidationError: [u'INVALID']
     125TypeCoercionError: [u'INVALID']
    126126
    127127# TimeField ###################################################################
    128128
    ValidationError: [u'REQUIRED'] 
    136136>>> f.clean('abc')
    137137Traceback (most recent call last):
    138138...
    139 ValidationError: [u'INVALID']
     139TypeCoercionError: [u'INVALID']
    140140
    141141# DateTimeField ###############################################################
    142142
    ValidationError: [u'REQUIRED'] 
    150150>>> f.clean('abc')
    151151Traceback (most recent call last):
    152152...
    153 ValidationError: [u'INVALID']
     153TypeCoercionError: [u'INVALID']
    154154
    155155# RegexField ##################################################################
    156156
    ValidationError: [u'LENGTH 11, MAX LENGTH 10'] 
    204204
    205205>>> e = {'required': 'REQUIRED'}
    206206>>> e['invalid'] = 'INVALID'
    207 >>> e['missing'] = 'MISSING'
    208207>>> e['empty'] = 'EMPTY FILE'
    209208>>> f = FileField(error_messages=e)
    210209>>> f.clean('')
    211210Traceback (most recent call last):
    212211...
    213 ValidationError: [u'REQUIRED']
     212ValidationError: [u'INVALID']
    214213>>> f.clean('abc')
    215214Traceback (most recent call last):
    216215...
    217216ValidationError: [u'INVALID']
    218 >>> f.clean(SimpleUploadedFile('name', None))
     217>>> f.clean({})
    219218Traceback (most recent call last):
    220219...
    221 ValidationError: [u'EMPTY FILE']
    222 >>> f.clean(SimpleUploadedFile('name', ''))
     220ValidationError: [u'INVALID']
     221>>> f.clean({'filename': 'name', 'content':''})
    223222Traceback (most recent call last):
    224223...
    225224ValidationError: [u'EMPTY FILE']
    ValidationError: [u'REQUIRED'] 
    279278>>> f.clean('b')
    280279Traceback (most recent call last):
    281280...
    282 ValidationError: [u'NOT A LIST']
     281TypeCoercionError: [u'NOT A LIST']
    283282>>> f.clean(['b'])
    284283Traceback (most recent call last):
    285284...
    ValidationError: [u'REQUIRED'] 
    353352>>> f.clean('3')
    354353Traceback (most recent call last):
    355354...
    356 ValidationError: [u'NOT A LIST OF VALUES']
     355TypeCoercionError: [u'NOT A LIST OF VALUES']
    357356>>> f.clean(['4'])
    358357Traceback (most recent call last):
    359358...
  • tests/regressiontests/forms/extra.py

    diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py
    index a8b3697..7e381db 100644
    a b u'sirrobin' 
    424424# Test overriding ErrorList in a form #
    425425#######################################
    426426
    427 >>> from django.newforms.util import ErrorList
     427>>> from django.core.validation import ErrorList
    428428>>> class DivErrorList(ErrorList):
    429429...     def __unicode__(self):
    430430...         return self.as_divs()
  • tests/regressiontests/forms/fields.py

    diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
    index f266e7b..179a750 100644
    a b Each Field's __init__() takes at least these parameters: 
    3232             field name, if the Field is part of a Form.
    3333    initial -- A value to use in this Field's initial display. This value is
    3434               *not* used as a fallback if data isn't given.
     35    validators -- Optional list of additional validator functions
    3536
    3637Other than that, the Field subclasses have class-specific options for
    3738__init__(). For example, CharField has a max_length option.
    u'1234567890' 
    104105>>> f.clean('1234567890a')
    105106u'1234567890a'
    106107
     108# Custom validator functions ##################################################
     109
     110>>> def validator(value, error_dict={}): raise ValidationError('validator failed')
     111>>> f = CharField(min_length=10, validators=[validator])
     112>>> f.clean('aa')
     113Traceback (most recent call last):
     114...
     115ValidationError: [u'validator failed']
     116
     117>>> def validator2(value, error_dict={}): raise ValidationError('validator2 failed')
     118>>> f = CharField(min_length=10, validators=[validator, validator, validator2])
     119>>> f.clean('aa')
     120Traceback (most recent call last):
     121...
     122ValidationError: [u'validator failed', u'validator failed', u'validator2 failed']
     123
     124>>> class MyCharField(CharField):
     125...     validators = [validator]
     126>>> f = MyCharField()
     127>>> f.clean('aa')
     128Traceback (most recent call last):
     129...
     130ValidationError: [u'validator failed']
     131
    107132# IntegerField ################################################################
    108133
    109134>>> f = IntegerField()
    True 
    124149>>> f.clean('a')
    125150Traceback (most recent call last):
    126151...
    127 ValidationError: [u'Enter a whole number.']
     152TypeCoercionError: [u'Enter a whole number.']
    128153>>> f.clean(42)
    12915442
    130155>>> f.clean(3.14)
    131156Traceback (most recent call last):
    132157...
    133 ValidationError: [u'Enter a whole number.']
     158TypeCoercionError: [u'Enter a whole number.']
    134159>>> f.clean('1 ')
    1351601
    136161>>> f.clean(' 1')
    ValidationError: [u'Enter a whole number.'] 
    140165>>> f.clean('1a')
    141166Traceback (most recent call last):
    142167...
    143 ValidationError: [u'Enter a whole number.']
     168TypeCoercionError: [u'Enter a whole number.']
    144169
    145170>>> f = IntegerField(required=False)
    146171>>> f.clean('')
    True 
    158183>>> f.clean('a')
    159184Traceback (most recent call last):
    160185...
    161 ValidationError: [u'Enter a whole number.']
     186TypeCoercionError: [u'Enter a whole number.']
    162187>>> f.clean('1 ')
    1631881
    164189>>> f.clean(' 1')
    ValidationError: [u'Enter a whole number.'] 
    168193>>> f.clean('1a')
    169194Traceback (most recent call last):
    170195...
    171 ValidationError: [u'Enter a whole number.']
     196TypeCoercionError: [u'Enter a whole number.']
    172197
    173198IntegerField accepts an optional max_value parameter:
    174199>>> f = IntegerField(max_value=10)
    True 
    261286>>> f.clean('a')
    262287Traceback (most recent call last):
    263288...
    264 ValidationError: [u'Enter a number.']
     289TypeCoercionError: [u'Enter a number.']
    265290>>> f.clean('1.0 ')
    2662911.0
    267292>>> f.clean(' 1.0')
    ValidationError: [u'Enter a number.'] 
    271296>>> f.clean('1.0a')
    272297Traceback (most recent call last):
    273298...
    274 ValidationError: [u'Enter a number.']
     299TypeCoercionError: [u'Enter a number.']
    275300
    276301>>> f = FloatField(required=False)
    277302>>> f.clean('')
    Decimal("3.14") 
    323348>>> f.clean('a')
    324349Traceback (most recent call last):
    325350...
    326 ValidationError: [u'Enter a number.']
     351TypeCoercionError: [u'Enter a number.']
    327352>>> f.clean(u'łąść')
    328353Traceback (most recent call last):
    329354...
    330 ValidationError: [u'Enter a number.']
     355TypeCoercionError: [u'Enter a number.']
    331356>>> f.clean('1.0 ')
    332357Decimal("1.0")
    333358>>> f.clean(' 1.0')
    Decimal("1.0") 
    337362>>> f.clean('1.0a')
    338363Traceback (most recent call last):
    339364...
    340 ValidationError: [u'Enter a number.']
     365TypeCoercionError: [u'Enter a number.']
    341366>>> f.clean('123.45')
    342367Traceback (most recent call last):
    343368...
    ValidationError: [u'Ensure that there are no more than 4 digits in total.'] 
    373398>>> f.clean('--0.12')
    374399Traceback (most recent call last):
    375400...
    376 ValidationError: [u'Enter a number.']
     401TypeCoercionError: [u'Enter a number.']
    377402
    378403>>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
    379404>>> f.clean('')
    datetime.date(2006, 10, 25) 
    434459>>> f.clean('2006-4-31')
    435460Traceback (most recent call last):
    436461...
    437 ValidationError: [u'Enter a valid date.']
     462TypeCoercionError: [u'Enter a valid date.']
    438463>>> f.clean('200a-10-25')
    439464Traceback (most recent call last):
    440465...
    441 ValidationError: [u'Enter a valid date.']
     466TypeCoercionError: [u'Enter a valid date.']
    442467>>> f.clean('25/10/06')
    443468Traceback (most recent call last):
    444469...
    445 ValidationError: [u'Enter a valid date.']
     470TypeCoercionError: [u'Enter a valid date.']
    446471>>> f.clean(None)
    447472Traceback (most recent call last):
    448473...
    so the default formats won't work unless you specify them: 
    470495>>> f.clean('2006-10-25')
    471496Traceback (most recent call last):
    472497...
    473 ValidationError: [u'Enter a valid date.']
     498TypeCoercionError: [u'Enter a valid date.']
    474499>>> f.clean('10/25/2006')
    475500Traceback (most recent call last):
    476501...
    477 ValidationError: [u'Enter a valid date.']
     502TypeCoercionError: [u'Enter a valid date.']
    478503>>> f.clean('10/25/06')
    479504Traceback (most recent call last):
    480505...
    481 ValidationError: [u'Enter a valid date.']
     506TypeCoercionError: [u'Enter a valid date.']
    482507
    483508# TimeField ###################################################################
    484509
    datetime.time(14, 25, 59) 
    495520>>> f.clean('hello')
    496521Traceback (most recent call last):
    497522...
    498 ValidationError: [u'Enter a valid time.']
     523TypeCoercionError: [u'Enter a valid time.']
    499524>>> f.clean('1:24 p.m.')
    500525Traceback (most recent call last):
    501526...
    502 ValidationError: [u'Enter a valid time.']
     527TypeCoercionError: [u'Enter a valid time.']
    503528
    504529TimeField accepts an optional input_formats parameter:
    505530>>> f = TimeField(input_formats=['%I:%M %p'])
    so the default formats won't work unless you specify them: 
    517542>>> f.clean('14:30:45')
    518543Traceback (most recent call last):
    519544...
    520 ValidationError: [u'Enter a valid time.']
     545TypeCoercionError: [u'Enter a valid time.']
    521546
    522547# DateTimeField ###############################################################
    523548
    datetime.datetime(2006, 10, 25, 0, 0) 
    558583>>> f.clean('hello')
    559584Traceback (most recent call last):
    560585...
    561 ValidationError: [u'Enter a valid date/time.']
     586TypeCoercionError: [u'Enter a valid date/time.']
    562587>>> f.clean('2006-10-25 4:30 p.m.')
    563588Traceback (most recent call last):
    564589...
    565 ValidationError: [u'Enter a valid date/time.']
     590TypeCoercionError: [u'Enter a valid date/time.']
    566591
    567592DateField accepts an optional input_formats parameter:
    568593>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
    so the default formats won't work unless you specify them: 
    582607>>> f.clean('2006-10-25 14:30:45')
    583608Traceback (most recent call last):
    584609...
    585 ValidationError: [u'Enter a valid date/time.']
     610TypeCoercionError: [u'Enter a valid date/time.']
    586611
    587612>>> f = DateTimeField(required=False)
    588613>>> f.clean(None)
    ValidationError: [u'Ensure this value has at most 15 characters (it has 20).'] 
    748773>>> f.clean('')
    749774Traceback (most recent call last):
    750775...
    751 ValidationError: [u'This field is required.']
     776ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    752777
    753778>>> f.clean('', '')
    754779Traceback (most recent call last):
    755780...
    756 ValidationError: [u'This field is required.']
     781ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    757782
    758783>>> f.clean('', 'files/test1.pdf')
    759784'files/test1.pdf'
    ValidationError: [u'This field is required.'] 
    761786>>> f.clean(None)
    762787Traceback (most recent call last):
    763788...
    764 ValidationError: [u'This field is required.']
     789ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    765790
    766791>>> f.clean(None, '')
    767792Traceback (most recent call last):
    768793...
    769 ValidationError: [u'This field is required.']
     794ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    770795
    771796>>> f.clean(None, 'files/test2.pdf')
    772797'files/test2.pdf'
    ValidationError: [u'Select a valid choice. That choice is not one of the availab 
    10001025
    10011026>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
    10021027>>> f.clean('')
    1003 u''
    10041028>>> f.clean(None)
    1005 u''
    10061029>>> f.clean(1)
    10071030u'1'
    10081031>>> f.clean('1')
    ValidationError: [u'This field is required.'] 
    10581081>>> f.clean('hello')
    10591082Traceback (most recent call last):
    10601083...
    1061 ValidationError: [u'Enter a list of values.']
     1084TypeCoercionError: [u'Enter a list of values.']
    10621085>>> f.clean([])
    10631086Traceback (most recent call last):
    10641087...
    ValidationError: [u'Select a valid choice. 3 is not one of the available choices 
    10901113>>> f.clean('hello')
    10911114Traceback (most recent call last):
    10921115...
    1093 ValidationError: [u'Enter a list of values.']
     1116TypeCoercionError: [u'Enter a list of values.']
    10941117>>> f.clean([])
    10951118[]
    10961119>>> f.clean(())
    ValidationError: [u'This field is required.'] 
    11931216>>> f.clean('hello')
    11941217Traceback (most recent call last):
    11951218...
    1196 ValidationError: [u'Enter a list of values.']
     1219TypeCoercionError: [u'Enter a list of values.']
    11971220>>> f.clean(['hello', 'there'])
    11981221Traceback (most recent call last):
    11991222...
    datetime.datetime(2006, 1, 10, 7, 30) 
    12191242>>> f.clean('hello')
    12201243Traceback (most recent call last):
    12211244...
    1222 ValidationError: [u'Enter a list of values.']
     1245TypeCoercionError: [u'Enter a list of values.']
    12231246>>> f.clean(['hello', 'there'])
    12241247Traceback (most recent call last):
    12251248...
  • tests/regressiontests/forms/forms.py

    diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
    index 041fa40..b70b71a 100644
    a b not request.POST. 
    14641464
    14651465>>> f = FileForm(data={}, files={}, auto_id=False)
    14661466>>> print f
    1467 <tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
     1467<tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>
    14681468
    14691469>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
    14701470>>> print f
  • tests/regressiontests/forms/localflavor/br.py

    diff --git a/tests/regressiontests/forms/localflavor/br.py b/tests/regressiontests/forms/localflavor/br.py
    index 757f382..94285c3 100644
    a b Traceback (most recent call last): 
    8585...
    8686ValidationError: [u'Invalid CNPJ number.']
    8787>>> f.clean('64.132.916/0001-88')
    88 '64.132.916/0001-88'
     88u'64.132.916/0001-88'
    8989>>> f.clean('64-132-916/0001-88')
    90 '64-132-916/0001-88'
     90u'64-132-916/0001-88'
    9191>>> f.clean('64132916/0001-88')
    92 '64132916/0001-88'
     92u'64132916/0001-88'
    9393>>> f.clean('64.132.916/0001-XX')
    9494Traceback (most recent call last):
    9595...
  • tests/regressiontests/forms/localflavor/generic.py

    diff --git a/tests/regressiontests/forms/localflavor/generic.py b/tests/regressiontests/forms/localflavor/generic.py
    index 0dbe30d..cda4f89 100644
    a b datetime.date(2006, 10, 25) 
    3838>>> f.clean('2006-4-31')
    3939Traceback (most recent call last):
    4040...
    41 ValidationError: [u'Enter a valid date.']
     41TypeCoercionError: [u'Enter a valid date.']
    4242>>> f.clean('200a-10-25')
    4343Traceback (most recent call last):
    4444...
    45 ValidationError: [u'Enter a valid date.']
     45TypeCoercionError: [u'Enter a valid date.']
    4646>>> f.clean('10/25/06')
    4747Traceback (most recent call last):
    4848...
    49 ValidationError: [u'Enter a valid date.']
     49TypeCoercionError: [u'Enter a valid date.']
    5050>>> f.clean(None)
    5151Traceback (most recent call last):
    5252...
    so the default formats won't work unless you specify them: 
    7474>>> f.clean('2006-10-25')
    7575Traceback (most recent call last):
    7676...
    77 ValidationError: [u'Enter a valid date.']
     77TypeCoercionError: [u'Enter a valid date.']
    7878>>> f.clean('25/10/2006')
    7979Traceback (most recent call last):
    8080...
    81 ValidationError: [u'Enter a valid date.']
     81TypeCoercionError: [u'Enter a valid date.']
    8282>>> f.clean('25/10/06')
    8383Traceback (most recent call last):
    8484...
    85 ValidationError: [u'Enter a valid date.']
     85TypeCoercionError: [u'Enter a valid date.']
    8686
    8787## Generic DateTimeField ######################################################
    8888
    datetime.datetime(2006, 10, 25, 0, 0) 
    126126>>> f.clean('hello')
    127127Traceback (most recent call last):
    128128...
    129 ValidationError: [u'Enter a valid date/time.']
     129TypeCoercionError: [u'Enter a valid date/time.']
    130130>>> f.clean('2006-10-25 4:30 p.m.')
    131131Traceback (most recent call last):
    132132...
    133 ValidationError: [u'Enter a valid date/time.']
     133TypeCoercionError: [u'Enter a valid date/time.']
    134134
    135135DateField accepts an optional input_formats parameter:
    136136>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
    so the default formats won't work unless you specify them: 
    150150>>> f.clean('2006-10-25 14:30:45')
    151151Traceback (most recent call last):
    152152...
    153 ValidationError: [u'Enter a valid date/time.']
     153TypeCoercionError: [u'Enter a valid date/time.']
    154154
    155155>>> f = DateTimeField(required=False)
    156156>>> f.clean(None)
  • tests/regressiontests/forms/util.py

    diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
    index bfaf73f..9933968 100644
    a b Tests for newforms/util.py module. 
    55
    66tests = r"""
    77>>> from django.newforms.util import *
    8 >>> from django.utils.translation import ugettext_lazy
    98
    109###########
    1110# flatatt #
    u' id="header"' 
    1817u' class="news" title="Read this"'
    1918>>> flatatt({})
    2019u''
    21 
    22 ###################
    23 # ValidationError #
    24 ###################
    25 
    26 # Can take a string.
    27 >>> print ValidationError("There was an error.").messages
    28 <ul class="errorlist"><li>There was an error.</li></ul>
    29 
    30 # Can take a unicode string.
    31 >>> print ValidationError(u"Not \u03C0.").messages
    32 <ul class="errorlist"><li>Not π.</li></ul>
    33 
    34 # Can take a lazy string.
    35 >>> print ValidationError(ugettext_lazy("Error.")).messages
    36 <ul class="errorlist"><li>Error.</li></ul>
    37 
    38 # Can take a list.
    39 >>> print ValidationError(["Error one.", "Error two."]).messages
    40 <ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
    41 
    42 # Can take a mixture in a list.
    43 >>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
    44 <ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
    45 
    46 >>> class VeryBadError:
    47 ...     def __unicode__(self): return u"A very bad error."
    48 
    49 # Can take a non-string.
    50 >>> print ValidationError(VeryBadError()).messages
    51 <ul class="errorlist"><li>A very bad error.</li></ul>
    5220"""
Back to Top