Code

Ticket #6845: 6845-against-8090.patch

File 6845-against-8090.patch, 129.0 KB (added by Honza_Kral, 6 years ago)

new patch after 1.0 alpha

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 967c9f7..9b7f0d1 100644
    a b answer newbie questions, and generally made Django that much better: 
    223223    Igor Kolar <ike@email.si> 
    224224    Gasper Koren 
    225225    Martin Kosír <martin@martinkosir.net> 
     226    Honza Kral <Honza.Kral@gmail.com> 
    226227    Meir Kriheli <http://mksoft.co.il/> 
    227228    Bruce Kroeze <http://coderseye.com/> 
    228229    krzysiek.pawlik@silvermedia.pl 
  • django/contrib/admin/views/template.py

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

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

    diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
    index 3ea184a..4c381c9 100644
    a b class User(models.Model): 
    131131 
    132132    Username and password are required. Other fields are optional. 
    133133    """ 
    134     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).")) 
     134    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).")) 
    135135    first_name = models.CharField(_('first name'), max_length=30, blank=True) 
    136136    last_name = models.CharField(_('last name'), max_length=30, blank=True) 
    137137    email = models.EmailField(_('e-mail address'), blank=True) 
  • django/contrib/comments/views/comments.py

    diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
    index ba59cba..87d60f9 100644
    a b  
    11import base64 
    22import datetime 
    33 
    4 from django.core import validators 
     4from django.oldforms import validators 
    55from django import oldforms 
    66from django.core.mail import mail_admins, mail_managers 
    77from django.http import Http404 
  • django/contrib/flatpages/models.py

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

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

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

    diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
    index c86dbaf..40b1407 100644
    a b  
    22JP-specific Form helpers 
    33""" 
    44 
    5 from django.core import validators 
    6 from django.forms import ValidationError 
    75from django.utils.translation import ugettext_lazy as _ 
    86from django.forms.fields import RegexField, Select 
    97 
  • django/core/exceptions.py

    diff --git a/django/core/exceptions.py b/django/core/exceptions.py
    index e5df8ca..fb6634f 100644
    a b class FieldError(Exception): 
    3232    """Some kind of problem with a model field.""" 
    3333    pass 
    3434 
     35NON_FIELD_ERRORS = '__all__' 
     36class ValidationError(Exception): 
     37    def __init__(self, message): 
     38        """ 
     39        ValidationError can be passed any object that can be printed (usually 
     40        a string) or a list of objects. 
     41        """ 
     42        from django.utils.encoding import force_unicode 
     43        if hasattr(message, '__iter__'): 
     44            self.messages = [force_unicode(msg) for msg in message] 
     45        else: 
     46            message = force_unicode(message) 
     47            self.messages = [message] 
     48 
     49        if isinstance(message, dict): 
     50            self.message_dict = message 
     51 
     52    def __str__(self): 
     53        # This is needed because, without a __str__(), printing an exception 
     54        # instance would result in this: 
     55        # AttributeError: ValidationError instance has no attribute 'args' 
     56        # See http://www.python.org/doc/current/tut/node10.html#handling 
     57        from django.utils.encoding import force_unicode 
     58        if hasattr(self, 'message_dict'): 
     59            return repr(self.message_dict) 
     60        return repr([force_unicode(e) for e in self.messages]) 
  • django/core/validators.py

    diff --git a/django/core/validators.py b/django/core/validators.py
    index f94db40..62af2eb 100644
    a b  
    11""" 
    22A library of validators that return None and raise ValidationError when the 
    33provided data isn't valid. 
    4  
    5 Validators may be callable classes, and they may have an 'always_test' 
    6 attribute. If an 'always_test' attribute exists (regardless of value), the 
    7 validator will *always* be run, regardless of whether its associated 
    8 form field is required. 
    94""" 
    105 
    11 import urllib2 
    126import re 
    13 try: 
    14     from decimal import Decimal, DecimalException 
    15 except ImportError: 
    16     from django.utils._decimal import Decimal, DecimalException    # Python 2.3 
     7import urllib2 
    178 
    189from django.conf import settings 
    19 from django.utils.translation import ugettext as _, ugettext_lazy, ungettext 
    20 from django.utils.functional import Promise, lazy 
    21 from django.utils.encoding import force_unicode, smart_str 
     10from django.utils.encoding import smart_str 
     11from django.utils.translation import ugettext as _ 
     12from django.core.exceptions 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('-')) 
     93    # This check is needed because strftime is used when saving the date 
     94    # value to the database, and strftime requires that the year be >=1900. 
     95    if year < 1900: 
     96        raise ValidationError, _('Year must be 1900 or later.') 
    14497    try: 
    14598        date(year, month, day) 
    14699    except ValueError, e: 
    147100        msg = _('Invalid date: %s') % _(str(e)) 
    148         raise ValidationError, msg 
    149101 
    150 def isValidANSIDate(field_data, all_data): 
    151     if not ansi_date_re.search(field_data): 
    152         raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.') 
    153     _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) 
    154105 
    155 def isValidANSITime(field_data, all_data): 
    156     if not ansi_time_re.search(field_data): 
    157         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) 
    158109 
    159 def isValidANSIDatetime(field_data, all_data): 
    160     if not ansi_datetime_re.search(field_data): 
    161         raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.') 
    162     _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.") 
    163113 
    164 def isValidEmail(field_data, all_data): 
    165     if not email_re.search(field_data): 
    166         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.") 
    167117 
    168 def isValidImage(field_data, all_data): 
    169     """ 
    170     Checks that the file-upload field data contains a valid image (GIF, JPG, 
    171     PNG, possibly others -- whatever the Python Imaging Library supports). 
    172     """ 
    173     from PIL import Image 
    174     from cStringIO import StringIO 
    175     try: 
    176         content = field_data.read() 
    177     except TypeError: 
    178         raise ValidationError, _("No file was submitted. Check the encoding type on the form.") 
    179     try: 
    180         # load() is the only method that can spot a truncated JPEG, 
    181         #  but it cannot be called sanely after verify() 
    182         trial_image = Image.open(StringIO(content)) 
    183         trial_image.load() 
    184         # verify() is the only method that can spot a corrupt PNG, 
    185         #  but it must be called immediately after the constructor 
    186         trial_image = Image.open(StringIO(content)) 
    187         trial_image.verify() 
    188     except Exception: # Python Imaging Library doesn't recognize it as an image 
    189         raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.") 
    190  
    191 def isValidImageURL(field_data, all_data): 
    192     uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png')) 
    193     try: 
    194         uc(field_data, all_data) 
    195     except URLMimeTypeCheck.InvalidContentType: 
    196         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.") 
    197121 
    198 def isValidPhone(field_data, all_data): 
    199     if not phone_re.search(field_data): 
    200         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.") 
    201125 
    202 def isValidQuicktimeVideoURL(field_data, all_data): 
    203     "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)" 
    204     uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',)) 
    205     try: 
    206         uc(field_data, all_data) 
    207     except URLMimeTypeCheck.InvalidContentType: 
    208         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.") 
    209129 
    210 def isValidURL(field_data, all_data): 
    211     if not url_re.search(field_data): 
    212         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.") 
    213133 
    214 def isValidHTML(field_data, all_data): 
    215     import urllib, urllib2 
    216     try: 
    217         u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'})) 
    218     except: 
    219         # Validator or Internet connection is unavailable. Fail silently. 
    220         return 
    221     html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid') 
    222     if html_is_valid: 
    223         return 
    224     from xml.dom.minidom import parseString 
    225     error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')] 
    226     raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages) 
    227  
    228 def isWellFormedXml(field_data, all_data): 
    229     from xml.dom.minidom import parseString 
    230     try: 
    231         parseString(field_data) 
    232     except Exception, e: # Naked except because we're not sure what will be thrown 
    233         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.") 
    234140 
    235 def isWellFormedXmlFragment(field_data, all_data): 
    236     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.") 
    237152 
    238 def isExistingURL(field_data, all_data): 
     153def validate_existing_url(value, message_dict={}): 
    239154    try: 
    240155        headers = { 
    241156            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
    def isExistingURL(field_data, all_data): 
    244159            "Connection" : "close", 
    245160            "User-Agent": settings.URL_VALIDATOR_USER_AGENT 
    246161            } 
    247         req = urllib2.Request(field_data,None, headers) 
     162        req = urllib2.Request(value,None, headers) 
    248163        u = urllib2.urlopen(req) 
    249164    except ValueError: 
    250         raise ValidationError, _("Invalid URL: %s") % field_data 
     165        raise ValidationError, message_dict.get('invalid', _("Invalid URL: %s") % value) 
    251166    except urllib2.HTTPError, e: 
    252167        # 401s are valid; they just mean authorization is required. 
    253168        # 301 and 302 are redirects; they just mean look somewhere else. 
    254169        if str(e.code) not in ('401','301','302'): 
    255             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.")) 
    256171    except: # urllib2.URLError, httplib.InvalidURL, etc. 
    257         raise ValidationError, _("The URL %s is a broken link.") % field_data 
    258  
    259 def isValidUSState(field_data, all_data): 
    260     "Checks that the given string is a valid two-letter U.S. state abbreviation" 
    261     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'] 
    262     if field_data.upper() not in states: 
    263         raise ValidationError, _("Enter a valid U.S. state abbreviation.") 
    264  
    265 def hasNoProfanities(field_data, all_data): 
    266     """ 
    267     Checks that the given string has no profanities in it. This does a simple 
    268     check for whether each profanity exists within the string, so 'fuck' will 
    269     catch 'motherfucker' as well. Raises a ValidationError such as: 
    270         Watch your mouth! The words "f--k" and "s--t" are not allowed here. 
    271     """ 
    272     field_data = field_data.lower() # normalize 
    273     words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data] 
    274     if words_seen: 
    275         from django.utils.text import get_text_list 
    276         plural = len(words_seen) 
    277         raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.", 
    278             "Watch your mouth! The words %s are not allowed here.", plural) % \ 
    279             get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and')) 
    280  
    281 class AlwaysMatchesOtherField(object): 
    282     def __init__(self, other_field_name, error_message=None): 
    283         self.other = other_field_name 
    284         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other) 
    285         self.always_test = True 
    286  
    287     def __call__(self, field_data, all_data): 
    288         if field_data != all_data[self.other]: 
    289             raise ValidationError, self.error_message 
    290  
    291 class ValidateIfOtherFieldEquals(object): 
    292     def __init__(self, other_field, other_value, validator_list): 
    293         self.other_field, self.other_value = other_field, other_value 
    294         self.validator_list = validator_list 
    295         self.always_test = True 
    296  
    297     def __call__(self, field_data, all_data): 
    298         if self.other_field in all_data and all_data[self.other_field] == self.other_value: 
    299             for v in self.validator_list: 
    300                 v(field_data, all_data) 
    301  
    302 class RequiredIfOtherFieldNotGiven(object): 
    303     def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")): 
    304         self.other, self.error_message = other_field_name, error_message 
    305         self.always_test = True 
    306  
    307     def __call__(self, field_data, all_data): 
    308         if not all_data.get(self.other, False) and not field_data: 
    309             raise ValidationError, self.error_message 
    310  
    311 class RequiredIfOtherFieldsGiven(object): 
    312     def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): 
    313         self.other, self.error_message = other_field_names, error_message 
    314         self.always_test = True 
    315  
    316     def __call__(self, field_data, all_data): 
    317         for field in self.other: 
    318             if all_data.get(field, False) and not field_data: 
    319                 raise ValidationError, self.error_message 
    320  
    321 class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven): 
    322     "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list." 
    323     def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): 
    324         RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message) 
    325  
    326 class RequiredIfOtherFieldEquals(object): 
    327     def __init__(self, other_field, other_value, error_message=None, other_label=None): 
    328         self.other_field = other_field 
    329         self.other_value = other_value 
    330         other_label = other_label or other_value 
    331         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), { 
    332             'field': other_field, 'value': other_label}) 
    333         self.always_test = True 
    334  
    335     def __call__(self, field_data, all_data): 
    336         if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data: 
    337             raise ValidationError(self.error_message) 
    338  
    339 class RequiredIfOtherFieldDoesNotEqual(object): 
    340     def __init__(self, other_field, other_value, other_label=None, error_message=None): 
    341         self.other_field = other_field 
    342         self.other_value = other_value 
    343         other_label = other_label or other_value 
    344         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), { 
    345             'field': other_field, 'value': other_label}) 
    346         self.always_test = True 
    347  
    348     def __call__(self, field_data, all_data): 
    349         if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data: 
    350             raise ValidationError(self.error_message) 
    351  
    352 class IsLessThanOtherField(object): 
    353     def __init__(self, other_field_name, error_message): 
    354         self.other, self.error_message = other_field_name, error_message 
    355  
    356     def __call__(self, field_data, all_data): 
    357         if field_data > all_data[self.other]: 
    358             raise ValidationError, self.error_message 
    359  
    360 class UniqueAmongstFieldsWithPrefix(object): 
    361     def __init__(self, field_name, prefix, error_message): 
    362         self.field_name, self.prefix = field_name, prefix 
    363         self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.") 
    364  
    365     def __call__(self, field_data, all_data): 
    366         for field_name, value in all_data.items(): 
    367             if field_name != self.field_name and value == field_data: 
    368                 raise ValidationError, self.error_message 
    369  
    370 class NumberIsInRange(object): 
    371     """ 
    372     Validator that tests if a value is in a range (inclusive). 
    373     """ 
    374     def __init__(self, lower=None, upper=None, error_message=''): 
    375         self.lower, self.upper = lower, upper 
    376         if not error_message: 
    377             if lower and upper: 
    378                  self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper} 
    379             elif lower: 
    380                 self.error_message = _("This value must be at least %s.") % lower 
    381             elif upper: 
    382                 self.error_message = _("This value must be no more than %s.") % upper 
    383         else: 
    384             self.error_message = error_message 
    385  
    386     def __call__(self, field_data, all_data): 
    387         # Try to make the value numeric. If this fails, we assume another 
    388         # validator will catch the problem. 
    389         try: 
    390             val = float(field_data) 
    391         except ValueError: 
    392             return 
    393  
    394         # Now validate 
    395         if self.lower and self.upper and (val < self.lower or val > self.upper): 
    396             raise ValidationError(self.error_message) 
    397         elif self.lower and val < self.lower: 
    398             raise ValidationError(self.error_message) 
    399         elif self.upper and val > self.upper: 
    400             raise ValidationError(self.error_message) 
    401  
    402 class IsAPowerOf(object): 
    403     """ 
    404     Usage: If you create an instance of the IsPowerOf validator: 
    405         v = IsAPowerOf(2) 
    406  
    407     The following calls will succeed: 
    408         v(4, None) 
    409         v(8, None) 
    410         v(16, None) 
    411  
    412     But this call: 
    413         v(17, None) 
    414     will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']" 
    415     """ 
    416     def __init__(self, power_of): 
    417         self.power_of = power_of 
    418  
    419     def __call__(self, field_data, all_data): 
    420         from math import log 
    421         val = log(int(field_data)) / log(self.power_of) 
    422         if val != int(val): 
    423             raise ValidationError, _("This value must be a power of %s.") % self.power_of 
    424  
    425 class IsValidDecimal(object): 
    426     def __init__(self, max_digits, decimal_places): 
    427         self.max_digits, self.decimal_places = max_digits, decimal_places 
    428  
    429     def __call__(self, field_data, all_data): 
    430         try: 
    431             val = Decimal(field_data) 
    432         except DecimalException: 
    433             raise ValidationError, _("Please enter a valid decimal number.") 
    434  
    435         pieces = str(val).lstrip("-").split('.') 
    436         decimals = (len(pieces) == 2) and len(pieces[1]) or 0 
    437         digits = len(pieces[0]) 
    438  
    439         if digits + decimals > self.max_digits: 
    440             raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.", 
    441                 "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits 
    442         if digits > (self.max_digits - self.decimal_places): 
    443             raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.", 
    444                 "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) 
    445         if decimals > self.decimal_places: 
    446             raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.", 
    447                 "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places 
    448  
    449 def isValidFloat(field_data, all_data): 
    450     data = smart_str(field_data) 
    451     try: 
    452         float(data) 
    453     except ValueError: 
    454         raise ValidationError, _("Please enter a valid floating point number.") 
    455  
    456 class HasAllowableSize(object): 
    457     """ 
    458     Checks that the file-upload field data is a certain size. min_size and 
    459     max_size are measurements in bytes. 
    460     """ 
    461     def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None): 
    462         self.min_size, self.max_size = min_size, max_size 
    463         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) 
    464         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) 
    465  
    466     def __call__(self, field_data, all_data): 
    467         try: 
    468             content = field_data.read() 
    469         except TypeError: 
    470             raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.") 
    471         if self.min_size is not None and len(content) < self.min_size: 
    472             raise ValidationError, self.min_error_message 
    473         if self.max_size is not None and len(content) > self.max_size: 
    474             raise ValidationError, self.max_error_message 
    475  
    476 class MatchesRegularExpression(object): 
    477     """ 
    478     Checks that the field matches the given regular-expression. The regex 
    479     should be in string format, not already compiled. 
    480     """ 
    481     def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")): 
    482         self.regexp = re.compile(regexp) 
    483         self.error_message = error_message 
    484  
    485     def __call__(self, field_data, all_data): 
    486         if not self.regexp.search(field_data): 
    487             raise ValidationError(self.error_message) 
    488  
    489 class AnyValidator(object): 
    490     """ 
    491     This validator tries all given validators. If any one of them succeeds, 
    492     validation passes. If none of them succeeds, the given message is thrown 
    493     as a validation error. The message is rather unspecific, so it's best to 
    494     specify one on instantiation. 
    495     """ 
    496     def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")): 
    497         if validator_list is None: validator_list = [] 
    498         self.validator_list = validator_list 
    499         self.error_message = error_message 
    500         for v in validator_list: 
    501             if hasattr(v, 'always_test'): 
    502                 self.always_test = True 
    503  
    504     def __call__(self, field_data, all_data): 
    505         for v in self.validator_list: 
    506             try: 
    507                 v(field_data, all_data) 
    508                 return 
    509             except ValidationError, e: 
    510                 pass 
    511         raise ValidationError(self.error_message) 
     172        raise ValidationError, message_dict.get('invalid_link', _("This URL appears to be a broken link.")) 
    512173 
    513174class URLMimeTypeCheck(object): 
    514175    "Checks that the provided URL points to a document with a listed mime type" 
    class URLMimeTypeCheck(object): 
    520181    def __init__(self, mime_type_list): 
    521182        self.mime_type_list = mime_type_list 
    522183 
    523     def __call__(self, field_data, all_data): 
    524         import urllib2 
    525         try: 
    526             isValidURL(field_data, all_data) 
    527         except ValidationError: 
    528             raise 
     184    def __call__(self, value, message_dict={}): 
     185        validate_existing_url(value) 
    529186        try: 
    530             info = urllib2.urlopen(field_data).info() 
     187            info = urllib2.urlopen(value).info() 
    531188        except (urllib2.HTTPError, urllib2.URLError): 
    532             raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data 
     189            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % value 
    533190        content_type = info['content-type'] 
    534191        if content_type not in self.mime_type_list: 
    535192            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % { 
    536                 'url': field_data, 'contenttype': content_type} 
    537  
    538 class RelaxNGCompact(object): 
    539     "Validate against a Relax NG compact schema" 
    540     def __init__(self, schema_path, additional_root_element=None): 
    541         self.schema_path = schema_path 
    542         self.additional_root_element = additional_root_element 
    543  
    544     def __call__(self, field_data, all_data): 
    545         import os, tempfile 
    546         if self.additional_root_element: 
    547             field_data = '<%(are)s>%(data)s\n</%(are)s>' % { 
    548                 'are': self.additional_root_element, 
    549                 'data': field_data 
    550             } 
    551         filename = tempfile.mktemp() # Insecure, but nothing else worked 
    552         fp = open(filename, 'w') 
    553         fp.write(field_data) 
    554         fp.close() 
    555         if not os.path.exists(settings.JING_PATH): 
    556             raise Exception, "%s not found!" % settings.JING_PATH 
    557         p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename)) 
    558         errors = [line.strip() for line in p.readlines()] 
    559         p.close() 
    560         os.unlink(filename) 
    561         display_errors = [] 
    562         lines = field_data.split('\n') 
    563         for error in errors: 
    564             ignored, line, level, message = error.split(':', 3) 
    565             # Scrape the Jing error messages to reword them more nicely. 
    566             m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message) 
    567             if m: 
    568                 display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \ 
    569                     {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]}) 
    570                 continue 
    571             if message.strip() == 'text not allowed here': 
    572                 display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \ 
    573                     {'line':line, 'start':lines[int(line) - 1][:30]}) 
    574                 continue 
    575             m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message) 
    576             if m: 
    577                 display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \ 
    578                     {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
    579                 continue 
    580             m = re.search(r'\s*unknown element "(.*?)"', message) 
    581             if m: 
    582                 display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \ 
    583                     {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
    584                 continue 
    585             if message.strip() == 'required attributes missing': 
    586                 display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \ 
    587                     {'line':line, 'start':lines[int(line) - 1][:30]}) 
    588                 continue 
    589             m = re.search(r'\s*bad value for attribute "(.*?)"', message) 
    590             if m: 
    591                 display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \ 
    592                     {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
    593                 continue 
    594             # Failing all those checks, use the default error message. 
    595             display_error = 'Line %s: %s [%s]' % (line, message, level.strip()) 
    596             display_errors.append(display_error) 
    597         if len(display_errors) > 0: 
    598             raise ValidationError, display_errors 
     193                'url': value, 'contenttype': content_type} 
     194 
     195def validate_image_url(value, message_dict={}): 
     196    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png')) 
     197    try: 
     198        uc(value, message_dict) 
     199    except URLMimeTypeCheck.InvalidContentType: 
     200        raise ValidationError, _("The URL %s does not point to a valid image.") % value 
     201 
     202 
     203def validate_float(value, message_dict={}): 
     204    data = smart_str(value) 
     205    try: 
     206        float(data) 
     207    except ValueError: 
     208        raise ValidationError, _("Please enter a valid floating point number.") 
  • django/db/models/__init__.py

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

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 53307dc..b126f37 100644
    a b except NameError: 
    1111import django.db.models.manipulators    # Imported to register signal handler. 
    1212import django.db.models.manager         # Ditto. 
    1313from django.core import validators 
    14 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError 
    1514from django.db.models.fields import AutoField, ImageField 
     15from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS 
    1616from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 
    1717from django.db.models.query import delete_objects, Q, CollectedObjects 
    1818from django.db.models.options import Options 
    from django.db.models import signals 
    2121from django.db.models.loading import register_models, get_model 
    2222from django.dispatch import dispatcher 
    2323from django.utils.functional import curry 
     24from django.utils.translation import ugettext_lazy as _ 
    2425from django.utils.encoding import smart_str, force_unicode, smart_unicode 
    2526from django.core.files.move import file_move_safe 
    2627from django.core.files import locks 
    class Model(object): 
    354355 
    355356    save_base.alters_data = True 
    356357 
    357     def validate(self): 
     358    def clean(self, new_data=None): 
     359        self.to_python() 
     360        self.validate(new_data) 
     361 
     362    def to_python(self): 
     363        error_dict = {} 
     364        for f in self._meta.fields: 
     365            try: 
     366                value = f.to_python(getattr(self, f.attname, f.get_default())) 
     367                setattr(self, f.attname, value) 
     368            except ValidationError, e: 
     369                error_dict[f.name] = e.messages 
     370        if error_dict: 
     371            raise ValidationError(error_dict) 
     372 
     373    def validate(self, new_data=None): 
    358374        """ 
    359         First coerces all fields on this instance to their proper Python types. 
    360         Then runs validation on every field. Returns a dictionary of 
    361         field_name -> error_list. 
     375        Validate the data on the model, if new_data is supplied, try and use those instead of 
     376        actual values on the fields. Note that the fields are validated separately. 
    362377        """ 
     378        if new_data is not None: 
     379            def get_value(f): 
     380                if f.name in new_data: 
     381                    return f.to_python(new_data[f.name]) 
     382                return getattr(self, f.attname, f.get_default()) 
     383        else: 
     384            get_value = lambda f: getattr(self, f.attname, f.get_default()) 
    363385        error_dict = {} 
    364         invalid_python = {} 
    365386        for f in self._meta.fields: 
    366387            try: 
    367                 setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default()))) 
    368             except validators.ValidationError, e: 
     388                value = get_value(f) 
     389                f.validate(value, instance=self) 
     390                if hasattr(self, 'validate_%s' % f.name): 
     391                    getattr(self, 'validate_%s' % f.name)(value) 
     392            except ValidationError, e: 
    369393                error_dict[f.name] = e.messages 
    370                 invalid_python[f.name] = 1 
    371         for f in self._meta.fields: 
    372             if f.name in invalid_python: 
    373                 continue 
    374             errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__) 
    375             if errors: 
    376                 error_dict[f.name] = errors 
    377         return error_dict 
     394 
     395        for un_together in self._meta.unique_together: 
     396            lookup = {} 
     397            for name in un_together: 
     398                if name in error_dict: 
     399                    break 
     400                f = self._meta.get_field(name) 
     401                lookup['%s__exact' % name] = get_value(f) 
     402            try: 
     403                qset = self.__class__._default_manager.all() 
     404                if self.pk: 
     405                    qset = qset.exclude(pk=self.pk) 
     406                obj = qset.get(**lookup) 
     407                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together) 
     408            except self.DoesNotExist: 
     409                pass 
     410 
     411        if error_dict: 
     412            raise ValidationError(error_dict) 
    378413 
    379414    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False): 
    380415        """ 
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 439633c..6abd261 100644
    a b from django.db.models import signals 
    1212from django.db.models.query_utils import QueryWrapper 
    1313from django.dispatch import dispatcher 
    1414from django.conf import settings 
     15from django.oldforms import validators as oldvalidators 
    1516from django.core import validators 
    1617from django import oldforms 
    1718from django import forms 
    class Field(object): 
    7677    creation_counter = 0 
    7778    auto_creation_counter = -1 
    7879 
     80    validators = [] 
     81 
    7982    def __init__(self, verbose_name=None, name=None, primary_key=False, 
    8083            max_length=None, unique=False, blank=False, null=False, 
    8184            db_index=False, core=False, rel=None, default=NOT_PROVIDED, 
    8285            editable=True, serialize=True, unique_for_date=None, 
    8386            unique_for_month=None, unique_for_year=None, validator_list=None, 
    8487            choices=None, help_text='', db_column=None, db_tablespace=None, 
    85             auto_created=False): 
     88            auto_created=False, validators=[]): 
    8689        self.name = name 
    8790        self.verbose_name = verbose_name 
    8891        self.primary_key = primary_key 
    class Field(object): 
    9598        self.core, self.rel, self.default = core, rel, default 
    9699        self.editable = editable 
    97100        self.serialize = serialize 
     101        self.validators = validators + self.validators 
    98102        self.validator_list = validator_list or [] 
    99103        self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month 
    100104        self.unique_for_year = unique_for_year 
    class Field(object): 
    160164            return get_creation_module().DATA_TYPES[self.get_internal_type()] % data 
    161165        except KeyError: 
    162166            return None 
    163  
    164167    def unique(self): 
    165168        return self._unique or self.primary_key 
    166169    unique = property(unique) 
    167170 
    168     def validate_full(self, field_data, all_data): 
     171    def validate(self, value, instance=None): 
    169172        """ 
    170         Returns a list of errors for this field. This is the main interface, 
    171         as it encapsulates some basic validation logic used by all fields. 
    172         Subclasses should implement validate(), not validate_full(). 
    173         """ 
    174         if not self.blank and not field_data: 
    175             return [_('This field is required.')] 
    176         try: 
    177             self.validate(field_data, all_data) 
    178         except validators.ValidationError, e: 
    179             return e.messages 
    180         return [] 
    181  
    182     def validate(self, field_data, all_data): 
    183         """ 
    184         Raises validators.ValidationError if field_data has any errors. 
     173        Raises validators.ValidationError if value has any errors. 
    185174        Subclasses should override this to specify field-specific validation 
    186         logic. This method should assume field_data has already been converted 
     175        logic. This method should assume value has already been converted 
    187176        into the appropriate data type by Field.to_python(). 
    188177        """ 
    189         pass 
     178        if not self.blank and self.editable and not value: 
     179            raise validators.ValidationError(_('This field is required.')) 
     180        elist = [] 
     181        for validator in self.validators: 
     182            validator(value)     
     183        if self.unique and instance: 
     184            try: 
     185                qset = instance.__class__._default_manager.all() 
     186                if instance.pk: 
     187                    qset = qset.exclude(pk=instance.pk) 
     188                obj = qset.get(**{'%s__exact' % self.name : value}) 
     189                raise validators.ValidationError(_('This field must be unique')) 
     190            except instance.DoesNotExist: 
     191                pass 
    190192 
    191193    def set_attributes_from_name(self, name): 
    192194        self.name = name 
    class Field(object): 
    340342                    core_field_names.extend(f.get_manipulator_field_names(name_prefix)) 
    341343            # Now, if there are any, add the validator to this FormField. 
    342344            if core_field_names: 
    343                 params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required."))) 
     345                params['validator_list'].append(oldvalidators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required."))) 
    344346 
    345347        # Finally, add the field_names. 
    346348        field_names = self.get_manipulator_field_names(name_prefix) 
    class DateField(Field): 
    545547            return value.date() 
    546548        if isinstance(value, datetime.date): 
    547549            return value 
    548         validators.isValidANSIDate(value, None) 
    549550        try: 
    550551            return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) 
    551552        except ValueError: 
    class DecimalField(Field): 
    746747        return super(DecimalField, self).formfield(**defaults) 
    747748 
    748749class EmailField(CharField): 
     750    validators = [validators.validate_email] 
    749751    def __init__(self, *args, **kwargs): 
    750752        kwargs['max_length'] = kwargs.get('max_length', 75) 
    751753        CharField.__init__(self, *args, **kwargs) 
    class EmailField(CharField): 
    753755    def get_manipulator_field_objs(self): 
    754756        return [oldforms.EmailField] 
    755757 
    756     def validate(self, field_data, all_data): 
    757         validators.isValidEmail(field_data, all_data) 
    758  
    759758    def formfield(self, **kwargs): 
    760759        defaults = {'form_class': forms.EmailField} 
    761760        defaults.update(kwargs) 
    class FileField(Field): 
    792791                        self.always_test = True 
    793792                    def __call__(self, field_data, all_data): 
    794793                        if not all_data.get(self.other_file_field_name, False): 
    795                             c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required.")) 
     794                            c = oldvalidators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required.")) 
    796795                            c(field_data, all_data) 
    797796                # First, get the core fields, if any. 
    798797                core_field_names = [] 
    class FileField(Field): 
    803802                if core_field_names: 
    804803                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name)) 
    805804            else: 
    806                 v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required.")) 
     805                v = oldvalidators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required.")) 
    807806                v.always_test = True 
    808807                field_list[0].validator_list.append(v) 
    809808                field_list[0].is_required = field_list[1].is_required = False 
    class IntegerField(Field): 
    971970 
    972971class IPAddressField(Field): 
    973972    empty_strings_allowed = False 
     973    validators = [validators.validate_ip_address4] 
     974 
    974975    def __init__(self, *args, **kwargs): 
    975976        kwargs['max_length'] = 15 
    976977        Field.__init__(self, *args, **kwargs) 
    class IPAddressField(Field): 
    981982    def get_internal_type(self): 
    982983        return "IPAddressField" 
    983984 
    984     def validate(self, field_data, all_data): 
    985         validators.isValidIPAddress4(field_data, None) 
    986  
    987985    def formfield(self, **kwargs): 
    988986        defaults = {'form_class': forms.IPAddressField} 
    989987        defaults.update(kwargs) 
    class NullBooleanField(Field): 
    10181016        return super(NullBooleanField, self).formfield(**defaults) 
    10191017 
    10201018class PhoneNumberField(IntegerField): 
     1019    validators = [validators.validate_phone_number] 
     1020 
    10211021    def get_manipulator_field_objs(self): 
    10221022        return [oldforms.PhoneNumberField] 
    10231023 
    10241024    def get_internal_type(self): 
    10251025        return "PhoneNumberField" 
    10261026 
    1027     def validate(self, field_data, all_data): 
    1028         validators.isValidPhone(field_data, all_data) 
    1029  
    10301027    def formfield(self, **kwargs): 
    10311028        from django.contrib.localflavor.us.forms import USPhoneNumberField 
    10321029        defaults = {'form_class': USPhoneNumberField} 
    class PositiveSmallIntegerField(IntegerField): 
    10581055        return super(PositiveSmallIntegerField, self).formfield(**defaults) 
    10591056 
    10601057class SlugField(CharField): 
     1058    validators = [validators.validate_slug] 
    10611059    def __init__(self, *args, **kwargs): 
    10621060        kwargs['max_length'] = kwargs.get('max_length', 50) 
    1063         kwargs.setdefault('validator_list', []).append(validators.isSlug) 
    10641061        # Set db_index=True unless it's been set manually. 
    10651062        if 'db_index' not in kwargs: 
    10661063            kwargs['db_index'] = True 
    class URLField(CharField): 
    11561153    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): 
    11571154        kwargs['max_length'] = kwargs.get('max_length', 200) 
    11581155        if verify_exists: 
    1159             kwargs.setdefault('validator_list', []).append(validators.isExistingURL) 
     1156            kwargs.setdefault('validators', []).append(validators.validate_existing_url) 
    11601157        self.verify_exists = verify_exists 
    11611158        CharField.__init__(self, verbose_name, name, **kwargs) 
    11621159 
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index ca25df2..061ea08 100644
    a b class ManyToManyField(RelatedField, Field): 
    770770        objects = mod._default_manager.in_bulk(pks) 
    771771        if len(objects) != len(pks): 
    772772            badkeys = [k for k in pks if k not in objects] 
    773             raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", 
     773            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", 
    774774                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % { 
    775775                'self': self.verbose_name, 
    776776                'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys), 
  • django/forms/__init__.py

    diff --git a/django/forms/__init__.py b/django/forms/__init__.py
    index 0d9c68f..8321417 100644
    a b TODO: 
    1010    "This form field requires foo.js" and form.js_includes() 
    1111""" 
    1212 
    13 from util import ValidationError 
     13from django.core.exceptions import ValidationError, NON_FIELD_ERRORS 
    1414from widgets import * 
    1515from fields import * 
    1616from forms import * 
    1717from models import * 
     18from formsets import * 
  • django/forms/fields.py

    diff --git a/django/forms/fields.py b/django/forms/fields.py
    index 6b834af..290fcbf 100644
    a b except NameError: 
    2525 
    2626from django.utils.translation import ugettext_lazy as _ 
    2727from django.utils.encoding import smart_unicode, smart_str 
     28from django.core.exceptions import ValidationError 
     29from django.core import validators 
    2830 
    29 from util import ErrorList, ValidationError 
    3031from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput 
     32from util import ErrorList 
    3133from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile 
    3234 
    3335__all__ = ( 
    EMPTY_VALUES = (None, '') 
    4749 
    4850class Field(object): 
    4951    widget = TextInput # Default widget to use when rendering this type of Field. 
     52    validators = [] 
    5053    hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". 
    5154    default_error_messages = { 
    5255        'required': _(u'This field is required.'), 
    class Field(object): 
    5760    creation_counter = 0 
    5861 
    5962    def __init__(self, required=True, widget=None, label=None, initial=None, 
    60                  help_text=None, error_messages=None): 
     63                 help_text=None, error_messages=None, validators=[]): 
    6164        # required -- Boolean that specifies whether the field is required. 
    6265        #             True by default. 
    6366        # widget -- A Widget class, or instance of a Widget class, that should 
    class Field(object): 
    7174        # initial -- A value to use in this Field's initial display. This value 
    7275        #            is *not* used as a fallback if data isn't given. 
    7376        # help_text -- An optional string to use as "help text" for this Field. 
     77        # validators -- Optional list of additional validator functions 
    7478        if label is not None: 
    7579            label = smart_unicode(label) 
     80        self.validators = self.validators + validators 
    7681        self.required, self.label, self.initial = required, label, initial 
    7782        self.help_text = smart_unicode(help_text or '') 
    7883        widget = widget or self.widget 
    class Field(object): 
    100105        messages.update(error_messages or {}) 
    101106        self.error_messages = messages 
    102107 
     108    def to_python(self, value): 
     109        if value in EMPTY_VALUES: 
     110            return None 
     111        return smart_unicode(value) 
     112 
     113    def validate(self, value): 
     114        if self.required and value in EMPTY_VALUES: 
     115            raise ValidationError(self.error_messages['required']) 
     116        elif value in EMPTY_VALUES: 
     117            return 
     118        elist = ErrorList() 
     119        for validator in self.validators: 
     120            try: 
     121                validator(value, self.error_messages) 
     122            except ValidationError, e: 
     123                elist.extend(e.messages) 
     124        if elist: 
     125            raise ValidationError(elist) 
     126 
    103127    def clean(self, value): 
    104128        """ 
    105129        Validates the given value and returns its "cleaned" value as an 
    class Field(object): 
    107131 
    108132        Raises ValidationError for any errors. 
    109133        """ 
    110         if self.required and value in EMPTY_VALUES: 
    111             raise ValidationError(self.error_messages['required']) 
     134        value = self.to_python(value) 
     135        self.validate(value) 
    112136        return value 
    113137 
    114138    def widget_attrs(self, widget): 
    class CharField(Field): 
    135159        self.max_length, self.min_length = max_length, min_length 
    136160        super(CharField, self).__init__(*args, **kwargs) 
    137161 
    138     def clean(self, value): 
    139         "Validates max_length and min_length. Returns a Unicode object." 
    140         super(CharField, self).clean(value) 
     162    def to_python(self, value): 
    141163        if value in EMPTY_VALUES: 
    142164            return u'' 
    143         value = smart_unicode(value) 
     165        return smart_unicode(value) 
     166 
     167    def validate(self, value): 
     168        "Validates max_length and min_length. Returns a Unicode object." 
     169        super(CharField, self).validate(value) 
    144170        value_length = len(value) 
     171        if value_length == 0 and not self.required: 
     172            return  
    145173        if self.max_length is not None and value_length > self.max_length: 
    146174            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) 
    147175        if self.min_length is not None and value_length < self.min_length: 
    148176            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) 
    149         return value 
    150177 
    151178    def widget_attrs(self, widget): 
    152179        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): 
    class IntegerField(Field): 
    164191        self.max_value, self.min_value = max_value, min_value 
    165192        super(IntegerField, self).__init__(*args, **kwargs) 
    166193 
    167     def clean(self, value): 
    168         """ 
    169         Validates that int() can be called on the input. Returns the result 
    170         of int(). Returns None for empty values. 
    171         """ 
    172         super(IntegerField, self).clean(value) 
     194    def to_python(self, value): 
    173195        if value in EMPTY_VALUES: 
    174196            return None 
    175197        try: 
    176             value = int(str(value)) 
     198            return int(smart_str(value)) 
    177199        except (ValueError, TypeError): 
    178200            raise ValidationError(self.error_messages['invalid']) 
     201 
     202    def validate(self, value): 
     203        """ 
     204        Validates that int() can be called on the input. Returns the result 
     205        of int(). Returns None for empty values. 
     206        """ 
     207        super(IntegerField, self).validate(value) 
     208        if value is None: return 
    179209        if self.max_value is not None and value > self.max_value: 
    180210            raise ValidationError(self.error_messages['max_value'] % self.max_value) 
    181211        if self.min_value is not None and value < self.min_value: 
    182212            raise ValidationError(self.error_messages['min_value'] % self.min_value) 
    183         return value 
    184213 
    185214class FloatField(Field): 
    186215    default_error_messages = { 
    class FloatField(Field): 
    193222        self.max_value, self.min_value = max_value, min_value 
    194223        Field.__init__(self, *args, **kwargs) 
    195224 
    196     def clean(self, value): 
     225    def to_python(self, value): 
    197226        """ 
    198227        Validates that float() can be called on the input. Returns a float. 
    199228        Returns None for empty values. 
    200229        """ 
    201         super(FloatField, self).clean(value) 
    202         if not self.required and value in EMPTY_VALUES: 
     230        if value in EMPTY_VALUES: 
    203231            return None 
    204232        try: 
    205             value = float(value) 
     233            return float(value) 
    206234        except (ValueError, TypeError): 
    207235            raise ValidationError(self.error_messages['invalid']) 
     236 
     237    def validate(self, value): 
     238        super(FloatField, self).validate(value) 
     239        if value is None: return 
    208240        if self.max_value is not None and value > self.max_value: 
    209241            raise ValidationError(self.error_messages['max_value'] % self.max_value) 
    210242        if self.min_value is not None and value < self.min_value: 
    211243            raise ValidationError(self.error_messages['min_value'] % self.min_value) 
    212         return value 
    213244 
    214245class DecimalField(Field): 
    215246    default_error_messages = { 
    class DecimalField(Field): 
    226257        self.max_digits, self.decimal_places = max_digits, decimal_places 
    227258        Field.__init__(self, *args, **kwargs) 
    228259 
    229     def clean(self, value): 
     260    def to_python(self, value): 
    230261        """ 
    231262        Validates that the input is a decimal number. Returns a Decimal 
    232263        instance. Returns None for empty values. Ensures that there are no more 
    233264        than max_digits in the number, and no more than decimal_places digits 
    234265        after the decimal point. 
    235266        """ 
    236         super(DecimalField, self).clean(value) 
    237         if not self.required and value in EMPTY_VALUES: 
     267        if value in EMPTY_VALUES: 
    238268            return None 
    239269        value = smart_str(value).strip() 
    240270        try: 
    241             value = Decimal(value) 
     271            return Decimal(value) 
    242272        except DecimalException: 
    243273            raise ValidationError(self.error_messages['invalid']) 
     274 
     275    def validate(self, value): 
     276        super(DecimalField, self).validate(value) 
     277        if value is None: return 
    244278        pieces = str(value).lstrip("-").split('.') 
    245279        decimals = (len(pieces) == 2) and len(pieces[1]) or 0 
    246280        digits = len(pieces[0]) 
    class DecimalField(Field): 
    254288            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 
    255289        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): 
    256290            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 
    257         return value 
    258291 
    259292DEFAULT_DATE_INPUT_FORMATS = ( 
    260293    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' 
    class DateField(Field): 
    273306        super(DateField, self).__init__(*args, **kwargs) 
    274307        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS 
    275308 
    276     def clean(self, value): 
     309    def to_python(self, value): 
    277310        """ 
    278311        Validates that the input can be converted to a date. Returns a Python 
    279312        datetime.date object. 
    280313        """ 
    281         super(DateField, self).clean(value) 
    282314        if value in EMPTY_VALUES: 
    283315            return None 
    284316        if isinstance(value, datetime.datetime): 
    class TimeField(Field): 
    306338        super(TimeField, self).__init__(*args, **kwargs) 
    307339        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS 
    308340 
    309     def clean(self, value): 
     341    def to_python(self, value): 
    310342        """ 
    311343        Validates that the input can be converted to a time. Returns a Python 
    312344        datetime.time object. 
    313345        """ 
    314         super(TimeField, self).clean(value) 
    315346        if value in EMPTY_VALUES: 
    316347            return None 
    317348        if isinstance(value, datetime.time): 
    class DateTimeField(Field): 
    345376        super(DateTimeField, self).__init__(*args, **kwargs) 
    346377        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS 
    347378 
    348     def clean(self, value): 
     379    def to_python(self, value): 
    349380        """ 
    350381        Validates that the input can be converted to a datetime. Returns a 
    351382        Python datetime.datetime object. 
    352383        """ 
    353         super(DateTimeField, self).clean(value) 
    354384        if value in EMPTY_VALUES: 
    355385            return None 
    356386        if isinstance(value, datetime.datetime): 
    class RegexField(CharField): 
    387417            regex = re.compile(regex) 
    388418        self.regex = regex 
    389419 
    390     def clean(self, value): 
     420    def validate(self, value): 
    391421        """ 
    392422        Validates that the input matches the regular expression. Returns a 
    393423        Unicode object. 
    394424        """ 
    395         value = super(RegexField, self).clean(value) 
     425        super(RegexField, self).validate(value) 
    396426        if value == u'': 
    397             return value 
     427            return 
    398428        if not self.regex.search(value): 
    399429            raise ValidationError(self.error_messages['invalid']) 
    400         return value 
    401430 
    402431email_re = re.compile( 
    403432    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom 
    404433    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 
    405434    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain 
    406435 
    407 class EmailField(RegexField): 
     436class EmailField(CharField): 
    408437    default_error_messages = { 
    409438        'invalid': _(u'Enter a valid e-mail address.'), 
    410439    } 
    411  
    412     def __init__(self, max_length=None, min_length=None, *args, **kwargs): 
    413         RegexField.__init__(self, email_re, max_length, min_length, *args, 
    414                             **kwargs) 
     440    validators = [validators.validate_email] 
    415441 
    416442try: 
    417443    from django.conf import settings 
    class FileField(Field): 
    425451    widget = FileInput 
    426452    default_error_messages = { 
    427453        'invalid': _(u"No file was submitted. Check the encoding type on the form."), 
    428         'missing': _(u"No file was submitted."), 
    429454        'empty': _(u"The submitted file is empty."), 
    430455    } 
    431456 
    432     def __init__(self, *args, **kwargs): 
    433         super(FileField, self).__init__(*args, **kwargs) 
    434  
    435     def clean(self, data, initial=None): 
    436         super(FileField, self).clean(initial or data) 
     457    def to_python(self, data, initial=None): 
    437458        if not self.required and data in EMPTY_VALUES: 
    438459            return None 
    439460        elif not data and initial: 
    class FileField(Field): 
    447468                category = DeprecationWarning, 
    448469                stacklevel = 2 
    449470            ) 
     471            if not data: 
     472                raise ValidationError(self.error_messages['invalid']) 
    450473            data = UploadedFile(data['filename'], data['content']) 
    451474 
    452475        try: 
    class FileField(Field): 
    459482            raise ValidationError(self.error_messages['invalid']) 
    460483        if not file_size: 
    461484            raise ValidationError(self.error_messages['empty']) 
    462  
    463485        return data 
    464486 
     487    def clean(self, value, initial=None): 
     488        "overriden clean to provide extra argument initial" 
     489        value = self.to_python(value, initial) 
     490        self.validate(value) 
     491        return value 
     492 
    465493class ImageField(FileField): 
    466494    default_error_messages = { 
    467495        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), 
    468496    } 
    469497 
    470     def clean(self, data, initial=None): 
    471         """ 
    472         Checks that the file-upload field data contains a valid image (GIF, JPG, 
    473         PNG, possibly others -- whatever the Python Imaging Library supports). 
    474         """ 
    475         f = super(ImageField, self).clean(data, initial) 
    476         if f is None: 
    477             return None 
    478         elif not data and initial: 
    479             return initial 
     498    def validate(self, data): 
     499        super(ImageField, self).validate(data) 
     500        if data is None: 
     501            return 
    480502        from PIL import Image 
    481  
    482503        # We need to get a file object for PIL. We might have a path or we might 
    483504        # have to read the data into memory. 
    484505        if hasattr(data, 'temporary_file_path'): 
    class ImageField(FileField): 
    486507        else: 
    487508            if hasattr(data, 'read'): 
    488509                file = StringIO(data.read()) 
     510            elif isinstance(data, UploadedFile): 
     511                file = StringIO(data.data.read()) 
     512            elif hasattr(data, 'seek') and callable(data.seek): 
     513                data.seek(0) 
     514                file = data 
    489515            else: 
    490                 file = StringIO(data['content']) 
     516                file = data 
    491517 
    492518        try: 
    493519            # load() is the only method that can spot a truncated JPEG, 
    class ImageField(FileField): 
    511537            raise 
    512538        except Exception: # Python Imaging Library doesn't recognize it as an image 
    513539            raise ValidationError(self.error_messages['invalid_image']) 
    514         if hasattr(f, 'seek') and callable(f.seek): 
    515             f.seek(0) 
    516         return f 
    517540 
    518541url_re = re.compile( 
    519542    r'^https?://' # http:// or https:// 
    class URLField(RegexField): 
    536559        self.verify_exists = verify_exists 
    537560        self.user_agent = validator_user_agent 
    538561 
    539     def clean(self, value): 
     562    def to_python(self, value): 
     563        value = super(URLField, self).to_python(value) 
    540564        # If no URL scheme given, assume http:// 
    541565        if value and '://' not in value: 
    542566            value = u'http://%s' % value 
    543567        # If no URL path given, assume / 
    544         if value and not urlparse.urlsplit(value).path: 
     568        if value and not urlparse.urlsplit(value)[2]: 
    545569            value += '/' 
    546         value = super(URLField, self).clean(value) 
     570        return value 
     571 
     572    def validate(self, value): 
     573        super(URLField, self).validate(value) 
    547574        if value == u'': 
    548             return value 
     575            return 
    549576        if self.verify_exists: 
    550             import urllib2 
    551             headers = { 
    552                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
    553                 "Accept-Language": "en-us,en;q=0.5", 
    554                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", 
    555                 "Connection": "close", 
    556                 "User-Agent": self.user_agent, 
    557             } 
    558             try: 
    559                 req = urllib2.Request(value, None, headers) 
    560                 u = urllib2.urlopen(req) 
    561             except ValueError: 
    562                 raise ValidationError(self.error_messages['invalid']) 
    563             except: # urllib2.URLError, httplib.InvalidURL, etc. 
    564                 raise ValidationError(self.error_messages['invalid_link']) 
    565         return value 
     577            # we cannot put this in self.validators because its conditional 
     578            validators.validate_existing_url(value, self.error_messages) 
    566579 
    567580class BooleanField(Field): 
    568581    widget = CheckboxInput 
    569582 
    570     def clean(self, value): 
     583    def to_python(self, value): 
    571584        """Returns a Python boolean object.""" 
    572585        # Explicitly check for the string 'False', which is what a hidden field 
    573586        # will submit for False. Because bool("True") == True, we don't need to 
    class BooleanField(Field): 
    576589            value = False 
    577590        else: 
    578591            value = bool(value) 
    579         super(BooleanField, self).clean(value) 
    580         if not value and self.required: 
    581             raise ValidationError(self.error_messages['required']) 
    582592        return value 
    583593 
     594    def validate(self, value): 
     595        if self.required and not value: 
     596            raise ValidationError(self.error_messages['required']) 
     597 
     598 
    584599class NullBooleanField(BooleanField): 
    585600    """ 
    586601    A field whose valid values are None, True and False. Invalid values are 
    587602    cleaned to None. 
     603 
     604    Note that validation doesn't apply here. 
    588605    """ 
    589606    widget = NullBooleanSelect 
    590607 
    591     def clean(self, value): 
     608    def to_python(self, value): 
    592609        return {True: True, False: False}.get(value, None) 
    593610 
     611    def validate(self, value): 
     612        pass 
     613 
     614 
    594615class ChoiceField(Field): 
    595616    widget = Select 
    596617    default_error_messages = { 
    class ChoiceField(Field): 
    614635 
    615636    choices = property(_get_choices, _set_choices) 
    616637 
    617     def clean(self, value): 
    618         """ 
    619         Validates that the input is in self.choices. 
    620         """ 
    621         value = super(ChoiceField, self).clean(value) 
    622         if value in EMPTY_VALUES: 
    623             value = u'' 
    624         value = smart_unicode(value) 
    625         if value == u'': 
    626             return value 
     638    def validate(self, value): 
     639        super(ChoiceField, self).validate(value) 
     640        if value is None and not self.required: 
     641            return 
    627642        if not self.valid_value(value): 
    628643            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 
    629         return value 
    630644 
    631645    def valid_value(self, value): 
    632646        "Check to see if the provided value is a valid choice" 
    class MultipleChoiceField(ChoiceField): 
    649663        'invalid_list': _(u'Enter a list of values.'), 
    650664    } 
    651665 
    652     def clean(self, value): 
     666    def to_python(self, value): 
    653667        """ 
    654668        Validates that the input is a list or tuple. 
    655669        """ 
    656         if self.required and not value: 
    657             raise ValidationError(self.error_messages['required']) 
    658         elif not self.required and not value: 
     670        if not value: 
    659671            return [] 
    660672        if not isinstance(value, (list, tuple)): 
    661673            raise ValidationError(self.error_messages['invalid_list']) 
    662         new_value = [smart_unicode(val) for val in value] 
     674        return [smart_unicode(val) for val in value] 
     675 
     676    def validate(self, value): 
    663677        # Validate that each value in the value list is in self.choices. 
    664         for val in new_value: 
     678        if self.required and value == []: 
     679            raise ValidationError(self.error_messages['required']) 
     680        for val in value: 
    665681            if not self.valid_value(val): 
    666682                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) 
    667         return new_value 
    668683 
    669684class ComboField(Field): 
    670685    """ 
    class ComboField(Field): 
    679694            f.required = False 
    680695        self.fields = fields 
    681696 
    682     def clean(self, value): 
     697    def to_python(self, value): 
     698        for field in self.fields: 
     699            value = field.to_python(value) 
     700        return value 
     701 
     702    def validate(self, value): 
    683703        """ 
    684704        Validates the given value against all of self.fields, which is a 
    685705        list of Field instances. 
    686706        """ 
    687         super(ComboField, self).clean(value) 
     707        super(ComboField, self).validate(value) 
    688708        for field in self.fields: 
    689             value = field.clean(value) 
    690         return value 
     709            field.validate(value) 
    691710 
    692711class MultiValueField(Field): 
    693712    """ 
    class MultiValueField(Field): 
    719738            f.required = False 
    720739        self.fields = fields 
    721740 
    722     def clean(self, value): 
     741    def to_python(self, value): 
    723742        """ 
    724743        Validates every value in the given list. A value is validated against 
    725744        the corresponding Field in self.fields. 
    class MultiValueField(Field): 
    733752        if not value or isinstance(value, (list, tuple)): 
    734753            if not value or not [v for v in value if v not in EMPTY_VALUES]: 
    735754                if self.required: 
    736                     raise ValidationError(self.error_messages['required']) 
     755                    return None 
    737756                else: 
    738757                    return self.compress([]) 
    739758        else: 
    740759            raise ValidationError(self.error_messages['invalid']) 
     760 
    741761        for i, field in enumerate(self.fields): 
    742762            try: 
    743763                field_value = value[i] 
    class SplitDateTimeField(MultiValueField): 
    821841            return datetime.datetime.combine(*data_list) 
    822842        return None 
    823843 
    824 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}$') 
    825  
    826 class IPAddressField(RegexField): 
     844class IPAddressField(CharField): 
    827845    default_error_messages = { 
    828846        'invalid': _(u'Enter a valid IPv4 address.'), 
    829847    } 
    830  
    831     def __init__(self, *args, **kwargs): 
    832         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) 
     848    validators = [validators.validate_ip_address4] 
  • django/forms/forms.py

    diff --git a/django/forms/forms.py b/django/forms/forms.py
    index 753ee25..05936f2 100644
    a b from django.utils.datastructures import SortedDict 
    88from django.utils.html import escape 
    99from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode 
    1010from django.utils.safestring import mark_safe 
     11from django.forms import ValidationError, NON_FIELD_ERRORS 
    1112 
    1213from fields import Field, FileField 
    1314from widgets import Media, media_property, TextInput, Textarea 
    14 from util import flatatt, ErrorDict, ErrorList, ValidationError 
     15from util import flatatt, ErrorDict, ErrorList 
    1516 
    1617__all__ = ('BaseForm', 'Form') 
    1718 
    18 NON_FIELD_ERRORS = '__all__' 
    1919 
    2020def pretty_name(name): 
    2121    "Converts 'first_name' to 'First name'" 
    class BaseForm(StrAndUnicode): 
    217217                else: 
    218218                    value = field.clean(value) 
    219219                self.cleaned_data[name] = value 
     220                # FIXME deprecated - keeping this here for backwards compatibility 
    220221                if hasattr(self, 'clean_%s' % name): 
    221222                    value = getattr(self, 'clean_%s' % name)() 
    222223                    self.cleaned_data[name] = value 
     224 
     225                if hasattr(self, 'validate_%s' % name): 
     226                    getattr(self, 'validate_%s' % name)(value) 
    223227            except ValidationError, e: 
    224                 self._errors[name] = e.messages 
     228                self._errors[name] = ErrorList(e.messages) 
    225229                if name in self.cleaned_data: 
    226230                    del self.cleaned_data[name] 
    227231        try: 
    228             self.cleaned_data = self.clean() 
     232            self.validate() 
    229233        except ValidationError, e: 
    230             self._errors[NON_FIELD_ERRORS] = e.messages 
     234            if hasattr(e, 'message_dict'): 
     235                for k, v in e.message_dict.items(): 
     236                    self._errors.setdefault(k, []).extend(v) 
     237            else: 
     238                self._errors[NON_FIELD_ERRORS] = ErrorList(e.messages) 
    231239        if self._errors: 
    232240            delattr(self, 'cleaned_data') 
    233241 
    234242    def clean(self): 
    235243        """ 
     244        FIXME: deprecated, use validate() instead 
     245 
    236246        Hook for doing any extra form-wide cleaning after Field.clean() been 
    237247        called on every field. Any ValidationError raised by this method will 
    238248        not be associated with a particular field; it will have a special-case 
    class BaseForm(StrAndUnicode): 
    274284        return media 
    275285    media = property(_get_media) 
    276286 
     287    def validate(self): 
     288        self.cleaned_data = self.clean() 
     289 
    277290    def is_multipart(self): 
    278291        """ 
    279292        Returns True if the form needs to be multipart-encrypted, i.e. it has 
  • django/forms/formsets.py

    diff --git a/django/forms/formsets.py b/django/forms/formsets.py
    index 2f13bf5..a0a9199 100644
    a b from django.utils.encoding import StrAndUnicode 
    33from django.utils.safestring import mark_safe 
    44from fields import IntegerField, BooleanField 
    55from widgets import Media, HiddenInput 
    6 from util import ErrorList, ValidationError 
     6from django.core.exceptions import ValidationError 
     7from util import ErrorList 
    78 
    89__all__ = ('BaseFormSet', 'all_valid') 
    910 
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index 56e19e7..c89f110 100644
    a b from warnings import warn 
    88from django.utils.translation import ugettext_lazy as _ 
    99from django.utils.encoding import smart_unicode 
    1010from django.utils.datastructures import SortedDict 
     11from django.core.exceptions import ImproperlyConfigured, ValidationError 
     12from django.forms.util import ErrorList 
    1113 
    12 from util import ValidationError, ErrorList 
    1314from forms import BaseForm, get_declared_fields 
    1415from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES 
    1516from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput 
    class BaseModelForm(BaseForm): 
    262263        BaseForm.__init__(self, data, files, auto_id, prefix, object_data, 
    263264                          error_class, label_suffix, empty_permitted) 
    264265 
     266    def validate(self): 
     267        super(BaseModelForm, self).validate() 
     268        if self._errors: 
     269            return 
     270        self.instance.clean(self.cleaned_data) 
     271 
    265272    def save(self, commit=True): 
    266273        """ 
    267274        Saves this ``form``'s cleaned_data into model instance 
    class ModelChoiceField(ChoiceField): 
    564571 
    565572    choices = property(_get_choices, ChoiceField._set_choices) 
    566573 
    567     def clean(self, value): 
    568         Field.clean(self, value) 
    569         if value in EMPTY_VALUES: 
     574    def to_python(self, value): 
     575        if self.required and value in EMPTY_VALUES: 
     576            raise ValidationError(self.error_messages['required']) 
     577        elif value in EMPTY_VALUES: 
    570578            return None 
    571579        try: 
    572580            value = self.queryset.get(pk=value) 
    class ModelChoiceField(ChoiceField): 
    574582            raise ValidationError(self.error_messages['invalid_choice']) 
    575583        return value 
    576584 
     585    def validate(self, value): 
     586        pass 
     587 
    577588class ModelMultipleChoiceField(ModelChoiceField): 
    578589    """A MultipleChoiceField whose choices are a model QuerySet.""" 
    579590    hidden_widget = MultipleHiddenInput 
    class ModelMultipleChoiceField(ModelChoiceField): 
    590601            cache_choices, required, widget, label, initial, help_text, 
    591602            *args, **kwargs) 
    592603 
    593     def clean(self, value): 
     604    def to_python(self, value): 
    594605        if self.required and not value: 
    595606            raise ValidationError(self.error_messages['required']) 
    596607        elif not self.required and not value: 
    class ModelMultipleChoiceField(ModelChoiceField): 
    606617            else: 
    607618                final_values.append(obj) 
    608619        return final_values 
     620 
  • django/forms/util.py

    diff --git a/django/forms/util.py b/django/forms/util.py
    index 3d80ad2..6355949 100644
    a b class ErrorList(list, StrAndUnicode): 
    3636    def __unicode__(self): 
    3737        return self.as_ul() 
    3838 
     39    def __repr__(self): 
     40        return repr([force_unicode(e) for e in self]) 
     41 
    3942    def as_ul(self): 
    4043        if not self: return u'' 
    4144        return mark_safe(u'<ul class="errorlist">%s</ul>' 
    class ErrorList(list, StrAndUnicode): 
    4447    def as_text(self): 
    4548        if not self: return u'' 
    4649        return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) 
    47  
    48     def __repr__(self): 
    49         return repr([force_unicode(e) for e in self]) 
    50  
    51 class ValidationError(Exception): 
    52     def __init__(self, message): 
    53         """ 
    54         ValidationError can be passed any object that can be printed (usually 
    55         a string) or a list of objects. 
    56         """ 
    57         if isinstance(message, list): 
    58             self.messages = ErrorList([smart_unicode(msg) for msg in message]) 
    59         else: 
    60             message = smart_unicode(message) 
    61             self.messages = ErrorList([message]) 
    62  
    63     def __str__(self): 
    64         # This is needed because, without a __str__(), printing an exception 
    65         # instance would result in this: 
    66         # AttributeError: ValidationError instance has no attribute 'args' 
    67         # See http://www.python.org/doc/current/tut/node10.html#handling 
    68         return repr(self.messages) 
  • django/newforms/__init__.py

    diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
    index 5f319f8..b437d84 100644
    a b warnings.warn( 
    44    message = "django.newforms is no longer new. Import django.forms instead.", 
    55    stacklevel = 2 
    66) 
    7 from django.forms import * 
    8  No newline at end of file 
     7from django.forms import * 
  • django/oldforms/__init__.py

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

    diff --git a/django/oldforms/validators.py b/django/oldforms/validators.py
    new file mode 100644
    index 0000000..7500347
    - +  
     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.exceptions 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    try: 
     146        date(year, month, day) 
     147    except ValueError, e: 
     148        msg = _('Invalid date: %s') % _(str(e)) 
     149        raise ValidationError, msg 
     150 
     151def isValidANSIDate(field_data, all_data): 
     152    # DONE 
     153    if not ansi_date_re.search(field_data): 
     154        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.') 
     155    _isValidDate(field_data) 
     156 
     157def isValidANSITime(field_data, all_data): 
     158    # DONE 
     159    if not ansi_time_re.search(field_data): 
     160        raise ValidationError, _('Enter a valid time in HH:MM format.') 
     161 
     162def isValidANSIDatetime(field_data, all_data): 
     163    # DONE 
     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]) 
     167 
     168def isValidEmail(field_data, all_data): 
     169    # DONE 
     170    if not email_re.search(field_data): 
     171        raise ValidationError, _('Enter a valid e-mail address.') 
     172 
     173def isValidImage(field_data, all_data): 
     174    """ 
     175    Checks that the file-upload field data contains a valid image (GIF, JPG, 
     176    PNG, possibly others -- whatever the Python Imaging Library supports). 
     177    """ 
     178    from PIL import Image 
     179    from cStringIO import StringIO 
     180    try: 
     181        content = field_data.read() 
     182    except TypeError: 
     183        raise ValidationError, _("No file was submitted. Check the encoding type on the form.") 
     184    try: 
     185        # load() is the only method that can spot a truncated JPEG, 
     186        #  but it cannot be called sanely after verify() 
     187        trial_image = Image.open(StringIO(content)) 
     188        trial_image.load() 
     189        # verify() is the only method that can spot a corrupt PNG, 
     190        #  but it must be called immediately after the constructor 
     191        trial_image = Image.open(StringIO(content)) 
     192        trial_image.verify() 
     193    except Exception: # Python Imaging Library doesn't recognize it as an image 
     194        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.") 
     195 
     196def isValidImageURL(field_data, all_data): 
     197    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png')) 
     198    try: 
     199        uc(field_data, all_data) 
     200    except URLMimeTypeCheck.InvalidContentType: 
     201        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data 
     202 
     203def isValidPhone(field_data, all_data): 
     204    if not phone_re.search(field_data): 
     205        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data 
     206 
     207def isValidQuicktimeVideoURL(field_data, all_data): 
     208    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)" 
     209    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',)) 
     210    try: 
     211        uc(field_data, all_data) 
     212    except URLMimeTypeCheck.InvalidContentType: 
     213        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data 
     214 
     215def isValidURL(field_data, all_data): 
     216    if not url_re.search(field_data): 
     217        raise ValidationError, _("A valid URL is required.") 
     218 
     219def isValidHTML(field_data, all_data): 
     220    import urllib, urllib2 
     221    try: 
     222        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'})) 
     223    except: 
     224        # Validator or Internet connection is unavailable. Fail silently. 
     225        return 
     226    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid') 
     227    if html_is_valid: 
     228        return 
     229    from xml.dom.minidom import parseString 
     230    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')] 
     231    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages) 
     232 
     233def isWellFormedXml(field_data, all_data): 
     234    from xml.dom.minidom import parseString 
     235    try: 
     236        parseString(field_data) 
     237    except Exception, e: # Naked except because we're not sure what will be thrown 
     238        raise ValidationError, _("Badly formed XML: %s") % str(e) 
     239 
     240def isWellFormedXmlFragment(field_data, all_data): 
     241    isWellFormedXml('<root>%s</root>' % field_data, all_data) 
     242 
     243def isExistingURL(field_data, all_data): 
     244    try: 
     245        headers = { 
     246            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
     247            "Accept-Language" : "en-us,en;q=0.5", 
     248            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", 
     249            "Connection" : "close", 
     250            "User-Agent": settings.URL_VALIDATOR_USER_AGENT 
     251            } 
     252        req = urllib2.Request(field_data,None, headers) 
     253        u = urllib2.urlopen(req) 
     254    except ValueError: 
     255        raise ValidationError, _("Invalid URL: %s") % field_data 
     256    except urllib2.HTTPError, e: 
     257        # 401s are valid; they just mean authorization is required. 
     258        # 301 and 302 are redirects; they just mean look somewhere else. 
     259        if str(e.code) not in ('401','301','302'): 
     260            raise ValidationError, _("The URL %s is a broken link.") % field_data 
     261    except: # urllib2.URLError, httplib.InvalidURL, etc. 
     262        raise ValidationError, _("The URL %s is a broken link.") % field_data 
     263 
     264def isValidUSState(field_data, all_data): 
     265    "Checks that the given string is a valid two-letter U.S. state abbreviation" 
     266    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'] 
     267    if field_data.upper() not in states: 
     268        raise ValidationError, _("Enter a valid U.S. state abbreviation.") 
     269 
     270def hasNoProfanities(field_data, all_data): 
     271    """ 
     272    Checks that the given string has no profanities in it. This does a simple 
     273    check for whether each profanity exists within the string, so 'fuck' will 
     274    catch 'motherfucker' as well. Raises a ValidationError such as: 
     275        Watch your mouth! The words "f--k" and "s--t" are not allowed here. 
     276    """ 
     277    field_data = field_data.lower() # normalize 
     278    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data] 
     279    if words_seen: 
     280        from django.utils.text import get_text_list 
     281        plural = len(words_seen) 
     282        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.", 
     283            "Watch your mouth! The words %s are not allowed here.", plural) % \ 
     284            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and')) 
     285 
     286class AlwaysMatchesOtherField(object): 
     287    def __init__(self, other_field_name, error_message=None): 
     288        self.other = other_field_name 
     289        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other) 
     290        self.always_test = True 
     291 
     292    def __call__(self, field_data, all_data): 
     293        if field_data != all_data[self.other]: 
     294            raise ValidationError, self.error_message 
     295 
     296class ValidateIfOtherFieldEquals(object): 
     297    def __init__(self, other_field, other_value, validator_list): 
     298        self.other_field, self.other_value = other_field, other_value 
     299        self.validator_list = validator_list 
     300        self.always_test = True 
     301 
     302    def __call__(self, field_data, all_data): 
     303        if self.other_field in all_data and all_data[self.other_field] == self.other_value: 
     304            for v in self.validator_list: 
     305                v(field_data, all_data) 
     306 
     307class RequiredIfOtherFieldNotGiven(object): 
     308    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")): 
     309        self.other, self.error_message = other_field_name, error_message 
     310        self.always_test = True 
     311 
     312    def __call__(self, field_data, all_data): 
     313        if not all_data.get(self.other, False) and not field_data: 
     314            raise ValidationError, self.error_message 
     315 
     316class RequiredIfOtherFieldsGiven(object): 
     317    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): 
     318        self.other, self.error_message = other_field_names, error_message 
     319        self.always_test = True 
     320 
     321    def __call__(self, field_data, all_data): 
     322        for field in self.other: 
     323            if all_data.get(field, False) and not field_data: 
     324                raise ValidationError, self.error_message 
     325 
     326class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven): 
     327    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list." 
     328    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): 
     329        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message) 
     330 
     331class RequiredIfOtherFieldEquals(object): 
     332    def __init__(self, other_field, other_value, error_message=None, other_label=None): 
     333        self.other_field = other_field 
     334        self.other_value = other_value 
     335        other_label = other_label or other_value 
     336        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), { 
     337            'field': other_field, 'value': other_label}) 
     338        self.always_test = True 
     339 
     340    def __call__(self, field_data, all_data): 
     341        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data: 
     342            raise ValidationError(self.error_message) 
     343 
     344class RequiredIfOtherFieldDoesNotEqual(object): 
     345    def __init__(self, other_field, other_value, other_label=None, error_message=None): 
     346        self.other_field = other_field 
     347        self.other_value = other_value 
     348        other_label = other_label or other_value 
     349        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), { 
     350            'field': other_field, 'value': other_label}) 
     351        self.always_test = True 
     352 
     353    def __call__(self, field_data, all_data): 
     354        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data: 
     355            raise ValidationError(self.error_message) 
     356 
     357class IsLessThanOtherField(object): 
     358    def __init__(self, other_field_name, error_message): 
     359        self.other, self.error_message = other_field_name, error_message 
     360 
     361    def __call__(self, field_data, all_data): 
     362        if field_data > all_data[self.other]: 
     363            raise ValidationError, self.error_message 
     364 
     365class UniqueAmongstFieldsWithPrefix(object): 
     366    def __init__(self, field_name, prefix, error_message): 
     367        self.field_name, self.prefix = field_name, prefix 
     368        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.") 
     369 
     370    def __call__(self, field_data, all_data): 
     371        for field_name, value in all_data.items(): 
     372            if field_name != self.field_name and value == field_data: 
     373                raise ValidationError, self.error_message 
     374 
     375class NumberIsInRange(object): 
     376    """ 
     377    Validator that tests if a value is in a range (inclusive). 
     378    """ 
     379    def __init__(self, lower=None, upper=None, error_message=''): 
     380        self.lower, self.upper = lower, upper 
     381        if not error_message: 
     382            if lower and upper: 
     383                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper} 
     384            elif lower: 
     385                self.error_message = _("This value must be at least %s.") % lower 
     386            elif upper: 
     387                self.error_message = _("This value must be no more than %s.") % upper 
     388        else: 
     389            self.error_message = error_message 
     390 
     391    def __call__(self, field_data, all_data): 
     392        # Try to make the value numeric. If this fails, we assume another 
     393        # validator will catch the problem. 
     394        try: 
     395            val = float(field_data) 
     396        except ValueError: 
     397            return 
     398 
     399        # Now validate 
     400        if self.lower and self.upper and (val < self.lower or val > self.upper): 
     401            raise ValidationError(self.error_message) 
     402        elif self.lower and val < self.lower: 
     403            raise ValidationError(self.error_message) 
     404        elif self.upper and val > self.upper: 
     405            raise ValidationError(self.error_message) 
     406 
     407class IsAPowerOf(object): 
     408    """ 
     409    Usage: If you create an instance of the IsPowerOf validator: 
     410        v = IsAPowerOf(2) 
     411 
     412    The following calls will succeed: 
     413        v(4, None) 
     414        v(8, None) 
     415        v(16, None) 
     416 
     417    But this call: 
     418        v(17, None) 
     419    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']" 
     420    """ 
     421    def __init__(self, power_of): 
     422        self.power_of = power_of 
     423 
     424    def __call__(self, field_data, all_data): 
     425        from math import log 
     426        val = log(int(field_data)) / log(self.power_of) 
     427        if val != int(val): 
     428            raise ValidationError, _("This value must be a power of %s.") % self.power_of 
     429 
     430class IsValidDecimal(object): 
     431    def __init__(self, max_digits, decimal_places): 
     432        self.max_digits, self.decimal_places = max_digits, decimal_places 
     433 
     434    def __call__(self, field_data, all_data): 
     435        try: 
     436            val = Decimal(field_data) 
     437        except DecimalException: 
     438            raise ValidationError, _("Please enter a valid decimal number.") 
     439 
     440        pieces = str(val).lstrip("-").split('.') 
     441        decimals = (len(pieces) == 2) and len(pieces[1]) or 0 
     442        digits = len(pieces[0]) 
     443 
     444        if digits + decimals > self.max_digits: 
     445            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.", 
     446                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits 
     447        if digits > (self.max_digits - self.decimal_places): 
     448            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.", 
     449                "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) 
     450        if decimals > self.decimal_places: 
     451            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.", 
     452                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places 
     453 
     454def isValidFloat(field_data, all_data): 
     455    data = smart_str(field_data) 
     456    try: 
     457        float(data) 
     458    except ValueError: 
     459        raise ValidationError, _("Please enter a valid floating point number.") 
     460 
     461class HasAllowableSize(object): 
     462    """ 
     463    Checks that the file-upload field data is a certain size. min_size and 
     464    max_size are measurements in bytes. 
     465    """ 
     466    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None): 
     467        self.min_size, self.max_size = min_size, max_size 
     468        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) 
     469        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) 
     470 
     471    def __call__(self, field_data, all_data): 
     472        try: 
     473            content = field_data.read() 
     474        except TypeError: 
     475            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.") 
     476        if self.min_size is not None and len(content) < self.min_size: 
     477            raise ValidationError, self.min_error_message 
     478        if self.max_size is not None and len(content) > self.max_size: 
     479            raise ValidationError, self.max_error_message 
     480 
     481class MatchesRegularExpression(object): 
     482    """ 
     483    Checks that the field matches the given regular-expression. The regex 
     484    should be in string format, not already compiled. 
     485    """ 
     486    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")): 
     487        self.regexp = re.compile(regexp) 
     488        self.error_message = error_message 
     489 
     490    def __call__(self, field_data, all_data): 
     491        if not self.regexp.search(field_data): 
     492            raise ValidationError(self.error_message) 
     493 
     494class AnyValidator(object): 
     495    """ 
     496    This validator tries all given validators. If any one of them succeeds, 
     497    validation passes. If none of them succeeds, the given message is thrown 
     498    as a validation error. The message is rather unspecific, so it's best to 
     499    specify one on instantiation. 
     500    """ 
     501    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")): 
     502        if validator_list is None: validator_list = [] 
     503        self.validator_list = validator_list 
     504        self.error_message = error_message 
     505        for v in validator_list: 
     506            if hasattr(v, 'always_test'): 
     507                self.always_test = True 
     508 
     509    def __call__(self, field_data, all_data): 
     510        for v in self.validator_list: 
     511            try: 
     512                v(field_data, all_data) 
     513                return 
     514            except ValidationError, e: 
     515                pass 
     516        raise ValidationError(self.error_message) 
     517 
     518class URLMimeTypeCheck(object): 
     519    "Checks that the provided URL points to a document with a listed mime type" 
     520    class CouldNotRetrieve(ValidationError): 
     521        pass 
     522    class InvalidContentType(ValidationError): 
     523        pass 
     524 
     525    def __init__(self, mime_type_list): 
     526        self.mime_type_list = mime_type_list 
     527 
     528    def __call__(self, field_data, all_data): 
     529        import urllib2 
     530        try: 
     531            isValidURL(field_data, all_data) 
     532        except ValidationError: 
     533            raise 
     534        try: 
     535            info = urllib2.urlopen(field_data).info() 
     536        except (urllib2.HTTPError, urllib2.URLError): 
     537            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data 
     538        content_type = info['content-type'] 
     539        if content_type not in self.mime_type_list: 
     540            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % { 
     541                'url': field_data, 'contenttype': content_type} 
     542 
     543class RelaxNGCompact(object): 
     544    "Validate against a Relax NG compact schema" 
     545    def __init__(self, schema_path, additional_root_element=None): 
     546        self.schema_path = schema_path 
     547        self.additional_root_element = additional_root_element 
     548 
     549    def __call__(self, field_data, all_data): 
     550        import os, tempfile 
     551        if self.additional_root_element: 
     552            field_data = '<%(are)s>%(data)s\n</%(are)s>' % { 
     553                'are': self.additional_root_element, 
     554                'data': field_data 
     555            } 
     556        filename = tempfile.mktemp() # Insecure, but nothing else worked 
     557        fp = open(filename, 'w') 
     558        fp.write(field_data) 
     559        fp.close() 
     560        if not os.path.exists(settings.JING_PATH): 
     561            raise Exception, "%s not found!" % settings.JING_PATH 
     562        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename)) 
     563        errors = [line.strip() for line in p.readlines()] 
     564        p.close() 
     565        os.unlink(filename) 
     566        display_errors = [] 
     567        lines = field_data.split('\n') 
     568        for error in errors: 
     569            ignored, line, level, message = error.split(':', 3) 
     570            # Scrape the Jing error messages to reword them more nicely. 
     571            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message) 
     572            if m: 
     573                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \ 
     574                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]}) 
     575                continue 
     576            if message.strip() == 'text not allowed here': 
     577                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \ 
     578                    {'line':line, 'start':lines[int(line) - 1][:30]}) 
     579                continue 
     580            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message) 
     581            if m: 
     582                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \ 
     583                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
     584                continue 
     585            m = re.search(r'\s*unknown element "(.*?)"', message) 
     586            if m: 
     587                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \ 
     588                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
     589                continue 
     590            if message.strip() == 'required attributes missing': 
     591                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \ 
     592                    {'line':line, 'start':lines[int(line) - 1][:30]}) 
     593                continue 
     594            m = re.search(r'\s*bad value for attribute "(.*?)"', message) 
     595            if m: 
     596                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \ 
     597                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
     598                continue 
     599            # Failing all those checks, use the default error message. 
     600            display_error = 'Line %s: %s [%s]' % (line, message, level.strip()) 
     601            display_errors.append(display_error) 
     602        if len(display_errors) > 0: 
     603            raise ValidationError, display_errors 
  • tests/modeltests/manipulators/models.py

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

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index be2a8ba..9d8f4ea 100644
    a b Create a new article, with categories, via the form. 
    487487...         model = Article 
    488488>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', 
    489489...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) 
     490>>> f.is_valid() 
     491True 
    490492>>> new_art = f.save() 
    491493>>> new_art.id 
    4924942 
    u'...test2.txt' 
    861863>>> instance.delete() 
    862864 
    863865# Test the non-required FileField 
    864  
     866# It should fail since the field IS required on the model 
    865867>>> f = TextFileForm(data={'description': u'Assistance'}) 
    866868>>> f.fields['file'].required = False 
    867869>>> f.is_valid() 
    868 True 
    869 >>> instance = f.save() 
    870 >>> instance.file 
    871 '' 
     870False 
     871>>> f.errors 
     872{'file': [u'This field is required.']} 
    872873 
    873874>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) 
    874875>>> f.is_valid() 
    u'...test2.png' 
    970971>>> f = ImageFileForm(data={'description': u'Test'}) 
    971972>>> f.fields['image'].required = False 
    972973>>> f.is_valid() 
    973 True 
    974 >>> instance = f.save() 
    975 >>> instance.image 
    976 '' 
     974False 
     975>>> f.errors 
     976{'image': [u'This field is required.']} 
     977 
    977978 
    978979>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) 
    979980>>> f.is_valid() 
  • tests/modeltests/validation/models.py

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

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

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

    diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
    index ec91b57..9b35551 100644
    a b ValidationError: [u'LENGTH 11, MAX LENGTH 10'] 
    204204 
    205205>>> e = {'required': 'REQUIRED'} 
    206206>>> e['invalid'] = 'INVALID' 
    207 >>> e['missing'] = 'MISSING' 
    208207>>> e['empty'] = 'EMPTY FILE' 
    209208>>> f = FileField(error_messages=e) 
    210209>>> f.clean('') 
    211210Traceback (most recent call last): 
    212211... 
    213 ValidationError: [u'REQUIRED'] 
     212ValidationError: [u'INVALID'] 
    214213>>> f.clean('abc') 
    215214Traceback (most recent call last): 
    216215... 
    217216ValidationError: [u'INVALID'] 
    218 >>> f.clean(SimpleUploadedFile('name', None)) 
     217>>> f.clean({}) 
    219218Traceback (most recent call last): 
    220219... 
    221 ValidationError: [u'EMPTY FILE'] 
    222 >>> f.clean(SimpleUploadedFile('name', '')) 
     220ValidationError: [u'INVALID'] 
     221>>> f.clean({'filename': 'name', 'content':''}) 
    223222Traceback (most recent call last): 
    224223... 
    225224ValidationError: [u'EMPTY FILE'] 
  • tests/regressiontests/forms/fields.py

    diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
    index 53364b8..34e96fe 100644
    a b Each Field's __init__() takes at least these parameters: 
    3232             field name, if the Field is part of a Form. 
    3333    initial -- A value to use in this Field's initial display. This value is 
    3434               *not* used as a fallback if data isn't given. 
     35    validators -- Optional list of additional validator functions 
    3536 
    3637Other than that, the Field subclasses have class-specific options for 
    3738__init__(). For example, CharField has a max_length option. 
    u'1234567890' 
    104105>>> f.clean('1234567890a') 
    105106u'1234567890a' 
    106107 
     108# Custom validator functions ################################################## 
     109 
     110>>> def validator(value, error_dict={}): raise ValidationError('validator failed') 
     111>>> f = CharField(min_length=10, validators=[validator]) 
     112>>> f.clean('aa') 
     113Traceback (most recent call last): 
     114... 
     115ValidationError: [u'validator failed'] 
     116 
     117>>> def validator2(value, error_dict={}): raise ValidationError('validator2 failed') 
     118>>> f = CharField(min_length=10, validators=[validator, validator, validator2]) 
     119>>> f.clean('aa') 
     120Traceback (most recent call last): 
     121... 
     122ValidationError: [u'validator failed', u'validator failed', u'validator2 failed'] 
     123 
     124>>> class MyCharField(CharField): 
     125...     validators = [validator] 
     126>>> f = MyCharField() 
     127>>> f.clean('aa') 
     128Traceback (most recent call last): 
     129... 
     130ValidationError: [u'validator failed'] 
     131 
    107132# IntegerField ################################################################ 
    108133 
    109134>>> f = IntegerField() 
    ValidationError: [u'Ensure this value has at most 15 characters (it has 20).'] 
    748773>>> f.clean('') 
    749774Traceback (most recent call last): 
    750775... 
    751 ValidationError: [u'This field is required.'] 
     776ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 
    752777 
    753778>>> f.clean('', '') 
    754779Traceback (most recent call last): 
    755780... 
    756 ValidationError: [u'This field is required.'] 
     781ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 
    757782 
    758783>>> f.clean('', 'files/test1.pdf') 
    759784'files/test1.pdf' 
    ValidationError: [u'This field is required.'] 
    761786>>> f.clean(None) 
    762787Traceback (most recent call last): 
    763788... 
    764 ValidationError: [u'This field is required.'] 
     789ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 
    765790 
    766791>>> f.clean(None, '') 
    767792Traceback (most recent call last): 
    768793... 
    769 ValidationError: [u'This field is required.'] 
     794ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 
    770795 
    771796>>> f.clean(None, 'files/test2.pdf') 
    772797'files/test2.pdf' 
    ValidationError: [u'Select a valid choice. 3 is not one of the available choices 
    10121037 
    10131038>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False) 
    10141039>>> f.clean('') 
    1015 u'' 
    10161040>>> f.clean(None) 
    1017 u'' 
    10181041>>> f.clean(1) 
    10191042u'1' 
    10201043>>> f.clean('1') 
  • tests/regressiontests/forms/forms.py

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

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

    diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
    index 68c082c..5272b2c 100644
    a b u' id="header"' 
    1818u' class="news" title="Read this"' 
    1919>>> flatatt({}) 
    2020u'' 
    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> 
    5221"""