Code

Ticket #6845: 6845-against-8198.patch

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

see attached comment for details about this patch

  • AUTHORS

    commit ccf27602e0b9fa46bbe3a246a45ae7524ed574be
    Author: Honza Král <Honza.Kral@gmail.com>
    Date:   Sun Aug 3 18:28:59 2008 +0200
    
        Ticket #6845
    
    diff --git a/AUTHORS b/AUTHORS
    index a9f5583..633070c 100644
    a b answer newbie questions, and generally made Django that much better: 
    225225    Igor Kolar <ike@email.si> 
    226226    Gasper Koren 
    227227    Martin Kosír <martin@martinkosir.net> 
     228    Honza Kral <Honza.Kral@gmail.com> 
    228229    Meir Kriheli <http://mksoft.co.il/> 
    229230    Bruce Kroeze <http://coderseye.com/> 
    230231    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 7d7def3..142f3ad 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): 
    356357 
    357358    save_base.alters_data = True 
    358359 
    359     def validate(self): 
     360    def clean(self, new_data=None): 
     361        self.to_python() 
     362        self.validate(new_data) 
     363 
     364    def to_python(self): 
     365        error_dict = {} 
     366        for f in self._meta.fields: 
     367            try: 
     368                value = f.to_python(getattr(self, f.attname, f.get_default())) 
     369                setattr(self, f.attname, value) 
     370            except ValidationError, e: 
     371                error_dict[f.name] = e.messages 
     372        if error_dict: 
     373            raise ValidationError(error_dict) 
     374 
     375    def validate(self, new_data=None): 
    360376        """ 
    361         First coerces all fields on this instance to their proper Python types. 
    362         Then runs validation on every field. Returns a dictionary of 
    363         field_name -> error_list. 
     377        Validate the data on the model, if new_data is supplied, try and use those instead of 
     378        actual values on the fields. Note that the fields are validated separately. 
    364379        """ 
     380        if new_data is not None: 
     381            def get_value(f): 
     382                if f.name in new_data: 
     383                    return f.to_python(new_data[f.name]) 
     384                return getattr(self, f.attname, f.get_default()) 
     385        else: 
     386            get_value = lambda f: getattr(self, f.attname, f.get_default()) 
    365387        error_dict = {} 
    366         invalid_python = {} 
    367388        for f in self._meta.fields: 
    368389            try: 
    369                 setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default()))) 
    370             except validators.ValidationError, e: 
     390                value = get_value(f) 
     391                f.validate(value, self) 
     392                if hasattr(self, 'validate_%s' % f.name): 
     393                    getattr(self, 'validate_%s' % f.name)(value) 
     394            except ValidationError, e: 
    371395                error_dict[f.name] = e.messages 
    372                 invalid_python[f.name] = 1 
    373         for f in self._meta.fields: 
    374             if f.name in invalid_python: 
    375                 continue 
    376             errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__) 
    377             if errors: 
    378                 error_dict[f.name] = errors 
    379         return error_dict 
     396 
     397        for un_together in self._meta.unique_together: 
     398            lookup = {} 
     399            for name in un_together: 
     400                if name in error_dict: 
     401                    break 
     402                f = self._meta.get_field(name) 
     403                lookup['%s__exact' % name] = get_value(f) 
     404            try: 
     405                qset = self.__class__._default_manager.all() 
     406                if self.pk: 
     407                    qset = qset.exclude(pk=self.pk) 
     408                obj = qset.get(**lookup) 
     409                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together) 
     410            except self.DoesNotExist: 
     411                pass 
     412 
     413        if error_dict: 
     414            raise ValidationError(error_dict) 
    380415 
    381416    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False): 
    382417        """ 
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 494c42c..1783264 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): 
    7172    creation_counter = 0 
    7273    auto_creation_counter = -1 
    7374 
     75    validators = [] 
     76 
    7477    def __init__(self, verbose_name=None, name=None, primary_key=False, 
    7578            max_length=None, unique=False, blank=False, null=False, 
    7679            db_index=False, core=False, rel=None, default=NOT_PROVIDED, 
    7780            editable=True, serialize=True, unique_for_date=None, 
    7881            unique_for_month=None, unique_for_year=None, validator_list=None, 
    7982            choices=None, help_text='', db_column=None, db_tablespace=None, 
    80             auto_created=False): 
     83            auto_created=False, validators=[]): 
    8184        self.name = name 
    8285        self.verbose_name = verbose_name 
    8386        self.primary_key = primary_key 
    class Field(object): 
    9093        self.core, self.rel, self.default = core, rel, default 
    9194        self.editable = editable 
    9295        self.serialize = serialize 
     96        self.validators = validators + self.validators 
    9397        self.validator_list = validator_list or [] 
    9498        self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month 
    9599        self.unique_for_year = unique_for_year 
    class Field(object): 
    155159            return get_creation_module().DATA_TYPES[self.get_internal_type()] % data 
    156160        except KeyError: 
    157161            return None 
    158  
    159162    def unique(self): 
    160163        return self._unique or self.primary_key 
    161164    unique = property(unique) 
    162165 
    163     def validate_full(self, field_data, all_data): 
    164         """ 
    165         Returns a list of errors for this field. This is the main interface, 
    166         as it encapsulates some basic validation logic used by all fields. 
    167         Subclasses should implement validate(), not validate_full(). 
    168         """ 
    169         if not self.blank and not field_data: 
    170             return [_('This field is required.')] 
    171         try: 
    172             self.validate(field_data, all_data) 
    173         except validators.ValidationError, e: 
    174             return e.messages 
    175         return [] 
    176  
    177     def validate(self, field_data, all_data): 
     166    def validate(self, value, instance): 
    178167        """ 
    179         Raises validators.ValidationError if field_data has any errors. 
     168        Raises validators.ValidationError if value has any errors. 
    180169        Subclasses should override this to specify field-specific validation 
    181         logic. This method should assume field_data has already been converted 
     170        logic. This method should assume value has already been converted 
    182171        into the appropriate data type by Field.to_python(). 
    183172        """ 
    184         pass 
     173        if not self.blank and self.editable and not value: 
     174            raise validators.ValidationError(_('This field is required.')) 
     175        elist = [] 
     176        for validator in self.validators: 
     177            validator(value) 
     178 
     179        # ??? shouldn't we move these into model.validate_FIELD() via contribute_to_class ??? 
     180        # validate uniqueness unless we are the PK 
     181        if self.unique and (not instance._meta.pk == self): 
     182            try: 
     183                qset = instance.__class__._default_manager.all() 
     184                if instance.pk: 
     185                    qset = qset.exclude(pk=instance.pk) 
     186                obj = qset.get(**{'%s__exact' % self.name : value}) 
     187                raise validators.ValidationError(_('This field must be unique')) 
     188            except instance.DoesNotExist: 
     189                pass 
     190 
     191        if self.unique_for_date: 
     192            try: 
     193                qset = instance.__class__._default_manager.all() 
     194                unique_for = getattr(instance, self.unique_for_date) 
     195                obj = qset.get(**{ 
     196                        '%s__exact' % self.name : value, 
     197                        '%s__year' % self.unique_for_date: unique_for.year, 
     198                        '%s__month' % self.unique_for_date: unique_for.month, 
     199                        '%s__day' % self.unique_for_date: unique_for.day 
     200                    }) 
     201                raise validators.ValidationError(_('This field must be unique for %s') % self.unique_for_date ) 
     202            except instance.DoesNotExist: 
     203                pass 
     204 
     205        if self.unique_for_month: 
     206            try: 
     207                qset = instance.__class__._default_manager.all() 
     208                unique_for = getattr(instance, self.unique_for_date) 
     209                obj = qset.get(**{ 
     210                        '%s__exact' % self.name : value, 
     211                        '%s__year' % self.unique_for_date: unique_for.year, 
     212                        '%s__month' % self.unique_for_date: unique_for.month, 
     213                    }) 
     214                raise validators.ValidationError(_('This field must be unique for %s') % self.unique_for_date ) 
     215            except instance.DoesNotExist: 
     216                pass 
     217            pass 
     218 
     219        if self.unique_for_year: 
     220            try: 
     221                qset = instance.__class__._default_manager.all() 
     222                unique_for = getattr(instance, self.unique_for_date) 
     223                obj = qset.get(**{ 
     224                        '%s__exact' % self.name : value, 
     225                        '%s__year' % self.unique_for_date: unique_for.year, 
     226                    }) 
     227                raise validators.ValidationError(_('This field must be unique for %s') % self.unique_for_date ) 
     228            except instance.DoesNotExist: 
     229                pass 
    185230 
    186231    def set_attributes_from_name(self, name): 
    187232        self.name = name 
    class Field(object): 
    340385                    core_field_names.extend(f.get_manipulator_field_names(name_prefix)) 
    341386            # Now, if there are any, add the validator to this FormField. 
    342387            if core_field_names: 
    343                 params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required."))) 
     388                params['validator_list'].append(oldvalidators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required."))) 
    344389 
    345390        # Finally, add the field_names. 
    346391        field_names = self.get_manipulator_field_names(name_prefix) 
    class DateField(Field): 
    562607            return value.date() 
    563608        if isinstance(value, datetime.date): 
    564609            return value 
    565         validators.isValidANSIDate(value, None) 
    566610        try: 
    567611            return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) 
    568612        except ValueError: 
    class DecimalField(Field): 
    743787        return super(DecimalField, self).formfield(**defaults) 
    744788 
    745789class EmailField(CharField): 
     790    validators = [validators.validate_email] 
    746791    def __init__(self, *args, **kwargs): 
    747792        kwargs['max_length'] = kwargs.get('max_length', 75) 
    748793        CharField.__init__(self, *args, **kwargs) 
    class EmailField(CharField): 
    750795    def get_manipulator_field_objs(self): 
    751796        return [oldforms.EmailField] 
    752797 
    753     def validate(self, field_data, all_data): 
    754         validators.isValidEmail(field_data, all_data) 
    755  
    756798    def formfield(self, **kwargs): 
    757799        defaults = {'form_class': forms.EmailField} 
    758800        defaults.update(kwargs) 
    class FileField(Field): 
    789831                        self.always_test = True 
    790832                    def __call__(self, field_data, all_data): 
    791833                        if not all_data.get(self.other_file_field_name, False): 
    792                             c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required.")) 
     834                            c = oldvalidators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required.")) 
    793835                            c(field_data, all_data) 
    794836                # First, get the core fields, if any. 
    795837                core_field_names = [] 
    class FileField(Field): 
    800842                if core_field_names: 
    801843                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name)) 
    802844            else: 
    803                 v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required.")) 
     845                v = oldvalidators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required.")) 
    804846                v.always_test = True 
    805847                field_list[0].validator_list.append(v) 
    806848                field_list[0].is_required = field_list[1].is_required = False 
    class IntegerField(Field): 
    9781020 
    9791021class IPAddressField(Field): 
    9801022    empty_strings_allowed = False 
     1023    validators = [validators.validate_ip_address4] 
     1024 
    9811025    def __init__(self, *args, **kwargs): 
    9821026        kwargs['max_length'] = 15 
    9831027        Field.__init__(self, *args, **kwargs) 
    class IPAddressField(Field): 
    9881032    def get_internal_type(self): 
    9891033        return "IPAddressField" 
    9901034 
    991     def validate(self, field_data, all_data): 
    992         validators.isValidIPAddress4(field_data, None) 
    993  
    9941035    def formfield(self, **kwargs): 
    9951036        defaults = {'form_class': forms.IPAddressField} 
    9961037        defaults.update(kwargs) 
    class NullBooleanField(Field): 
    10301071        return super(NullBooleanField, self).formfield(**defaults) 
    10311072 
    10321073class PhoneNumberField(Field): 
     1074    validators = [validators.validate_phone_number] 
     1075 
    10331076    def get_manipulator_field_objs(self): 
    10341077        return [oldforms.PhoneNumberField] 
    10351078 
    10361079    def get_internal_type(self): 
    10371080        return "PhoneNumberField" 
    10381081 
    1039     def validate(self, field_data, all_data): 
    1040         validators.isValidPhone(field_data, all_data) 
    1041  
    10421082    def formfield(self, **kwargs): 
    10431083        from django.contrib.localflavor.us.forms import USPhoneNumberField 
    10441084        defaults = {'form_class': USPhoneNumberField} 
    class PositiveSmallIntegerField(IntegerField): 
    10701110        return super(PositiveSmallIntegerField, self).formfield(**defaults) 
    10711111 
    10721112class SlugField(CharField): 
     1113    validators = [validators.validate_slug] 
    10731114    def __init__(self, *args, **kwargs): 
    10741115        kwargs['max_length'] = kwargs.get('max_length', 50) 
    1075         kwargs.setdefault('validator_list', []).append(validators.isSlug) 
    10761116        # Set db_index=True unless it's been set manually. 
    10771117        if 'db_index' not in kwargs: 
    10781118            kwargs['db_index'] = True 
    class URLField(CharField): 
    11681208    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): 
    11691209        kwargs['max_length'] = kwargs.get('max_length', 200) 
    11701210        if verify_exists: 
    1171             kwargs.setdefault('validator_list', []).append(validators.isExistingURL) 
     1211            kwargs.setdefault('validators', []).append(validators.validate_existing_url) 
    11721212        self.verify_exists = verify_exists 
    11731213        CharField.__init__(self, verbose_name, name, **kwargs) 
    11741214 
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index e814198..60fe8d3 100644
    a b class ManyToManyField(RelatedField, Field): 
    830830        objects = mod._default_manager.in_bulk(pks) 
    831831        if len(objects) != len(pks): 
    832832            badkeys = [k for k in pks if k not in objects] 
    833             raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", 
     833            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", 
    834834                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % { 
    835835                'self': self.verbose_name, 
    836836                '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 47ae5e1..904849c 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        if help_text is None: 
    7883            self.help_text = u'' 
    class Field(object): 
    103108        messages.update(error_messages or {}) 
    104109        self.error_messages = messages 
    105110 
     111    def to_python(self, value): 
     112        if value in EMPTY_VALUES: 
     113            return None 
     114        return smart_unicode(value) 
     115 
     116    def validate(self, value): 
     117        if self.required and value in EMPTY_VALUES: 
     118            raise ValidationError(self.error_messages['required']) 
     119        elif value in EMPTY_VALUES: 
     120            return 
     121        elist = ErrorList() 
     122        for validator in self.validators: 
     123            try: 
     124                validator(value, self.error_messages) 
     125            except ValidationError, e: 
     126                elist.extend(e.messages) 
     127        if elist: 
     128            raise ValidationError(elist) 
     129 
    106130    def clean(self, value): 
    107131        """ 
    108132        Validates the given value and returns its "cleaned" value as an 
    class Field(object): 
    110134 
    111135        Raises ValidationError for any errors. 
    112136        """ 
    113         if self.required and value in EMPTY_VALUES: 
    114             raise ValidationError(self.error_messages['required']) 
     137        value = self.to_python(value) 
     138        self.validate(value) 
    115139        return value 
    116140 
    117141    def widget_attrs(self, widget): 
    class CharField(Field): 
    138162        self.max_length, self.min_length = max_length, min_length 
    139163        super(CharField, self).__init__(*args, **kwargs) 
    140164 
    141     def clean(self, value): 
    142         "Validates max_length and min_length. Returns a Unicode object." 
    143         super(CharField, self).clean(value) 
     165    def to_python(self, value): 
    144166        if value in EMPTY_VALUES: 
    145167            return u'' 
    146         value = smart_unicode(value) 
     168        return smart_unicode(value) 
     169 
     170    def validate(self, value): 
     171        "Validates max_length and min_length. Returns a Unicode object." 
     172        super(CharField, self).validate(value) 
    147173        value_length = len(value) 
     174        if value_length == 0 and not self.required: 
     175            return  
    148176        if self.max_length is not None and value_length > self.max_length: 
    149177            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) 
    150178        if self.min_length is not None and value_length < self.min_length: 
    151179            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) 
    152         return value 
    153180 
    154181    def widget_attrs(self, widget): 
    155182        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): 
    class IntegerField(Field): 
    167194        self.max_value, self.min_value = max_value, min_value 
    168195        super(IntegerField, self).__init__(*args, **kwargs) 
    169196 
    170     def clean(self, value): 
    171         """ 
    172         Validates that int() can be called on the input. Returns the result 
    173         of int(). Returns None for empty values. 
    174         """ 
    175         super(IntegerField, self).clean(value) 
     197    def to_python(self, value): 
    176198        if value in EMPTY_VALUES: 
    177199            return None 
    178200        try: 
    179             value = int(str(value)) 
     201            return int(smart_str(value)) 
    180202        except (ValueError, TypeError): 
    181203            raise ValidationError(self.error_messages['invalid']) 
     204 
     205    def validate(self, value): 
     206        """ 
     207        Validates that int() can be called on the input. Returns the result 
     208        of int(). Returns None for empty values. 
     209        """ 
     210        super(IntegerField, self).validate(value) 
     211        if value is None: return 
    182212        if self.max_value is not None and value > self.max_value: 
    183213            raise ValidationError(self.error_messages['max_value'] % self.max_value) 
    184214        if self.min_value is not None and value < self.min_value: 
    185215            raise ValidationError(self.error_messages['min_value'] % self.min_value) 
    186         return value 
    187216 
    188217class FloatField(Field): 
    189218    default_error_messages = { 
    class FloatField(Field): 
    196225        self.max_value, self.min_value = max_value, min_value 
    197226        Field.__init__(self, *args, **kwargs) 
    198227 
    199     def clean(self, value): 
     228    def to_python(self, value): 
    200229        """ 
    201230        Validates that float() can be called on the input. Returns a float. 
    202231        Returns None for empty values. 
    203232        """ 
    204         super(FloatField, self).clean(value) 
    205         if not self.required and value in EMPTY_VALUES: 
     233        if value in EMPTY_VALUES: 
    206234            return None 
    207235        try: 
    208             value = float(value) 
     236            return float(value) 
    209237        except (ValueError, TypeError): 
    210238            raise ValidationError(self.error_messages['invalid']) 
     239 
     240    def validate(self, value): 
     241        super(FloatField, self).validate(value) 
     242        if value is None: return 
    211243        if self.max_value is not None and value > self.max_value: 
    212244            raise ValidationError(self.error_messages['max_value'] % self.max_value) 
    213245        if self.min_value is not None and value < self.min_value: 
    214246            raise ValidationError(self.error_messages['min_value'] % self.min_value) 
    215         return value 
    216247 
    217248class DecimalField(Field): 
    218249    default_error_messages = { 
    class DecimalField(Field): 
    229260        self.max_digits, self.decimal_places = max_digits, decimal_places 
    230261        Field.__init__(self, *args, **kwargs) 
    231262 
    232     def clean(self, value): 
     263    def to_python(self, value): 
    233264        """ 
    234265        Validates that the input is a decimal number. Returns a Decimal 
    235266        instance. Returns None for empty values. Ensures that there are no more 
    236267        than max_digits in the number, and no more than decimal_places digits 
    237268        after the decimal point. 
    238269        """ 
    239         super(DecimalField, self).clean(value) 
    240         if not self.required and value in EMPTY_VALUES: 
     270        if value in EMPTY_VALUES: 
    241271            return None 
    242272        value = smart_str(value).strip() 
    243273        try: 
    244             value = Decimal(value) 
     274            return Decimal(value) 
    245275        except DecimalException: 
    246276            raise ValidationError(self.error_messages['invalid']) 
     277 
     278    def validate(self, value): 
     279        super(DecimalField, self).validate(value) 
     280        if value is None: return 
    247281        pieces = str(value).lstrip("-").split('.') 
    248282        decimals = (len(pieces) == 2) and len(pieces[1]) or 0 
    249283        digits = len(pieces[0]) 
    class DecimalField(Field): 
    257291            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 
    258292        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): 
    259293            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 
    260         return value 
    261294 
    262295DEFAULT_DATE_INPUT_FORMATS = ( 
    263296    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' 
    class DateField(Field): 
    276309        super(DateField, self).__init__(*args, **kwargs) 
    277310        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS 
    278311 
    279     def clean(self, value): 
     312    def to_python(self, value): 
    280313        """ 
    281314        Validates that the input can be converted to a date. Returns a Python 
    282315        datetime.date object. 
    283316        """ 
    284         super(DateField, self).clean(value) 
    285317        if value in EMPTY_VALUES: 
    286318            return None 
    287319        if isinstance(value, datetime.datetime): 
    class TimeField(Field): 
    309341        super(TimeField, self).__init__(*args, **kwargs) 
    310342        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS 
    311343 
    312     def clean(self, value): 
     344    def to_python(self, value): 
    313345        """ 
    314346        Validates that the input can be converted to a time. Returns a Python 
    315347        datetime.time object. 
    316348        """ 
    317         super(TimeField, self).clean(value) 
    318349        if value in EMPTY_VALUES: 
    319350            return None 
    320351        if isinstance(value, datetime.time): 
    class DateTimeField(Field): 
    348379        super(DateTimeField, self).__init__(*args, **kwargs) 
    349380        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS 
    350381 
    351     def clean(self, value): 
     382    def to_python(self, value): 
    352383        """ 
    353384        Validates that the input can be converted to a datetime. Returns a 
    354385        Python datetime.datetime object. 
    355386        """ 
    356         super(DateTimeField, self).clean(value) 
    357387        if value in EMPTY_VALUES: 
    358388            return None 
    359389        if isinstance(value, datetime.datetime): 
    class RegexField(CharField): 
    390420            regex = re.compile(regex) 
    391421        self.regex = regex 
    392422 
    393     def clean(self, value): 
     423    def validate(self, value): 
    394424        """ 
    395425        Validates that the input matches the regular expression. Returns a 
    396426        Unicode object. 
    397427        """ 
    398         value = super(RegexField, self).clean(value) 
     428        super(RegexField, self).validate(value) 
    399429        if value == u'': 
    400             return value 
     430            return 
    401431        if not self.regex.search(value): 
    402432            raise ValidationError(self.error_messages['invalid']) 
    403         return value 
    404433 
    405434email_re = re.compile( 
    406435    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom 
    407436    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 
    408437    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain 
    409438 
    410 class EmailField(RegexField): 
     439class EmailField(CharField): 
    411440    default_error_messages = { 
    412441        'invalid': _(u'Enter a valid e-mail address.'), 
    413442    } 
    414  
    415     def __init__(self, max_length=None, min_length=None, *args, **kwargs): 
    416         RegexField.__init__(self, email_re, max_length, min_length, *args, 
    417                             **kwargs) 
     443    validators = [validators.validate_email] 
    418444 
    419445try: 
    420446    from django.conf import settings 
    class FileField(Field): 
    428454    widget = FileInput 
    429455    default_error_messages = { 
    430456        'invalid': _(u"No file was submitted. Check the encoding type on the form."), 
    431         'missing': _(u"No file was submitted."), 
    432457        'empty': _(u"The submitted file is empty."), 
    433458    } 
    434459 
    435     def __init__(self, *args, **kwargs): 
    436         super(FileField, self).__init__(*args, **kwargs) 
    437  
    438     def clean(self, data, initial=None): 
    439         super(FileField, self).clean(initial or data) 
     460    def to_python(self, data, initial=None): 
    440461        if not self.required and data in EMPTY_VALUES: 
    441462            return None 
    442463        elif not data and initial: 
    class FileField(Field): 
    450471                category = DeprecationWarning, 
    451472                stacklevel = 2 
    452473            ) 
     474            if not data: 
     475                raise ValidationError(self.error_messages['invalid']) 
    453476            data = UploadedFile(data['filename'], data['content']) 
    454477 
    455478        try: 
    class FileField(Field): 
    462485            raise ValidationError(self.error_messages['invalid']) 
    463486        if not file_size: 
    464487            raise ValidationError(self.error_messages['empty']) 
    465  
    466488        return data 
    467489 
     490    def clean(self, value, initial=None): 
     491        "overriden clean to provide extra argument initial" 
     492        value = self.to_python(value, initial) 
     493        self.validate(value) 
     494        return value 
     495 
    468496class ImageField(FileField): 
    469497    default_error_messages = { 
    470498        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), 
    471499    } 
    472500 
    473     def clean(self, data, initial=None): 
    474         """ 
    475         Checks that the file-upload field data contains a valid image (GIF, JPG, 
    476         PNG, possibly others -- whatever the Python Imaging Library supports). 
    477         """ 
    478         f = super(ImageField, self).clean(data, initial) 
    479         if f is None: 
    480             return None 
    481         elif not data and initial: 
    482             return initial 
     501    def validate(self, data): 
     502        super(ImageField, self).validate(data) 
     503        if data is None: 
     504            return 
    483505        from PIL import Image 
    484  
    485506        # We need to get a file object for PIL. We might have a path or we might 
    486507        # have to read the data into memory. 
    487508        if hasattr(data, 'temporary_file_path'): 
    class ImageField(FileField): 
    489510        else: 
    490511            if hasattr(data, 'read'): 
    491512                file = StringIO(data.read()) 
     513            elif isinstance(data, UploadedFile): 
     514                file = StringIO(data.data.read()) 
     515            elif hasattr(data, 'seek') and callable(data.seek): 
     516                data.seek(0) 
     517                file = data 
    492518            else: 
    493                 file = StringIO(data['content']) 
     519                file = data 
    494520 
    495521        try: 
    496522            # load() is the only method that can spot a truncated JPEG, 
    class ImageField(FileField): 
    514540            raise 
    515541        except Exception: # Python Imaging Library doesn't recognize it as an image 
    516542            raise ValidationError(self.error_messages['invalid_image']) 
    517         if hasattr(f, 'seek') and callable(f.seek): 
    518             f.seek(0) 
    519         return f 
    520543 
    521544url_re = re.compile( 
    522545    r'^https?://' # http:// or https:// 
    class URLField(RegexField): 
    539562        self.verify_exists = verify_exists 
    540563        self.user_agent = validator_user_agent 
    541564 
    542     def clean(self, value): 
     565    def to_python(self, value): 
     566        value = super(URLField, self).to_python(value) 
    543567        # If no URL scheme given, assume http:// 
    544568        if value and '://' not in value: 
    545569            value = u'http://%s' % value 
    546570        # If no URL path given, assume / 
    547571        if value and not urlparse.urlsplit(value)[2]: 
    548572            value += '/' 
    549         value = super(URLField, self).clean(value) 
     573        return value 
     574 
     575    def validate(self, value): 
     576        super(URLField, self).validate(value) 
    550577        if value == u'': 
    551             return value 
     578            return 
    552579        if self.verify_exists: 
    553             import urllib2 
    554             headers = { 
    555                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
    556                 "Accept-Language": "en-us,en;q=0.5", 
    557                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", 
    558                 "Connection": "close", 
    559                 "User-Agent": self.user_agent, 
    560             } 
    561             try: 
    562                 req = urllib2.Request(value, None, headers) 
    563                 u = urllib2.urlopen(req) 
    564             except ValueError: 
    565                 raise ValidationError(self.error_messages['invalid']) 
    566             except: # urllib2.URLError, httplib.InvalidURL, etc. 
    567                 raise ValidationError(self.error_messages['invalid_link']) 
    568         return value 
     580            # we cannot put this in self.validators because its conditional 
     581            validators.validate_existing_url(value, self.error_messages) 
    569582 
    570583class BooleanField(Field): 
    571584    widget = CheckboxInput 
    572585 
    573     def clean(self, value): 
     586    def to_python(self, value): 
    574587        """Returns a Python boolean object.""" 
    575588        # Explicitly check for the string 'False', which is what a hidden field 
    576589        # will submit for False. Because bool("True") == True, we don't need to 
    class BooleanField(Field): 
    579592            value = False 
    580593        else: 
    581594            value = bool(value) 
    582         super(BooleanField, self).clean(value) 
    583         if not value and self.required: 
    584             raise ValidationError(self.error_messages['required']) 
    585595        return value 
    586596 
     597    def validate(self, value): 
     598        if self.required and not value: 
     599            raise ValidationError(self.error_messages['required']) 
     600 
     601 
    587602class NullBooleanField(BooleanField): 
    588603    """ 
    589604    A field whose valid values are None, True and False. Invalid values are 
    590605    cleaned to None. 
     606 
     607    Note that validation doesn't apply here. 
    591608    """ 
    592609    widget = NullBooleanSelect 
    593610 
    594     def clean(self, value): 
     611    def to_python(self, value): 
    595612        return {True: True, False: False}.get(value, None) 
    596613 
     614    def validate(self, value): 
     615        pass 
     616 
     617 
    597618class ChoiceField(Field): 
    598619    widget = Select 
    599620    default_error_messages = { 
    class ChoiceField(Field): 
    617638 
    618639    choices = property(_get_choices, _set_choices) 
    619640 
    620     def clean(self, value): 
    621         """ 
    622         Validates that the input is in self.choices. 
    623         """ 
    624         value = super(ChoiceField, self).clean(value) 
    625         if value in EMPTY_VALUES: 
    626             value = u'' 
    627         value = smart_unicode(value) 
    628         if value == u'': 
    629             return value 
     641    def validate(self, value): 
     642        super(ChoiceField, self).validate(value) 
     643        if value is None and not self.required: 
     644            return 
    630645        if not self.valid_value(value): 
    631646            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 
    632         return value 
    633647 
    634648    def valid_value(self, value): 
    635649        "Check to see if the provided value is a valid choice" 
    class MultipleChoiceField(ChoiceField): 
    652666        'invalid_list': _(u'Enter a list of values.'), 
    653667    } 
    654668 
    655     def clean(self, value): 
     669    def to_python(self, value): 
    656670        """ 
    657671        Validates that the input is a list or tuple. 
    658672        """ 
    659         if self.required and not value: 
    660             raise ValidationError(self.error_messages['required']) 
    661         elif not self.required and not value: 
     673        if not value: 
    662674            return [] 
    663675        if not isinstance(value, (list, tuple)): 
    664676            raise ValidationError(self.error_messages['invalid_list']) 
    665         new_value = [smart_unicode(val) for val in value] 
     677        return [smart_unicode(val) for val in value] 
     678 
     679    def validate(self, value): 
    666680        # Validate that each value in the value list is in self.choices. 
    667         for val in new_value: 
     681        if self.required and value == []: 
     682            raise ValidationError(self.error_messages['required']) 
     683        for val in value: 
    668684            if not self.valid_value(val): 
    669685                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) 
    670         return new_value 
    671686 
    672687class ComboField(Field): 
    673688    """ 
    class ComboField(Field): 
    682697            f.required = False 
    683698        self.fields = fields 
    684699 
    685     def clean(self, value): 
     700    def to_python(self, value): 
     701        for field in self.fields: 
     702            value = field.to_python(value) 
     703        return value 
     704 
     705    def validate(self, value): 
    686706        """ 
    687707        Validates the given value against all of self.fields, which is a 
    688708        list of Field instances. 
    689709        """ 
    690         super(ComboField, self).clean(value) 
     710        super(ComboField, self).validate(value) 
    691711        for field in self.fields: 
    692             value = field.clean(value) 
    693         return value 
     712            field.validate(value) 
    694713 
    695714class MultiValueField(Field): 
    696715    """ 
    class MultiValueField(Field): 
    722741            f.required = False 
    723742        self.fields = fields 
    724743 
    725     def clean(self, value): 
     744    def to_python(self, value): 
    726745        """ 
    727746        Validates every value in the given list. A value is validated against 
    728747        the corresponding Field in self.fields. 
    class MultiValueField(Field): 
    736755        if not value or isinstance(value, (list, tuple)): 
    737756            if not value or not [v for v in value if v not in EMPTY_VALUES]: 
    738757                if self.required: 
    739                     raise ValidationError(self.error_messages['required']) 
     758                    return None 
    740759                else: 
    741760                    return self.compress([]) 
    742761        else: 
    743762            raise ValidationError(self.error_messages['invalid']) 
     763 
    744764        for i, field in enumerate(self.fields): 
    745765            try: 
    746766                field_value = value[i] 
    class SplitDateTimeField(MultiValueField): 
    824844            return datetime.datetime.combine(*data_list) 
    825845        return None 
    826846 
    827 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}$') 
    828  
    829 class IPAddressField(RegexField): 
     847class IPAddressField(CharField): 
    830848    default_error_messages = { 
    831849        'invalid': _(u'Enter a valid IPv4 address.'), 
    832850    } 
    833  
    834     def __init__(self, *args, **kwargs): 
    835         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) 
     851    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 1f807f2..da9c4f0 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        super(BaseModelForm, self).__init__(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 BaseInlineFormset(BaseModelFormSet): 
    410417        # is there a better way to get the object descriptor? 
    411418        self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() 
    412419        super(BaseInlineFormset, self).__init__(data, files, prefix=prefix or self.rel_name) 
     420 
     421    def _construct_form(self, i, **kwargs): 
     422        form = super(BaseInlineFormset, self)._construct_form(i, **kwargs) 
     423        setattr(form.instance, self.fk.get_attname(), self.instance.pk) 
     424        return form 
    413425     
    414426    def _construct_forms(self): 
    415427        if self.save_as_new: 
    class ModelChoiceField(ChoiceField): 
    572584 
    573585    choices = property(_get_choices, ChoiceField._set_choices) 
    574586 
    575     def clean(self, value): 
    576         Field.clean(self, value) 
    577         if value in EMPTY_VALUES: 
     587    def to_python(self, value): 
     588        if self.required and value in EMPTY_VALUES: 
     589            raise ValidationError(self.error_messages['required']) 
     590        elif value in EMPTY_VALUES: 
    578591            return None 
    579592        try: 
    580593            value = self.queryset.get(pk=value) 
    class ModelChoiceField(ChoiceField): 
    582595            raise ValidationError(self.error_messages['invalid_choice']) 
    583596        return value 
    584597 
     598    def validate(self, value): 
     599        pass 
     600 
    585601class ModelMultipleChoiceField(ModelChoiceField): 
    586602    """A MultipleChoiceField whose choices are a model QuerySet.""" 
    587603    hidden_widget = MultipleHiddenInput 
    class ModelMultipleChoiceField(ModelChoiceField): 
    598614            cache_choices, required, widget, label, initial, help_text, 
    599615            *args, **kwargs) 
    600616 
    601     def clean(self, value): 
     617    def to_python(self, value): 
    602618        if self.required and not value: 
    603619            raise ValidationError(self.error_messages['required']) 
    604620        elif not self.required and not value: 
    class ModelMultipleChoiceField(ModelChoiceField): 
    614630            else: 
    615631                final_values.append(obj) 
    616632        return final_values 
     633 
  • 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 b5698ab..b9f7204 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 7ed9d66..4d08650 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() 
     18    email = models.EmailField(unique=True) 
    1919    best_time = models.TimeField() 
    2020 
     21    class Meta: 
     22        unique_together = (('name', 'is_child'),) 
    2123    def __unicode__(self): 
    2224        return self.name 
    2325 
    __test__ = {'API_TESTS':""" 
    3234...     'email': 'john@example.com', 
    3335...     'best_time': datetime.time(16, 20), 
    3436... } 
    35 >>> p = Person(**valid_params) 
    36 >>> p.validate() 
    37 {} 
    38  
    39 >>> p = Person(**dict(valid_params, id='23')) 
    40 >>> p.validate() 
    41 {} 
     37>>> p = Person(**dict(valid_params, email='john@e.com', name='Jack')) 
     38>>> p.clean() 
     39>>> p.save() 
     40 
     41>>> p = Person(**dict(valid_params, email='john@e.com')) 
     42>>> p.clean() 
     43Traceback (most recent call last): 
     44... 
     45ValidationError: {'email': [u'This field must be unique']} 
     46 
     47>>> p = Person(**dict(valid_params, id='23', name='Jack')) 
     48>>> p.clean() 
     49Traceback (most recent call last): 
     50... 
     51ValidationError: {'__all__': u'Fields name, is_child must be unique.'} 
    4252>>> p.id 
    435323 
    4454 
    45 >>> p = Person(**dict(valid_params, id='foo')) 
    46 >>> p.validate()['id'] 
    47 [u'This value must be an integer.'] 
     55# when type coercion fails, no other validation is done 
     56>>> p = Person(**dict(valid_params, email='john@e.com', id='foo')) 
     57>>> p.clean() 
     58Traceback (most recent call last): 
     59... 
     60ValidationError: {'id': [u'This value must be an integer.']} 
    4861 
    4962>>> p = Person(**dict(valid_params, id=None)) 
    50 >>> p.validate() 
    51 {} 
     63>>> p.clean() 
    5264>>> repr(p.id) 
    5365'None' 
    5466 
    5567>>> p = Person(**dict(valid_params, is_child='t')) 
    56 >>> p.validate() 
    57 {} 
     68>>> p.clean() 
    5869>>> p.is_child 
    5970True 
    6071 
    6172>>> p = Person(**dict(valid_params, is_child='f')) 
    62 >>> p.validate() 
    63 {} 
     73>>> p.clean() 
    6474>>> p.is_child 
    6575False 
    6676 
    6777>>> p = Person(**dict(valid_params, is_child=True)) 
    68 >>> p.validate() 
    69 {} 
     78>>> p.clean() 
    7079>>> p.is_child 
    7180True 
    7281 
    7382>>> p = Person(**dict(valid_params, is_child=False)) 
    74 >>> p.validate() 
    75 {} 
     83>>> p.clean() 
    7684>>> p.is_child 
    7785False 
    7886 
    7987>>> p = Person(**dict(valid_params, is_child='foo')) 
    80 >>> p.validate()['is_child'] 
    81 [u'This value must be either True or False.'] 
     88>>> p.clean() 
     89Traceback (most recent call last): 
     90... 
     91ValidationError: {'is_child': [u'This value must be either True or False.']} 
    8292 
    8393>>> p = Person(**dict(valid_params, name=u'Jose')) 
    84 >>> p.validate() 
    85 {} 
     94>>> p.clean() 
    8695>>> p.name 
    8796u'Jose' 
    8897 
    8998>>> p = Person(**dict(valid_params, name=227)) 
    90 >>> p.validate() 
    91 {} 
     99>>> p.clean() 
    92100>>> p.name 
    93101u'227' 
    94102 
    95103>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3))) 
    96 >>> p.validate() 
    97 {} 
     104>>> p.clean() 
    98105>>> p.birthdate 
    99106datetime.date(2000, 5, 3) 
    100107 
    101108>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3))) 
    102 >>> p.validate() 
    103 {} 
     109>>> p.clean() 
    104110>>> p.birthdate 
    105111datetime.date(2000, 5, 3) 
    106112 
    107113>>> p = Person(**dict(valid_params, birthdate='2000-05-03')) 
    108 >>> p.validate() 
    109 {} 
     114>>> p.clean() 
    110115>>> p.birthdate 
    111116datetime.date(2000, 5, 3) 
    112117 
    113118>>> p = Person(**dict(valid_params, birthdate='2000-5-3')) 
    114 >>> p.validate() 
    115 {} 
     119>>> p.clean() 
    116120>>> p.birthdate 
    117121datetime.date(2000, 5, 3) 
    118122 
    119123>>> p = Person(**dict(valid_params, birthdate='foo')) 
    120 >>> p.validate()['birthdate'] 
    121 [u'Enter a valid date in YYYY-MM-DD format.'] 
     124>>> p.clean() 
     125Traceback (most recent call last): 
     126... 
     127ValidationError: {'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']} 
    122128 
    123129>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23))) 
    124 >>> p.validate() 
    125 {} 
     130>>> p.clean() 
    126131>>> p.favorite_moment 
    127132datetime.datetime(2002, 4, 3, 13, 23) 
    128133 
    129134>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3))) 
    130 >>> p.validate() 
    131 {} 
     135>>> p.clean() 
    132136>>> p.favorite_moment 
    133137datetime.datetime(2002, 4, 3, 0, 0) 
    134138 
    135139>>> p = Person(**dict(valid_params, best_time='16:20:00')) 
    136 >>> p.validate() 
    137 {} 
     140>>> p.clean() 
    138141>>> p.best_time 
    139142datetime.time(16, 20) 
    140143 
    141144>>> p = Person(**dict(valid_params, best_time='16:20')) 
    142 >>> p.validate() 
    143 {} 
     145>>> p.clean() 
    144146>>> p.best_time 
    145147datetime.time(16, 20) 
    146148 
    147149>>> p = Person(**dict(valid_params, best_time='bar')) 
    148 >>> p.validate()['best_time'] 
    149 [u'Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'] 
     150>>> p.clean()['best_time'] 
     151Traceback (most recent call last): 
     152... 
     153ValidationError: {'best_time': [u'Enter a valid time in HH:MM[:ss[.uuuuuu]] format.']} 
    150154 
    151155>>> p = Person(**dict(valid_params, email='john@example.com')) 
    152 >>> p.validate() 
    153 {} 
     156>>> p.clean() 
    154157>>> p.email 
    155158'john@example.com' 
    156159 
    157160>>> p = Person(**dict(valid_params, email=u'john@example.com')) 
    158 >>> p.validate() 
    159 {} 
     161>>> p.clean() 
    160162>>> p.email 
    161163u'john@example.com' 
    162164 
    163165>>> p = Person(**dict(valid_params, email=22)) 
    164 >>> p.validate()['email'] 
    165 [u'Enter a valid e-mail address.'] 
     166>>> p.clean() 
     167Traceback (most recent call last): 
     168... 
     169ValidationError: {'email': [u'Enter a valid e-mail address.']} 
    166170 
    167171# Make sure that Date and DateTime return validation errors and don't raise Python errors. 
    168 >>> p = Person(name='John Doe', is_child=True, email='abc@def.com') 
    169 >>> errors = p.validate() 
    170 >>> errors['favorite_moment'] 
    171 [u'This field is required.'] 
    172 >>> errors['birthdate'] 
     172>>> from django.core.exceptions import ValidationError 
     173>>> try: 
     174...     Person(name='John Doe', is_child=True, email='abc@def.com').clean() 
     175... except ValidationError, e: 
     176...     e.message_dict['favorite_moment'] 
     177...     e.message_dict['birthdate'] 
    173178[u'This field is required.'] 
    174 >>> errors['best_time'] 
    175179[u'This field is required.'] 
    176180 
    177181"""} 
  • 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..469116c
    - +  
     1# -*- coding: UTF-8 -*- 
     2tests = r""" 
     3################### 
     4# ValidationError # 
     5################### 
     6>>> from django.core.exceptions import ValidationError 
     7>>> from django.utils.translation import ugettext_lazy 
     8 
     9# Can take a string. 
     10>>> print ValidationError("There was an error.").messages 
     11<ul class="errorlist"><li>There was an error.</li></ul> 
     12 
     13# Can take a unicode string. 
     14>>> print ValidationError(u"Not \u03C0.").messages 
     15<ul class="errorlist"><li>Not π.</li></ul> 
     16 
     17# Can take a lazy string. 
     18>>> print ValidationError(ugettext_lazy("Error.")).messages 
     19<ul class="errorlist"><li>Error.</li></ul> 
     20 
     21# Can take a list. 
     22>>> print ValidationError(["Error one.", "Error two."]).messages 
     23<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul> 
     24 
     25# Can take a mixture in a list. 
     26>>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages 
     27<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul> 
     28 
     29>>> class VeryBadError: 
     30...     def __unicode__(self): return u"A very bad error." 
     31 
     32# Can take a non-string. 
     33>>> print ValidationError(VeryBadError()).messages 
     34<ul class="errorlist"><li>A very bad error.</li></ul> 
     35""" 
     36 
  • 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 a9aae4f..42be8bb 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"""