Django

Code

Ticket #6845: 6845-against-7877.patch

File 6845-against-7877.patch, 142.6 kB (added by Honza_Kral, 2 years ago)
  • a/AUTHORS

    old new  
    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 
  • a/django/contrib/admin/views/template.py

    old new  
    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 
     
    6969            error = e 
    7070        template.builtins.remove(register) 
    7171        if error: 
    72             raise validators.ValidationError, e.args 
     72            raise validation.ValidationError, e.args 
  • a/django/contrib/auth/forms.py

    old new  
    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 
  • a/django/contrib/auth/management/commands/createsuperuser.py

    old new  
    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 
  • a/django/contrib/auth/models.py

    old new  
    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) 
  • a/django/contrib/comments/views/comments.py

    old new  
    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 
  • a/django/contrib/flatpages/models.py

    old new  
    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) 
  • a/django/contrib/localflavor/br/forms.py

    old new  
    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)) 
  • a/django/contrib/localflavor/fi/forms.py

    old new  
    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" 
  • a/django/contrib/localflavor/jp/forms.py

    old new  
    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 
  • /dev/null

    old new  
     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 
  • a/django/core/validators.py

    old new  
    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: 
     
    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", 
     
    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" 
     
    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.") 
  • a/django/db/models/__init__.py

    old new  
    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 
  • a/django/db/models/base.py

    old new  
    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 
     
    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 
     
    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        """ 
  • a/django/db/models/fields/__init__.py

    old new  
    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 
     
    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 
     
    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 
     
    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 
     
    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) 
     
    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: 
     
    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) 
     
    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) 
     
    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 = [] 
     
    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 
     
    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) 
     
    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) 
     
    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} 
     
    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 
     
    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 
  • a/django/db/models/fields/related.py

    old new  
    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), 
  • a/django/newforms/__init__.py

    old new  
    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 * 
  • a/django/newforms/fields.py

    old new  
    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 
     
    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.'), 
     
    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 
     
    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 
     
    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 
     
    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): 
     
    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)): 
     
    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 = { 
     
    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 = { 
     
    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]) 
     
    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' 
     
    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): 
     
    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' 
     
    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): 
     
    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' 
     
    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): 
     
    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): 
     
    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 
     
    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: 
     
    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: 
     
    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'): 
     
    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, 
     
    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:// 
     
    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 
     
    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 = { 
     
    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 
     
    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    """ 
     
    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    """ 
     
    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. 
     
    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] 
     
    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] 
  • a/django/newforms/forms.py

    old new  
    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'" 
     
    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 
     
    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 
  • a/django/newforms/models.py

    old new  
    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 
     
    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 
     
    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) 
     
    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 
     
    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: 
     
    398408            else: 
    399409                final_values.append(obj) 
    400410        return final_values 
     411 
  • a/django/newforms/util.py

    old new  
    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) 
  • a/django/oldforms/__init__.py

    old new  
    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 
  • /dev/null

    old new  
     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 
  • a/tests/modeltests/manipulators/models.py

    old new  
    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"""} 
  • a/tests/modeltests/model_forms/models.py

    old new  
    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 
     
    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 
     
    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() 
     
    968969>>> f = ImageFileForm(data={'description': u'Test'}) 
    969970>>> f.fields['image'].required = False 
    970971>>> f.is_valid() 
    971 Tru
    972 >>> instance = f.save() 
    973 >>> instance.image 
    974 '' 
     972Fals
     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() 
  • a/tests/modeltests/validation/models.py

    old new  
    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""" 
     
    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 
     
    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"""} 
  • /dev/null

    old new  
     1# A models.py so that tests run. 
     2 
  • /dev/null

    old new  
     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 
  • a/tests/regressiontests/forms/error_messages.py

    old new  
    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... 
     
    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... 
     
    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... 
     
    122122>>> f.clean('abc') 
    123123Traceback (most recent call last): 
    124124... 
    125 ValidationError: [u'INVALID'] 
     125TypeCoercionError: [u'INVALID'] 
    126126 
    127127# TimeField ################################################################### 
    128128 
     
    136136>>> f.clean('abc') 
    137137Traceback (most recent call last): 
    138138... 
    139 ValidationError: [u'INVALID'] 
     139TypeCoercionError: [u'INVALID'] 
    140140 
    141141# DateTimeField ############################################################### 
    142142 
     
    150150>>> f.clean('abc') 
    151151Traceback (most recent call last): 
    152152... 
    153 ValidationError: [u'INVALID'] 
     153TypeCoercionError: [u'INVALID'] 
    154154 
    155155# RegexField ################################################################## 
    156156 
     
    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'] 
     
    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... 
     
    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... 
  • a/tests/regressiontests/forms/extra.py

    old new  
    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() 
  • a/tests/regressiontests/forms/fields.py

    old new  
    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. 
     
    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() 
     
    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') 
     
    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('') 
     
    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') 
     
    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) 
     
    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') 
     
    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('') 
     
    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') 
     
    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... 
     
    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('') 
     
    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... 
     
    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 
     
    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']) 
     
    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 
     
    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']) 
     
    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) 
     
    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' 
     
    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' 
     
    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') 
     
    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... 
     
    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(()) 
     
    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... 
     
    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... 
  • a/tests/regressiontests/forms/forms.py

    old new  
    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 
  • a/tests/regressiontests/forms/localflavor/br.py

    old new  
    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... 
  • a/tests/regressiontests/forms/localflavor/generic.py

    old new  
    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... 
     
    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 
     
    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']) 
     
    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) 
  • a/tests/regressiontests/forms/util.py

    old new  
    55 
    66tests = r""" 
    77>>> from django.newforms.util import * 
    8 >>> from django.utils.translation import ugettext_lazy 
    98 
    109########### 
    1110# flatatt # 
     
    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"""