Code

Ticket #6845: 6845-against-7877.2.patch

File 6845-against-7877.2.patch, 128.8 KB (added by Honza_Kral, 6 years ago)
  • AUTHORS

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

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

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

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

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

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

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

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

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

    diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
    index 70def85..e5b9457 100644
    a b  
    22JP-specific Form helpers 
    33""" 
    44 
    5 from django.core import validators 
    6 from django.newforms import ValidationError 
    75from django.utils.translation import ugettext_lazy as _ 
    86from django.newforms.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 3ef0ade..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('-')) 
    14493    # This check is needed because strftime is used when saving the date 
    14594    # value to the database, and strftime requires that the year be >=1900. 
    14695    if year < 1900: 
    def _isValidDate(date_string): 
    14998        date(year, month, day) 
    15099    except ValueError, e: 
    151100        msg = _('Invalid date: %s') % _(str(e)) 
    152         raise ValidationError, msg 
    153101 
    154 def isValidANSIDate(field_data, all_data): 
    155     if not ansi_date_re.search(field_data): 
    156         raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.') 
    157     _isValidDate(field_data) 
     102def validate_ansi_date(value, message_dict={}): 
     103    validate_ansi_date_format(value, message_dict) 
     104    validate_correct_ansi_date(value, message_dict) 
    158105 
    159 def isValidANSITime(field_data, all_data): 
    160     if not ansi_time_re.search(field_data): 
    161         raise ValidationError, _('Enter a valid time in HH:MM format.') 
     106def validate_ansi_datetime(value, message_dict={}): 
     107    validate_ansi_datetime_format(value, message_dict) 
     108    validate_correct_ansi_date(value.split()[0], message_dict) 
    162109 
    163 def isValidANSIDatetime(field_data, all_data): 
    164     if not ansi_datetime_re.search(field_data): 
    165         raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.') 
    166     _isValidDate(field_data.split()[0]) 
     110def validate_not_empty(value, message_dict={}): 
     111    if value.strip() == '': 
     112        raise ValidationError, _("Empty values are not allowed here.") 
    167113 
    168 def isValidEmail(field_data, all_data): 
    169     if not email_re.search(field_data): 
    170         raise ValidationError, _('Enter a valid e-mail address.') 
     114def validate_digits_only(value, message_dict={}): 
     115    if not value.isdigit(): 
     116        raise ValidationError, _("Non-numeric characters aren't allowed here.") 
    171117 
    172 def isValidImage(field_data, all_data): 
    173     """ 
    174     Checks that the file-upload field data contains a valid image (GIF, JPG, 
    175     PNG, possibly others -- whatever the Python Imaging Library supports). 
    176     """ 
    177     from PIL import Image 
    178     from cStringIO import StringIO 
    179     try: 
    180         content = field_data.read() 
    181     except TypeError: 
    182         raise ValidationError, _("No file was submitted. Check the encoding type on the form.") 
    183     try: 
    184         # load() is the only method that can spot a truncated JPEG, 
    185         #  but it cannot be called sanely after verify() 
    186         trial_image = Image.open(StringIO(content)) 
    187         trial_image.load() 
    188         # verify() is the only method that can spot a corrupt PNG, 
    189         #  but it must be called immediately after the constructor 
    190         trial_image = Image.open(StringIO(content)) 
    191         trial_image.verify() 
    192     except Exception: # Python Imaging Library doesn't recognize it as an image 
    193         raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.") 
    194  
    195 def isValidImageURL(field_data, all_data): 
    196     uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png')) 
    197     try: 
    198         uc(field_data, all_data) 
    199     except URLMimeTypeCheck.InvalidContentType: 
    200         raise ValidationError, _("The URL %s does not point to a valid image.") % field_data 
     118def validate_not_digits_only(value, message_dict={}): 
     119    if value.isdigit(): 
     120        raise ValidationError, _("This value can't be comprised solely of digits.") 
    201121 
    202 def isValidPhone(field_data, all_data): 
    203     if not phone_re.search(field_data): 
    204         raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data 
     122def validate_letters_only(value, message_dict={}): 
     123    if not value.isalpha(): 
     124        raise ValidationError, _("Only alphabetical characters are allowed here.") 
    205125 
    206 def isValidQuicktimeVideoURL(field_data, all_data): 
    207     "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)" 
    208     uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',)) 
    209     try: 
    210         uc(field_data, all_data) 
    211     except URLMimeTypeCheck.InvalidContentType: 
    212         raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data 
     126def validate_lower(value, message_dict={}): 
     127    if value.lower() != value: 
     128        raise ValidationError, _("Uppercase letters are not allowed here.") 
    213129 
    214 def isValidURL(field_data, all_data): 
    215     if not url_re.search(field_data): 
    216         raise ValidationError, _("A valid URL is required.") 
     130def validate_upper(value, message_dict={}): 
     131    if value.upper() != value: 
     132        raise ValidationError, _("Lowercase letters are not allowed here.") 
    217133 
    218 def isValidHTML(field_data, all_data): 
    219     import urllib, urllib2 
    220     try: 
    221         u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'})) 
    222     except: 
    223         # Validator or Internet connection is unavailable. Fail silently. 
    224         return 
    225     html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid') 
    226     if html_is_valid: 
    227         return 
    228     from xml.dom.minidom import parseString 
    229     error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')] 
    230     raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages) 
    231  
    232 def isWellFormedXml(field_data, all_data): 
    233     from xml.dom.minidom import parseString 
    234     try: 
    235         parseString(field_data) 
    236     except Exception, e: # Naked except because we're not sure what will be thrown 
    237         raise ValidationError, _("Badly formed XML: %s") % str(e) 
     134def validate_comma_separated_integer_list(value, message_dict={}): 
     135    for supposed_integer in value.split(','): 
     136        try: 
     137            validate_integer(supposed_integer.strip()) 
     138        except ValidationError: 
     139            raise ValidationError, _("Enter valid integers separated by commas.") 
    238140 
    239 def isWellFormedXmlFragment(field_data, all_data): 
    240     isWellFormedXml('<root>%s</root>' % field_data, all_data) 
     141def validate_comma_separated_email_list(value, message_dict={}): 
     142    """ 
     143    Checks that value is a string of e-mail addresses separated by commas. 
     144    Blank value values will not throw a validation error, and whitespace 
     145    is allowed around the commas. 
     146    """ 
     147    for supposed_email in value.split(','): 
     148        try: 
     149            validate_email(supposed_email.strip()) 
     150        except ValidationError: 
     151            raise ValidationError, _("Enter valid e-mail addresses separated by commas.") 
    241152 
    242 def isExistingURL(field_data, all_data): 
     153def validate_existing_url(value, message_dict={}): 
    243154    try: 
    244155        headers = { 
    245156            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
    def isExistingURL(field_data, all_data): 
    248159            "Connection" : "close", 
    249160            "User-Agent": settings.URL_VALIDATOR_USER_AGENT 
    250161            } 
    251         req = urllib2.Request(field_data,None, headers) 
     162        req = urllib2.Request(value,None, headers) 
    252163        u = urllib2.urlopen(req) 
    253164    except ValueError: 
    254         raise ValidationError, _("Invalid URL: %s") % field_data 
     165        raise ValidationError, message_dict.get('invalid', _("Invalid URL: %s") % value) 
    255166    except urllib2.HTTPError, e: 
    256167        # 401s are valid; they just mean authorization is required. 
    257168        # 301 and 302 are redirects; they just mean look somewhere else. 
    258169        if str(e.code) not in ('401','301','302'): 
    259             raise ValidationError, _("The URL %s is a broken link.") % field_data 
     170            raise ValidationError, message_dict.get('invalid_link', _("This URL appears to be a broken link.")) 
    260171    except: # urllib2.URLError, httplib.InvalidURL, etc. 
    261         raise ValidationError, _("The URL %s is a broken link.") % field_data 
    262  
    263 def isValidUSState(field_data, all_data): 
    264     "Checks that the given string is a valid two-letter U.S. state abbreviation" 
    265     states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY'] 
    266     if field_data.upper() not in states: 
    267         raise ValidationError, _("Enter a valid U.S. state abbreviation.") 
    268  
    269 def hasNoProfanities(field_data, all_data): 
    270     """ 
    271     Checks that the given string has no profanities in it. This does a simple 
    272     check for whether each profanity exists within the string, so 'fuck' will 
    273     catch 'motherfucker' as well. Raises a ValidationError such as: 
    274         Watch your mouth! The words "f--k" and "s--t" are not allowed here. 
    275     """ 
    276     field_data = field_data.lower() # normalize 
    277     words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data] 
    278     if words_seen: 
    279         from django.utils.text import get_text_list 
    280         plural = len(words_seen) 
    281         raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.", 
    282             "Watch your mouth! The words %s are not allowed here.", plural) % \ 
    283             get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and')) 
    284  
    285 class AlwaysMatchesOtherField(object): 
    286     def __init__(self, other_field_name, error_message=None): 
    287         self.other = other_field_name 
    288         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other) 
    289         self.always_test = True 
    290  
    291     def __call__(self, field_data, all_data): 
    292         if field_data != all_data[self.other]: 
    293             raise ValidationError, self.error_message 
    294  
    295 class ValidateIfOtherFieldEquals(object): 
    296     def __init__(self, other_field, other_value, validator_list): 
    297         self.other_field, self.other_value = other_field, other_value 
    298         self.validator_list = validator_list 
    299         self.always_test = True 
    300  
    301     def __call__(self, field_data, all_data): 
    302         if self.other_field in all_data and all_data[self.other_field] == self.other_value: 
    303             for v in self.validator_list: 
    304                 v(field_data, all_data) 
    305  
    306 class RequiredIfOtherFieldNotGiven(object): 
    307     def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")): 
    308         self.other, self.error_message = other_field_name, error_message 
    309         self.always_test = True 
    310  
    311     def __call__(self, field_data, all_data): 
    312         if not all_data.get(self.other, False) and not field_data: 
    313             raise ValidationError, self.error_message 
    314  
    315 class RequiredIfOtherFieldsGiven(object): 
    316     def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): 
    317         self.other, self.error_message = other_field_names, error_message 
    318         self.always_test = True 
    319  
    320     def __call__(self, field_data, all_data): 
    321         for field in self.other: 
    322             if all_data.get(field, False) and not field_data: 
    323                 raise ValidationError, self.error_message 
    324  
    325 class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven): 
    326     "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list." 
    327     def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")): 
    328         RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message) 
    329  
    330 class RequiredIfOtherFieldEquals(object): 
    331     def __init__(self, other_field, other_value, error_message=None, other_label=None): 
    332         self.other_field = other_field 
    333         self.other_value = other_value 
    334         other_label = other_label or other_value 
    335         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), { 
    336             'field': other_field, 'value': other_label}) 
    337         self.always_test = True 
    338  
    339     def __call__(self, field_data, all_data): 
    340         if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data: 
    341             raise ValidationError(self.error_message) 
    342  
    343 class RequiredIfOtherFieldDoesNotEqual(object): 
    344     def __init__(self, other_field, other_value, other_label=None, error_message=None): 
    345         self.other_field = other_field 
    346         self.other_value = other_value 
    347         other_label = other_label or other_value 
    348         self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), { 
    349             'field': other_field, 'value': other_label}) 
    350         self.always_test = True 
    351  
    352     def __call__(self, field_data, all_data): 
    353         if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data: 
    354             raise ValidationError(self.error_message) 
    355  
    356 class IsLessThanOtherField(object): 
    357     def __init__(self, other_field_name, error_message): 
    358         self.other, self.error_message = other_field_name, error_message 
    359  
    360     def __call__(self, field_data, all_data): 
    361         if field_data > all_data[self.other]: 
    362             raise ValidationError, self.error_message 
    363  
    364 class UniqueAmongstFieldsWithPrefix(object): 
    365     def __init__(self, field_name, prefix, error_message): 
    366         self.field_name, self.prefix = field_name, prefix 
    367         self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.") 
    368  
    369     def __call__(self, field_data, all_data): 
    370         for field_name, value in all_data.items(): 
    371             if field_name != self.field_name and value == field_data: 
    372                 raise ValidationError, self.error_message 
    373  
    374 class NumberIsInRange(object): 
    375     """ 
    376     Validator that tests if a value is in a range (inclusive). 
    377     """ 
    378     def __init__(self, lower=None, upper=None, error_message=''): 
    379         self.lower, self.upper = lower, upper 
    380         if not error_message: 
    381             if lower and upper: 
    382                  self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper} 
    383             elif lower: 
    384                 self.error_message = _("This value must be at least %s.") % lower 
    385             elif upper: 
    386                 self.error_message = _("This value must be no more than %s.") % upper 
    387         else: 
    388             self.error_message = error_message 
    389  
    390     def __call__(self, field_data, all_data): 
    391         # Try to make the value numeric. If this fails, we assume another 
    392         # validator will catch the problem. 
    393         try: 
    394             val = float(field_data) 
    395         except ValueError: 
    396             return 
    397  
    398         # Now validate 
    399         if self.lower and self.upper and (val < self.lower or val > self.upper): 
    400             raise ValidationError(self.error_message) 
    401         elif self.lower and val < self.lower: 
    402             raise ValidationError(self.error_message) 
    403         elif self.upper and val > self.upper: 
    404             raise ValidationError(self.error_message) 
    405  
    406 class IsAPowerOf(object): 
    407     """ 
    408     Usage: If you create an instance of the IsPowerOf validator: 
    409         v = IsAPowerOf(2) 
    410      
    411     The following calls will succeed: 
    412         v(4, None)  
    413         v(8, None) 
    414         v(16, None) 
    415      
    416     But this call: 
    417         v(17, None) 
    418     will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']" 
    419     """ 
    420     def __init__(self, power_of): 
    421         self.power_of = power_of 
    422  
    423     def __call__(self, field_data, all_data): 
    424         from math import log 
    425         val = log(int(field_data)) / log(self.power_of) 
    426         if val != int(val): 
    427             raise ValidationError, _("This value must be a power of %s.") % self.power_of 
    428  
    429 class IsValidDecimal(object): 
    430     def __init__(self, max_digits, decimal_places): 
    431         self.max_digits, self.decimal_places = max_digits, decimal_places 
    432  
    433     def __call__(self, field_data, all_data): 
    434         try: 
    435             val = Decimal(field_data) 
    436         except DecimalException: 
    437             raise ValidationError, _("Please enter a valid decimal number.") 
    438  
    439         pieces = str(val).lstrip("-").split('.') 
    440         decimals = (len(pieces) == 2) and len(pieces[1]) or 0 
    441         digits = len(pieces[0]) 
    442  
    443         if digits + decimals > self.max_digits: 
    444             raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.", 
    445                 "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits 
    446         if digits > (self.max_digits - self.decimal_places): 
    447             raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.", 
    448                 "Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places) 
    449         if decimals > self.decimal_places: 
    450             raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.", 
    451                 "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places 
    452  
    453 def isValidFloat(field_data, all_data): 
    454     data = smart_str(field_data) 
    455     try: 
    456         float(data) 
    457     except ValueError: 
    458         raise ValidationError, _("Please enter a valid floating point number.") 
    459  
    460 class HasAllowableSize(object): 
    461     """ 
    462     Checks that the file-upload field data is a certain size. min_size and 
    463     max_size are measurements in bytes. 
    464     """ 
    465     def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None): 
    466         self.min_size, self.max_size = min_size, max_size 
    467         self.min_error_message = min_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size) 
    468         self.max_error_message = max_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size) 
    469  
    470     def __call__(self, field_data, all_data): 
    471         try: 
    472             content = field_data.read() 
    473         except TypeError: 
    474             raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.") 
    475         if self.min_size is not None and len(content) < self.min_size: 
    476             raise ValidationError, self.min_error_message 
    477         if self.max_size is not None and len(content) > self.max_size: 
    478             raise ValidationError, self.max_error_message 
    479  
    480 class MatchesRegularExpression(object): 
    481     """ 
    482     Checks that the field matches the given regular-expression. The regex 
    483     should be in string format, not already compiled. 
    484     """ 
    485     def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")): 
    486         self.regexp = re.compile(regexp) 
    487         self.error_message = error_message 
    488  
    489     def __call__(self, field_data, all_data): 
    490         if not self.regexp.search(field_data): 
    491             raise ValidationError(self.error_message) 
    492  
    493 class AnyValidator(object): 
    494     """ 
    495     This validator tries all given validators. If any one of them succeeds, 
    496     validation passes. If none of them succeeds, the given message is thrown 
    497     as a validation error. The message is rather unspecific, so it's best to 
    498     specify one on instantiation. 
    499     """ 
    500     def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")): 
    501         if validator_list is None: validator_list = [] 
    502         self.validator_list = validator_list 
    503         self.error_message = error_message 
    504         for v in validator_list: 
    505             if hasattr(v, 'always_test'): 
    506                 self.always_test = True 
    507  
    508     def __call__(self, field_data, all_data): 
    509         for v in self.validator_list: 
    510             try: 
    511                 v(field_data, all_data) 
    512                 return 
    513             except ValidationError, e: 
    514                 pass 
    515         raise ValidationError(self.error_message) 
     172        raise ValidationError, message_dict.get('invalid_link', _("This URL appears to be a broken link.")) 
    516173 
    517174class URLMimeTypeCheck(object): 
    518175    "Checks that the provided URL points to a document with a listed mime type" 
    class URLMimeTypeCheck(object): 
    524181    def __init__(self, mime_type_list): 
    525182        self.mime_type_list = mime_type_list 
    526183 
    527     def __call__(self, field_data, all_data): 
    528         import urllib2 
    529         try: 
    530             isValidURL(field_data, all_data) 
    531         except ValidationError: 
    532             raise 
     184    def __call__(self, value, message_dict={}): 
     185        validate_existing_url(value) 
    533186        try: 
    534             info = urllib2.urlopen(field_data).info() 
     187            info = urllib2.urlopen(value).info() 
    535188        except (urllib2.HTTPError, urllib2.URLError): 
    536             raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data 
     189            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % value 
    537190        content_type = info['content-type'] 
    538191        if content_type not in self.mime_type_list: 
    539192            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % { 
    540                 'url': field_data, 'contenttype': content_type} 
    541  
    542 class RelaxNGCompact(object): 
    543     "Validate against a Relax NG compact schema" 
    544     def __init__(self, schema_path, additional_root_element=None): 
    545         self.schema_path = schema_path 
    546         self.additional_root_element = additional_root_element 
    547  
    548     def __call__(self, field_data, all_data): 
    549         import os, tempfile 
    550         if self.additional_root_element: 
    551             field_data = '<%(are)s>%(data)s\n</%(are)s>' % { 
    552                 'are': self.additional_root_element, 
    553                 'data': field_data 
    554             } 
    555         filename = tempfile.mktemp() # Insecure, but nothing else worked 
    556         fp = open(filename, 'w') 
    557         fp.write(field_data) 
    558         fp.close() 
    559         if not os.path.exists(settings.JING_PATH): 
    560             raise Exception, "%s not found!" % settings.JING_PATH 
    561         p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename)) 
    562         errors = [line.strip() for line in p.readlines()] 
    563         p.close() 
    564         os.unlink(filename) 
    565         display_errors = [] 
    566         lines = field_data.split('\n') 
    567         for error in errors: 
    568             ignored, line, level, message = error.split(':', 3) 
    569             # Scrape the Jing error messages to reword them more nicely. 
    570             m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message) 
    571             if m: 
    572                 display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \ 
    573                     {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]}) 
    574                 continue 
    575             if message.strip() == 'text not allowed here': 
    576                 display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \ 
    577                     {'line':line, 'start':lines[int(line) - 1][:30]}) 
    578                 continue 
    579             m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message) 
    580             if m: 
    581                 display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \ 
    582                     {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
    583                 continue 
    584             m = re.search(r'\s*unknown element "(.*?)"', message) 
    585             if m: 
    586                 display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \ 
    587                     {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
    588                 continue 
    589             if message.strip() == 'required attributes missing': 
    590                 display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \ 
    591                     {'line':line, 'start':lines[int(line) - 1][:30]}) 
    592                 continue 
    593             m = re.search(r'\s*bad value for attribute "(.*?)"', message) 
    594             if m: 
    595                 display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \ 
    596                     {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]}) 
    597                 continue 
    598             # Failing all those checks, use the default error message. 
    599             display_error = 'Line %s: %s [%s]' % (line, message, level.strip()) 
    600             display_errors.append(display_error) 
    601         if len(display_errors) > 0: 
    602             raise ValidationError, display_errors 
     193                'url': value, 'contenttype': content_type} 
     194 
     195def validate_image_url(value, message_dict={}): 
     196    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png')) 
     197    try: 
     198        uc(value, message_dict) 
     199    except URLMimeTypeCheck.InvalidContentType: 
     200        raise ValidationError, _("The URL %s does not point to a valid image.") % value 
     201 
     202 
     203def validate_float(value, message_dict={}): 
     204    data = smart_str(value) 
     205    try: 
     206        float(data) 
     207    except ValueError: 
     208        raise ValidationError, _("Please enter a valid floating point number.") 
  • django/db/models/__init__.py

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

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 338b061..e6ae4bb 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 
     14from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS 
    1515from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist 
    1616from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 
    1717from django.db.models.query import delete_objects, Q, CollectedObjects 
    from django.db.models.loading import register_models, get_model 
    2222from django.dispatch import dispatcher 
    2323from django.utils.datastructures import SortedDict 
    2424from django.utils.functional import curry 
     25from django.utils.translation import ugettext_lazy as _ 
    2526from django.utils.encoding import smart_str, force_unicode, smart_unicode 
    2627from django.core.files.move import file_move_safe 
    2728from django.core.files import locks 
    class Model(object): 
    352353 
    353354    save_base.alters_data = True 
    354355 
    355     def validate(self): 
     356    def clean(self, new_data=None): 
     357        self.to_python() 
     358        self.validate(new_data) 
     359 
     360    def to_python(self): 
     361        error_dict = {} 
     362        for f in self._meta.fields: 
     363            try: 
     364                value = f.to_python(getattr(self, f.attname, f.get_default())) 
     365                setattr(self, f.attname, value) 
     366            except ValidationError, e: 
     367                error_dict[f.name] = e.messages 
     368        if error_dict: 
     369            raise ValidationError(error_dict) 
     370 
     371    def validate(self, new_data=None): 
    356372        """ 
    357         First coerces all fields on this instance to their proper Python types. 
    358         Then runs validation on every field. Returns a dictionary of 
    359         field_name -> error_list. 
     373        Validate the data on the model, if new_data is supplied, try and use those instead of 
     374        actual values on the fields. Note that the fields are validated separately. 
    360375        """ 
     376        if new_data is not None: 
     377            def get_value(f): 
     378                if f.name in new_data: 
     379                    return f.to_python(new_data[f.name]) 
     380                return getattr(self, f.attname, f.get_default()) 
     381        else: 
     382            get_value = lambda f: getattr(self, f.attname, f.get_default()) 
    361383        error_dict = {} 
    362         invalid_python = {} 
    363384        for f in self._meta.fields: 
    364385            try: 
    365                 setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default()))) 
    366             except validators.ValidationError, e: 
     386                value = get_value(f) 
     387                f.validate(value, instance=self) 
     388                if hasattr(self, 'validate_%s' % f.name): 
     389                    getattr(self, 'validate_%s' % f.name)(value) 
     390            except ValidationError, e: 
    367391                error_dict[f.name] = e.messages 
    368                 invalid_python[f.name] = 1 
    369         for f in self._meta.fields: 
    370             if f.name in invalid_python: 
    371                 continue 
    372             errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__) 
    373             if errors: 
    374                 error_dict[f.name] = errors 
    375         return error_dict 
     392 
     393        for un_together in self._meta.unique_together: 
     394            lookup = {} 
     395            for name in un_together: 
     396                if name in error_dict: 
     397                    break 
     398                f = self._meta.get_field(name) 
     399                lookup['%s__exact' % name] = get_value(f) 
     400            try: 
     401                qset = self.__class__._default_manager.all() 
     402                if self.pk: 
     403                    qset = qset.exclude(pk=self.pk) 
     404                obj = qset.get(**lookup) 
     405                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together) 
     406            except self.DoesNotExist: 
     407                pass 
     408 
     409        if error_dict: 
     410            raise ValidationError(error_dict) 
    376411 
    377412    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False): 
    378413        """ 
  • django/db/models/fields/__init__.py

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

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

    diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
    index 0d9c68f..5d5884e 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 * 
     18 
  • django/newforms/fields.py

    diff --git a/django/newforms/fields.py b/django/newforms/fields.py
    index ad46d78..0b728cb 100644
    a b except NameError: 
    2424 
    2525from django.utils.translation import ugettext_lazy as _ 
    2626from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str 
     27from django.core.exceptions import ValidationError 
     28from django.core import validators 
    2729 
    28 from util import ErrorList, ValidationError 
    2930from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput 
     31from util import ErrorList 
    3032from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile 
    3133 
    3234__all__ = ( 
    EMPTY_VALUES = (None, '') 
    4648 
    4749class Field(object): 
    4850    widget = TextInput # Default widget to use when rendering this type of Field. 
     51    validators = [] 
    4952    hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". 
    5053    default_error_messages = { 
    5154        'required': _(u'This field is required.'), 
    class Field(object): 
    5659    creation_counter = 0 
    5760 
    5861    def __init__(self, required=True, widget=None, label=None, initial=None, 
    59                  help_text=None, error_messages=None): 
     62                 help_text=None, error_messages=None, validators=[]): 
    6063        # required -- Boolean that specifies whether the field is required. 
    6164        #             True by default. 
    6265        # widget -- A Widget class, or instance of a Widget class, that should 
    class Field(object): 
    7073        # initial -- A value to use in this Field's initial display. This value 
    7174        #            is *not* used as a fallback if data isn't given. 
    7275        # help_text -- An optional string to use as "help text" for this Field. 
     76        # validators -- Optional list of additional validator functions 
    7377        if label is not None: 
    7478            label = smart_unicode(label) 
     79        self.validators = self.validators + validators 
    7580        self.required, self.label, self.initial = required, label, initial 
    7681        self.help_text = smart_unicode(help_text or '') 
    7782        widget = widget or self.widget 
    class Field(object): 
    99104        messages.update(error_messages or {}) 
    100105        self.error_messages = messages 
    101106 
     107    def to_python(self, value): 
     108        if value in EMPTY_VALUES: 
     109            return None 
     110        return smart_unicode(value) 
     111 
     112    def validate(self, value): 
     113        if self.required and value in EMPTY_VALUES: 
     114            raise ValidationError(self.error_messages['required']) 
     115        elif value in EMPTY_VALUES: 
     116            return 
     117        elist = ErrorList() 
     118        for validator in self.validators: 
     119            try: 
     120                validator(value, self.error_messages) 
     121            except ValidationError, e: 
     122                elist.extend(e.messages) 
     123        if elist: 
     124            raise ValidationError(elist) 
     125 
    102126    def clean(self, value): 
    103127        """ 
    104128        Validates the given value and returns its "cleaned" value as an 
    class Field(object): 
    106130 
    107131        Raises ValidationError for any errors. 
    108132        """ 
    109         if self.required and value in EMPTY_VALUES: 
    110             raise ValidationError(self.error_messages['required']) 
     133        value = self.to_python(value) 
     134        self.validate(value) 
    111135        return value 
    112136 
    113137    def widget_attrs(self, widget): 
    class CharField(Field): 
    134158        self.max_length, self.min_length = max_length, min_length 
    135159        super(CharField, self).__init__(*args, **kwargs) 
    136160 
    137     def clean(self, value): 
    138         "Validates max_length and min_length. Returns a Unicode object." 
    139         super(CharField, self).clean(value) 
     161    def to_python(self, value): 
    140162        if value in EMPTY_VALUES: 
    141163            return u'' 
    142         value = smart_unicode(value) 
     164        return smart_unicode(value) 
     165 
     166    def validate(self, value): 
     167        "Validates max_length and min_length. Returns a Unicode object." 
     168        super(CharField, self).validate(value) 
    143169        value_length = len(value) 
     170        if value_length == 0 and not self.required: 
     171            return  
    144172        if self.max_length is not None and value_length > self.max_length: 
    145173            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) 
    146174        if self.min_length is not None and value_length < self.min_length: 
    147175            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) 
    148         return value 
    149176 
    150177    def widget_attrs(self, widget): 
    151178        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): 
    class IntegerField(Field): 
    163190        self.max_value, self.min_value = max_value, min_value 
    164191        super(IntegerField, self).__init__(*args, **kwargs) 
    165192 
    166     def clean(self, value): 
    167         """ 
    168         Validates that int() can be called on the input. Returns the result 
    169         of int(). Returns None for empty values. 
    170         """ 
    171         super(IntegerField, self).clean(value) 
     193    def to_python(self, value): 
    172194        if value in EMPTY_VALUES: 
    173195            return None 
    174196        try: 
    175             value = int(str(value)) 
     197            return int(smart_str(value)) 
    176198        except (ValueError, TypeError): 
    177199            raise ValidationError(self.error_messages['invalid']) 
     200 
     201    def validate(self, value): 
     202        """ 
     203        Validates that int() can be called on the input. Returns the result 
     204        of int(). Returns None for empty values. 
     205        """ 
     206        super(IntegerField, self).validate(value) 
     207        if value is None: return 
    178208        if self.max_value is not None and value > self.max_value: 
    179209            raise ValidationError(self.error_messages['max_value'] % self.max_value) 
    180210        if self.min_value is not None and value < self.min_value: 
    181211            raise ValidationError(self.error_messages['min_value'] % self.min_value) 
    182         return value 
    183212 
    184213class FloatField(Field): 
    185214    default_error_messages = { 
    class FloatField(Field): 
    192221        self.max_value, self.min_value = max_value, min_value 
    193222        Field.__init__(self, *args, **kwargs) 
    194223 
    195     def clean(self, value): 
     224    def to_python(self, value): 
    196225        """ 
    197226        Validates that float() can be called on the input. Returns a float. 
    198227        Returns None for empty values. 
    199228        """ 
    200         super(FloatField, self).clean(value) 
    201         if not self.required and value in EMPTY_VALUES: 
     229        if value in EMPTY_VALUES: 
    202230            return None 
    203231        try: 
    204             value = float(value) 
     232            return float(value) 
    205233        except (ValueError, TypeError): 
    206234            raise ValidationError(self.error_messages['invalid']) 
     235 
     236    def validate(self, value): 
     237        super(FloatField, self).validate(value) 
     238        if value is None: return 
    207239        if self.max_value is not None and value > self.max_value: 
    208240            raise ValidationError(self.error_messages['max_value'] % self.max_value) 
    209241        if self.min_value is not None and value < self.min_value: 
    210242            raise ValidationError(self.error_messages['min_value'] % self.min_value) 
    211         return value 
    212243 
    213244class DecimalField(Field): 
    214245    default_error_messages = { 
    class DecimalField(Field): 
    225256        self.max_digits, self.decimal_places = max_digits, decimal_places 
    226257        Field.__init__(self, *args, **kwargs) 
    227258 
    228     def clean(self, value): 
     259    def to_python(self, value): 
    229260        """ 
    230261        Validates that the input is a decimal number. Returns a Decimal 
    231262        instance. Returns None for empty values. Ensures that there are no more 
    232263        than max_digits in the number, and no more than decimal_places digits 
    233264        after the decimal point. 
    234265        """ 
    235         super(DecimalField, self).clean(value) 
    236         if not self.required and value in EMPTY_VALUES: 
     266        if value in EMPTY_VALUES: 
    237267            return None 
    238268        value = smart_str(value).strip() 
    239269        try: 
    240             value = Decimal(value) 
     270            return Decimal(value) 
    241271        except DecimalException: 
    242272            raise ValidationError(self.error_messages['invalid']) 
     273 
     274    def validate(self, value): 
     275        super(DecimalField, self).validate(value) 
     276        if value is None: return 
    243277        pieces = str(value).lstrip("-").split('.') 
    244278        decimals = (len(pieces) == 2) and len(pieces[1]) or 0 
    245279        digits = len(pieces[0]) 
    class DecimalField(Field): 
    253287            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 
    254288        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): 
    255289            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 
    256         return value 
    257290 
    258291DEFAULT_DATE_INPUT_FORMATS = ( 
    259292    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' 
    class DateField(Field): 
    272305        super(DateField, self).__init__(*args, **kwargs) 
    273306        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS 
    274307 
    275     def clean(self, value): 
     308    def to_python(self, value): 
    276309        """ 
    277310        Validates that the input can be converted to a date. Returns a Python 
    278311        datetime.date object. 
    279312        """ 
    280         super(DateField, self).clean(value) 
    281313        if value in EMPTY_VALUES: 
    282314            return None 
    283315        if isinstance(value, datetime.datetime): 
    class TimeField(Field): 
    305337        super(TimeField, self).__init__(*args, **kwargs) 
    306338        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS 
    307339 
    308     def clean(self, value): 
     340    def to_python(self, value): 
    309341        """ 
    310342        Validates that the input can be converted to a time. Returns a Python 
    311343        datetime.time object. 
    312344        """ 
    313         super(TimeField, self).clean(value) 
    314345        if value in EMPTY_VALUES: 
    315346            return None 
    316347        if isinstance(value, datetime.time): 
    class DateTimeField(Field): 
    344375        super(DateTimeField, self).__init__(*args, **kwargs) 
    345376        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS 
    346377 
    347     def clean(self, value): 
     378    def to_python(self, value): 
    348379        """ 
    349380        Validates that the input can be converted to a datetime. Returns a 
    350381        Python datetime.datetime object. 
    351382        """ 
    352         super(DateTimeField, self).clean(value) 
    353383        if value in EMPTY_VALUES: 
    354384            return None 
    355385        if isinstance(value, datetime.datetime): 
    class RegexField(CharField): 
    386416            regex = re.compile(regex) 
    387417        self.regex = regex 
    388418 
    389     def clean(self, value): 
     419    def validate(self, value): 
    390420        """ 
    391421        Validates that the input matches the regular expression. Returns a 
    392422        Unicode object. 
    393423        """ 
    394         value = super(RegexField, self).clean(value) 
     424        super(RegexField, self).validate(value) 
    395425        if value == u'': 
    396             return value 
     426            return 
    397427        if not self.regex.search(value): 
    398428            raise ValidationError(self.error_messages['invalid']) 
    399         return value 
    400429 
    401430email_re = re.compile( 
    402431    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom 
    403432    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 
    404433    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain 
    405434 
    406 class EmailField(RegexField): 
     435class EmailField(CharField): 
    407436    default_error_messages = { 
    408437        'invalid': _(u'Enter a valid e-mail address.'), 
    409438    } 
    410  
    411     def __init__(self, max_length=None, min_length=None, *args, **kwargs): 
    412         RegexField.__init__(self, email_re, max_length, min_length, *args, 
    413                             **kwargs) 
     439    validators = [validators.validate_email] 
    414440 
    415441try: 
    416442    from django.conf import settings 
    class FileField(Field): 
    424450    widget = FileInput 
    425451    default_error_messages = { 
    426452        'invalid': _(u"No file was submitted. Check the encoding type on the form."), 
    427         'missing': _(u"No file was submitted."), 
    428453        'empty': _(u"The submitted file is empty."), 
    429454    } 
    430455 
    431     def __init__(self, *args, **kwargs): 
    432         super(FileField, self).__init__(*args, **kwargs) 
    433  
    434     def clean(self, data, initial=None): 
    435         super(FileField, self).clean(initial or data) 
     456    def to_python(self, data, initial=None): 
    436457        if not self.required and data in EMPTY_VALUES: 
    437458            return None 
    438459        elif not data and initial: 
    class FileField(Field): 
    448469                category = DeprecationWarning, 
    449470                stacklevel = 2 
    450471            ) 
     472            if not data: 
     473                raise ValidationError(self.error_messages['invalid']) 
    451474            data = UploadedFile(data['filename'], data['content']) 
    452475 
    453476        try: 
    class FileField(Field): 
    463486 
    464487        return data 
    465488 
     489    def clean(self, value, initial=None): 
     490        "overriden clean to provide extra argument initial" 
     491        value = self.to_python(value, initial) 
     492        self.validate(value) 
     493        return value 
     494 
    466495class ImageField(FileField): 
    467496    default_error_messages = { 
    468497        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), 
    469498    } 
    470499 
    471     def clean(self, data, initial=None): 
    472         """ 
    473         Checks that the file-upload field data contains a valid image (GIF, JPG, 
    474         PNG, possibly others -- whatever the Python Imaging Library supports). 
    475         """ 
    476         f = super(ImageField, self).clean(data, initial) 
    477         if f is None: 
    478             return None 
    479         elif not data and initial: 
    480             return initial 
     500    def validate(self, data): 
     501        super(ImageField, self).validate(data) 
     502        if data is None: 
     503            return 
    481504        from PIL import Image 
    482  
    483505        # We need to get a file object for PIL. We might have a path or we might 
    484506        # have to read the data into memory. 
    485507        if hasattr(data, 'temporary_file_path'): 
    class ImageField(FileField): 
    487509        else: 
    488510            if hasattr(data, 'read'): 
    489511                file = StringIO(data.read()) 
     512            elif isinstance(data, UploadedFile): 
     513                file = StringIO(data.data.read()) 
    490514            else: 
    491                 file = StringIO(data['content']) 
     515                file = data 
    492516 
    493517        try: 
    494518            # load() is the only method that can spot a truncated JPEG, 
    class ImageField(FileField): 
    507531            trial_image.verify() 
    508532        except Exception: # Python Imaging Library doesn't recognize it as an image 
    509533            raise ValidationError(self.error_messages['invalid_image']) 
    510         return f 
    511534 
    512535url_re = re.compile( 
    513536    r'^https?://' # http:// or https:// 
    class URLField(RegexField): 
    530553        self.verify_exists = verify_exists 
    531554        self.user_agent = validator_user_agent 
    532555 
    533     def clean(self, value): 
    534         # If no URL scheme given, assume http:// 
     556    def to_python(self, value): 
     557        value = super(URLField, self).to_python(value) 
    535558        if value and '://' not in value: 
    536559            value = u'http://%s' % value 
    537         value = super(URLField, self).clean(value) 
     560        return value 
     561 
     562    def validate(self, value): 
     563        # If no URL scheme given, assume http:// 
     564        super(URLField, self).validate(value) 
    538565        if value == u'': 
    539             return value 
     566            return 
    540567        if self.verify_exists: 
    541             import urllib2 
    542             headers = { 
    543                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
    544                 "Accept-Language": "en-us,en;q=0.5", 
    545                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", 
    546                 "Connection": "close", 
    547                 "User-Agent": self.user_agent, 
    548             } 
    549             try: 
    550                 req = urllib2.Request(value, None, headers) 
    551                 u = urllib2.urlopen(req) 
    552             except ValueError: 
    553                 raise ValidationError(self.error_messages['invalid']) 
    554             except: # urllib2.URLError, httplib.InvalidURL, etc. 
    555                 raise ValidationError(self.error_messages['invalid_link']) 
    556         return value 
     568            # we cannot put this in self.validators because its conditional 
     569            validators.validate_existing_url(value, self.error_messages) 
    557570 
    558571class BooleanField(Field): 
    559572    widget = CheckboxInput 
    560573 
    561     def clean(self, value): 
     574    def to_python(self, value): 
    562575        """Returns a Python boolean object.""" 
    563576        # Explicitly check for the string 'False', which is what a hidden field 
    564577        # will submit for False. Because bool("True") == True, we don't need to 
    class BooleanField(Field): 
    567580            value = False 
    568581        else: 
    569582            value = bool(value) 
    570         super(BooleanField, self).clean(value) 
    571         if not value and self.required: 
    572             raise ValidationError(self.error_messages['required']) 
    573583        return value 
    574584 
     585    def validate(self, value): 
     586        if self.required and not value: 
     587            raise ValidationError(self.error_messages['required']) 
     588 
     589 
    575590class NullBooleanField(BooleanField): 
    576591    """ 
    577592    A field whose valid values are None, True and False. Invalid values are 
    578593    cleaned to None. 
     594 
     595    Note that validation doesn't apply here. 
    579596    """ 
    580597    widget = NullBooleanSelect 
    581598 
    582     def clean(self, value): 
     599    def to_python(self, value): 
    583600        return {True: True, False: False}.get(value, None) 
    584601 
     602    def validate(self, value): 
     603        pass 
     604 
     605 
    585606class ChoiceField(Field): 
    586607    widget = Select 
    587608    default_error_messages = { 
    class ChoiceField(Field): 
    605626 
    606627    choices = property(_get_choices, _set_choices) 
    607628 
    608     def clean(self, value): 
    609         """ 
    610         Validates that the input is in self.choices. 
    611         """ 
    612         value = super(ChoiceField, self).clean(value) 
    613         if value in EMPTY_VALUES: 
    614             value = u'' 
    615         value = smart_unicode(value) 
    616         if value == u'': 
    617             return value 
     629    def validate(self, value): 
     630        super(ChoiceField, self).validate(value) 
     631        if value is None and not self.required: 
     632            return u'' 
    618633        valid_values = set([smart_unicode(k) for k, v in self.choices]) 
    619634        if value not in valid_values: 
    620635            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 
    621         return value 
    622636 
    623637class MultipleChoiceField(ChoiceField): 
    624638    hidden_widget = MultipleHiddenInput 
    class MultipleChoiceField(ChoiceField): 
    628642        'invalid_list': _(u'Enter a list of values.'), 
    629643    } 
    630644 
    631     def clean(self, value): 
     645    def to_python(self, value): 
    632646        """ 
    633647        Validates that the input is a list or tuple. 
    634648        """ 
    635         if self.required and not value: 
    636             raise ValidationError(self.error_messages['required']) 
    637         elif not self.required and not value: 
     649        if not value: 
    638650            return [] 
    639651        if not isinstance(value, (list, tuple)): 
    640652            raise ValidationError(self.error_messages['invalid_list']) 
    641         new_value = [smart_unicode(val) for val in value] 
     653        return [smart_unicode(val) for val in value] 
     654 
     655    def validate(self, value): 
    642656        # Validate that each value in the value list is in self.choices. 
     657        if self.required and value == []: 
     658            raise ValidationError(self.error_messages['required']) 
    643659        valid_values = set([smart_unicode(k) for k, v in self.choices]) 
    644         for val in new_value: 
     660        for val in value: 
    645661            if val not in valid_values: 
    646662                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) 
    647         return new_value 
    648663 
    649664class ComboField(Field): 
    650665    """ 
    class ComboField(Field): 
    659674            f.required = False 
    660675        self.fields = fields 
    661676 
    662     def clean(self, value): 
     677    def to_python(self, value): 
     678        for field in self.fields: 
     679            value = field.to_python(value) 
     680        return value 
     681 
     682    def validate(self, value): 
    663683        """ 
    664684        Validates the given value against all of self.fields, which is a 
    665685        list of Field instances. 
    666686        """ 
    667         super(ComboField, self).clean(value) 
     687        super(ComboField, self).validate(value) 
    668688        for field in self.fields: 
    669             value = field.clean(value) 
    670         return value 
     689            field.validate(value) 
    671690 
    672691class MultiValueField(Field): 
    673692    """ 
    class MultiValueField(Field): 
    699718            f.required = False 
    700719        self.fields = fields 
    701720 
    702     def clean(self, value): 
     721    def to_python(self, value): 
    703722        """ 
    704723        Validates every value in the given list. A value is validated against 
    705724        the corresponding Field in self.fields. 
    class MultiValueField(Field): 
    713732        if not value or isinstance(value, (list, tuple)): 
    714733            if not value or not [v for v in value if v not in EMPTY_VALUES]: 
    715734                if self.required: 
    716                     raise ValidationError(self.error_messages['required']) 
     735                    return None 
    717736                else: 
    718737                    return self.compress([]) 
    719738        else: 
    720739            raise ValidationError(self.error_messages['invalid']) 
     740 
    721741        for i, field in enumerate(self.fields): 
    722742            try: 
    723743                field_value = value[i] 
    class SplitDateTimeField(MultiValueField): 
    801821            return datetime.datetime.combine(*data_list) 
    802822        return None 
    803823 
    804 ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') 
    805  
    806 class IPAddressField(RegexField): 
     824class IPAddressField(CharField): 
    807825    default_error_messages = { 
    808826        'invalid': _(u'Enter a valid IPv4 address.'), 
    809827    } 
    810  
    811     def __init__(self, *args, **kwargs): 
    812         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) 
     828    validators = [validators.validate_ip_address4] 
  • django/newforms/forms.py

    diff --git a/django/newforms/forms.py b/django/newforms/forms.py
    index fc203f3..524a903 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.newforms import ValidationError, NON_FIELD_ERRORS 
    1112 
    1213from fields import Field, FileField 
    1314from widgets import TextInput, Textarea 
    14 from util import flatatt, ErrorDict, ErrorList, ValidationError 
     15from util import flatatt, ErrorDict, 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): 
    206206                else: 
    207207                    value = field.clean(value) 
    208208                self.cleaned_data[name] = value 
     209                # FIXME deprecated - keeping this here for backwards compatibility 
    209210                if hasattr(self, 'clean_%s' % name): 
    210211                    value = getattr(self, 'clean_%s' % name)() 
    211212                    self.cleaned_data[name] = value 
     213 
     214                if hasattr(self, 'validate_%s' % name): 
     215                    getattr(self, 'validate_%s' % name)(value) 
    212216            except ValidationError, e: 
    213                 self._errors[name] = e.messages 
     217                self._errors[name] = ErrorList(e.messages) 
    214218                if name in self.cleaned_data: 
    215219                    del self.cleaned_data[name] 
    216220        try: 
    217             self.cleaned_data = self.clean() 
     221            self.validate() 
    218222        except ValidationError, e: 
    219             self._errors[NON_FIELD_ERRORS] = e.messages 
     223            if hasattr(e, 'message_dict'): 
     224                for k, v in e.message_dict.items(): 
     225                    self._errors.setdefault(k, []).extend(v) 
     226            else: 
     227                self._errors[NON_FIELD_ERRORS] = ErrorList(e.messages) 
    220228        if self._errors: 
    221229            delattr(self, 'cleaned_data') 
    222230 
    223231    def clean(self): 
    224232        """ 
     233        FIXME: deprecated, use validate() instead 
     234 
    225235        Hook for doing any extra form-wide cleaning after Field.clean() been 
    226236        called on every field. Any ValidationError raised by this method will 
    227237        not be associated with a particular field; it will have a special-case 
    class BaseForm(StrAndUnicode): 
    229239        """ 
    230240        return self.cleaned_data 
    231241 
     242    def validate(self): 
     243        self.cleaned_data = self.clean() 
     244 
    232245    def is_multipart(self): 
    233246        """ 
    234247        Returns True if the form needs to be multipart-encrypted, i.e. it has 
  • django/newforms/models.py

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

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

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

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

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

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index f2b0e05..2b55931 100644
    a b Create a new article, with categories, via the form. 
    485485...         model = Article 
    486486>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', 
    487487...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) 
     488>>> f.is_valid() 
     489True 
    488490>>> new_art = f.save() 
    489491>>> new_art.id 
    4904922 
    u'...test2.txt' 
    859861>>> instance.delete() 
    860862 
    861863# Test the non-required FileField 
    862  
     864# It should fail since the field IS required on the model 
    863865>>> f = TextFileForm(data={'description': u'Assistance'}) 
    864866>>> f.fields['file'].required = False 
    865867>>> f.is_valid() 
    866 True 
    867 >>> instance = f.save() 
    868 >>> instance.file 
    869 '' 
     868False 
     869>>> f.errors 
     870{'file': [u'This field is required.']} 
    870871 
    871872>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) 
    872873>>> f.is_valid() 
    u'...test2.png' 
    968969>>> f = ImageFileForm(data={'description': u'Test'}) 
    969970>>> f.fields['image'].required = False 
    970971>>> f.is_valid() 
    971 True 
    972 >>> instance = f.save() 
    973 >>> instance.image 
    974 '' 
     972False 
     973>>> f.errors 
     974{'image': [u'This field is required.']} 
     975 
    975976 
    976977>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) 
    977978>>> f.is_valid() 
  • tests/modeltests/validation/models.py

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

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

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

    diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
    index 580326f..9b62ee0 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 f266e7b..597a3e7 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. That choice is not one of the availab 
    10001025 
    10011026>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False) 
    10021027>>> f.clean('') 
    1003 u'' 
    10041028>>> f.clean(None) 
    1005 u'' 
    10061029>>> f.clean(1) 
    10071030u'1' 
    10081031>>> f.clean('1') 
  • tests/regressiontests/forms/forms.py

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

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

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