Ticket #6845: 6845-against-8198.patch

File 6845-against-8198.patch, 132.4 KB (added by Honza Král, 16 years ago)

see attached comment for details about this patch

  • AUTHORS

    commit ccf27602e0b9fa46bbe3a246a45ae7524ed574be
    Author: Honza Král <Honza.Kral@gmail.com>
    Date:   Sun Aug 3 18:28:59 2008 +0200
    
        Ticket #6845
    
    diff --git a/AUTHORS b/AUTHORS
    index a9f5583..633070c 100644
    a b answer newbie questions, and generally made Django that much better:  
    225225    Igor Kolar <ike@email.si>
    226226    Gasper Koren
    227227    Martin Kosír <martin@martinkosir.net>
     228    Honza Kral <Honza.Kral@gmail.com>
    228229    Meir Kriheli <http://mksoft.co.il/>
    229230    Bruce Kroeze <http://coderseye.com/>
    230231    krzysiek.pawlik@silvermedia.pl
  • django/contrib/admin/views/template.py

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

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

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

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

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

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

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

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

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

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

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

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

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

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index e814198..60fe8d3 100644
    a b class ManyToManyField(RelatedField, Field):  
    830830        objects = mod._default_manager.in_bulk(pks)
    831831        if len(objects) != len(pks):
    832832            badkeys = [k for k in pks if k not in objects]
    833             raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
     833            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
    834834                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
    835835                'self': self.verbose_name,
    836836                'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
  • django/forms/__init__.py

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

    diff --git a/django/forms/fields.py b/django/forms/fields.py
    index 47ae5e1..904849c 100644
    a b except NameError:  
    2525
    2626from django.utils.translation import ugettext_lazy as _
    2727from django.utils.encoding import smart_unicode, smart_str
     28from django.core.exceptions import ValidationError
     29from django.core import validators
    2830
    29 from util import ErrorList, ValidationError
    3031from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
     32from util import ErrorList
    3133from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
    3234
    3335__all__ = (
    EMPTY_VALUES = (None, '')  
    4749
    4850class Field(object):
    4951    widget = TextInput # Default widget to use when rendering this type of Field.
     52    validators = []
    5053    hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
    5154    default_error_messages = {
    5255        'required': _(u'This field is required.'),
    class Field(object):  
    5760    creation_counter = 0
    5861
    5962    def __init__(self, required=True, widget=None, label=None, initial=None,
    60                  help_text=None, error_messages=None):
     63                 help_text=None, error_messages=None, validators=[]):
    6164        # required -- Boolean that specifies whether the field is required.
    6265        #             True by default.
    6366        # widget -- A Widget class, or instance of a Widget class, that should
    class Field(object):  
    7174        # initial -- A value to use in this Field's initial display. This value
    7275        #            is *not* used as a fallback if data isn't given.
    7376        # help_text -- An optional string to use as "help text" for this Field.
     77        # validators -- Optional list of additional validator functions
    7478        if label is not None:
    7579            label = smart_unicode(label)
     80        self.validators = self.validators + validators
    7681        self.required, self.label, self.initial = required, label, initial
    7782        if help_text is None:
    7883            self.help_text = u''
    class Field(object):  
    103108        messages.update(error_messages or {})
    104109        self.error_messages = messages
    105110
     111    def to_python(self, value):
     112        if value in EMPTY_VALUES:
     113            return None
     114        return smart_unicode(value)
     115
     116    def validate(self, value):
     117        if self.required and value in EMPTY_VALUES:
     118            raise ValidationError(self.error_messages['required'])
     119        elif value in EMPTY_VALUES:
     120            return
     121        elist = ErrorList()
     122        for validator in self.validators:
     123            try:
     124                validator(value, self.error_messages)
     125            except ValidationError, e:
     126                elist.extend(e.messages)
     127        if elist:
     128            raise ValidationError(elist)
     129
    106130    def clean(self, value):
    107131        """
    108132        Validates the given value and returns its "cleaned" value as an
    class Field(object):  
    110134
    111135        Raises ValidationError for any errors.
    112136        """
    113         if self.required and value in EMPTY_VALUES:
    114             raise ValidationError(self.error_messages['required'])
     137        value = self.to_python(value)
     138        self.validate(value)
    115139        return value
    116140
    117141    def widget_attrs(self, widget):
    class CharField(Field):  
    138162        self.max_length, self.min_length = max_length, min_length
    139163        super(CharField, self).__init__(*args, **kwargs)
    140164
    141     def clean(self, value):
    142         "Validates max_length and min_length. Returns a Unicode object."
    143         super(CharField, self).clean(value)
     165    def to_python(self, value):
    144166        if value in EMPTY_VALUES:
    145167            return u''
    146         value = smart_unicode(value)
     168        return smart_unicode(value)
     169
     170    def validate(self, value):
     171        "Validates max_length and min_length. Returns a Unicode object."
     172        super(CharField, self).validate(value)
    147173        value_length = len(value)
     174        if value_length == 0 and not self.required:
     175            return
    148176        if self.max_length is not None and value_length > self.max_length:
    149177            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
    150178        if self.min_length is not None and value_length < self.min_length:
    151179            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
    152         return value
    153180
    154181    def widget_attrs(self, widget):
    155182        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
    class IntegerField(Field):  
    167194        self.max_value, self.min_value = max_value, min_value
    168195        super(IntegerField, self).__init__(*args, **kwargs)
    169196
    170     def clean(self, value):
    171         """
    172         Validates that int() can be called on the input. Returns the result
    173         of int(). Returns None for empty values.
    174         """
    175         super(IntegerField, self).clean(value)
     197    def to_python(self, value):
    176198        if value in EMPTY_VALUES:
    177199            return None
    178200        try:
    179             value = int(str(value))
     201            return int(smart_str(value))
    180202        except (ValueError, TypeError):
    181203            raise ValidationError(self.error_messages['invalid'])
     204
     205    def validate(self, value):
     206        """
     207        Validates that int() can be called on the input. Returns the result
     208        of int(). Returns None for empty values.
     209        """
     210        super(IntegerField, self).validate(value)
     211        if value is None: return
    182212        if self.max_value is not None and value > self.max_value:
    183213            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    184214        if self.min_value is not None and value < self.min_value:
    185215            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    186         return value
    187216
    188217class FloatField(Field):
    189218    default_error_messages = {
    class FloatField(Field):  
    196225        self.max_value, self.min_value = max_value, min_value
    197226        Field.__init__(self, *args, **kwargs)
    198227
    199     def clean(self, value):
     228    def to_python(self, value):
    200229        """
    201230        Validates that float() can be called on the input. Returns a float.
    202231        Returns None for empty values.
    203232        """
    204         super(FloatField, self).clean(value)
    205         if not self.required and value in EMPTY_VALUES:
     233        if value in EMPTY_VALUES:
    206234            return None
    207235        try:
    208             value = float(value)
     236            return float(value)
    209237        except (ValueError, TypeError):
    210238            raise ValidationError(self.error_messages['invalid'])
     239
     240    def validate(self, value):
     241        super(FloatField, self).validate(value)
     242        if value is None: return
    211243        if self.max_value is not None and value > self.max_value:
    212244            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    213245        if self.min_value is not None and value < self.min_value:
    214246            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    215         return value
    216247
    217248class DecimalField(Field):
    218249    default_error_messages = {
    class DecimalField(Field):  
    229260        self.max_digits, self.decimal_places = max_digits, decimal_places
    230261        Field.__init__(self, *args, **kwargs)
    231262
    232     def clean(self, value):
     263    def to_python(self, value):
    233264        """
    234265        Validates that the input is a decimal number. Returns a Decimal
    235266        instance. Returns None for empty values. Ensures that there are no more
    236267        than max_digits in the number, and no more than decimal_places digits
    237268        after the decimal point.
    238269        """
    239         super(DecimalField, self).clean(value)
    240         if not self.required and value in EMPTY_VALUES:
     270        if value in EMPTY_VALUES:
    241271            return None
    242272        value = smart_str(value).strip()
    243273        try:
    244             value = Decimal(value)
     274            return Decimal(value)
    245275        except DecimalException:
    246276            raise ValidationError(self.error_messages['invalid'])
     277
     278    def validate(self, value):
     279        super(DecimalField, self).validate(value)
     280        if value is None: return
    247281        pieces = str(value).lstrip("-").split('.')
    248282        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
    249283        digits = len(pieces[0])
    class DecimalField(Field):  
    257291            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
    258292        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
    259293            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
    260         return value
    261294
    262295DEFAULT_DATE_INPUT_FORMATS = (
    263296    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
    class DateField(Field):  
    276309        super(DateField, self).__init__(*args, **kwargs)
    277310        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
    278311
    279     def clean(self, value):
     312    def to_python(self, value):
    280313        """
    281314        Validates that the input can be converted to a date. Returns a Python
    282315        datetime.date object.
    283316        """
    284         super(DateField, self).clean(value)
    285317        if value in EMPTY_VALUES:
    286318            return None
    287319        if isinstance(value, datetime.datetime):
    class TimeField(Field):  
    309341        super(TimeField, self).__init__(*args, **kwargs)
    310342        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
    311343
    312     def clean(self, value):
     344    def to_python(self, value):
    313345        """
    314346        Validates that the input can be converted to a time. Returns a Python
    315347        datetime.time object.
    316348        """
    317         super(TimeField, self).clean(value)
    318349        if value in EMPTY_VALUES:
    319350            return None
    320351        if isinstance(value, datetime.time):
    class DateTimeField(Field):  
    348379        super(DateTimeField, self).__init__(*args, **kwargs)
    349380        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
    350381
    351     def clean(self, value):
     382    def to_python(self, value):
    352383        """
    353384        Validates that the input can be converted to a datetime. Returns a
    354385        Python datetime.datetime object.
    355386        """
    356         super(DateTimeField, self).clean(value)
    357387        if value in EMPTY_VALUES:
    358388            return None
    359389        if isinstance(value, datetime.datetime):
    class RegexField(CharField):  
    390420            regex = re.compile(regex)
    391421        self.regex = regex
    392422
    393     def clean(self, value):
     423    def validate(self, value):
    394424        """
    395425        Validates that the input matches the regular expression. Returns a
    396426        Unicode object.
    397427        """
    398         value = super(RegexField, self).clean(value)
     428        super(RegexField, self).validate(value)
    399429        if value == u'':
    400             return value
     430            return
    401431        if not self.regex.search(value):
    402432            raise ValidationError(self.error_messages['invalid'])
    403         return value
    404433
    405434email_re = re.compile(
    406435    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
    407436    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
    408437    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
    409438
    410 class EmailField(RegexField):
     439class EmailField(CharField):
    411440    default_error_messages = {
    412441        'invalid': _(u'Enter a valid e-mail address.'),
    413442    }
    414 
    415     def __init__(self, max_length=None, min_length=None, *args, **kwargs):
    416         RegexField.__init__(self, email_re, max_length, min_length, *args,
    417                             **kwargs)
     443    validators = [validators.validate_email]
    418444
    419445try:
    420446    from django.conf import settings
    class FileField(Field):  
    428454    widget = FileInput
    429455    default_error_messages = {
    430456        'invalid': _(u"No file was submitted. Check the encoding type on the form."),
    431         'missing': _(u"No file was submitted."),
    432457        'empty': _(u"The submitted file is empty."),
    433458    }
    434459
    435     def __init__(self, *args, **kwargs):
    436         super(FileField, self).__init__(*args, **kwargs)
    437 
    438     def clean(self, data, initial=None):
    439         super(FileField, self).clean(initial or data)
     460    def to_python(self, data, initial=None):
    440461        if not self.required and data in EMPTY_VALUES:
    441462            return None
    442463        elif not data and initial:
    class FileField(Field):  
    450471                category = DeprecationWarning,
    451472                stacklevel = 2
    452473            )
     474            if not data:
     475                raise ValidationError(self.error_messages['invalid'])
    453476            data = UploadedFile(data['filename'], data['content'])
    454477
    455478        try:
    class FileField(Field):  
    462485            raise ValidationError(self.error_messages['invalid'])
    463486        if not file_size:
    464487            raise ValidationError(self.error_messages['empty'])
    465 
    466488        return data
    467489
     490    def clean(self, value, initial=None):
     491        "overriden clean to provide extra argument initial"
     492        value = self.to_python(value, initial)
     493        self.validate(value)
     494        return value
     495
    468496class ImageField(FileField):
    469497    default_error_messages = {
    470498        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
    471499    }
    472500
    473     def clean(self, data, initial=None):
    474         """
    475         Checks that the file-upload field data contains a valid image (GIF, JPG,
    476         PNG, possibly others -- whatever the Python Imaging Library supports).
    477         """
    478         f = super(ImageField, self).clean(data, initial)
    479         if f is None:
    480             return None
    481         elif not data and initial:
    482             return initial
     501    def validate(self, data):
     502        super(ImageField, self).validate(data)
     503        if data is None:
     504            return
    483505        from PIL import Image
    484 
    485506        # We need to get a file object for PIL. We might have a path or we might
    486507        # have to read the data into memory.
    487508        if hasattr(data, 'temporary_file_path'):
    class ImageField(FileField):  
    489510        else:
    490511            if hasattr(data, 'read'):
    491512                file = StringIO(data.read())
     513            elif isinstance(data, UploadedFile):
     514                file = StringIO(data.data.read())
     515            elif hasattr(data, 'seek') and callable(data.seek):
     516                data.seek(0)
     517                file = data
    492518            else:
    493                 file = StringIO(data['content'])
     519                file = data
    494520
    495521        try:
    496522            # load() is the only method that can spot a truncated JPEG,
    class ImageField(FileField):  
    514540            raise
    515541        except Exception: # Python Imaging Library doesn't recognize it as an image
    516542            raise ValidationError(self.error_messages['invalid_image'])
    517         if hasattr(f, 'seek') and callable(f.seek):
    518             f.seek(0)
    519         return f
    520543
    521544url_re = re.compile(
    522545    r'^https?://' # http:// or https://
    class URLField(RegexField):  
    539562        self.verify_exists = verify_exists
    540563        self.user_agent = validator_user_agent
    541564
    542     def clean(self, value):
     565    def to_python(self, value):
     566        value = super(URLField, self).to_python(value)
    543567        # If no URL scheme given, assume http://
    544568        if value and '://' not in value:
    545569            value = u'http://%s' % value
    546570        # If no URL path given, assume /
    547571        if value and not urlparse.urlsplit(value)[2]:
    548572            value += '/'
    549         value = super(URLField, self).clean(value)
     573        return value
     574
     575    def validate(self, value):
     576        super(URLField, self).validate(value)
    550577        if value == u'':
    551             return value
     578            return
    552579        if self.verify_exists:
    553             import urllib2
    554             headers = {
    555                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
    556                 "Accept-Language": "en-us,en;q=0.5",
    557                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
    558                 "Connection": "close",
    559                 "User-Agent": self.user_agent,
    560             }
    561             try:
    562                 req = urllib2.Request(value, None, headers)
    563                 u = urllib2.urlopen(req)
    564             except ValueError:
    565                 raise ValidationError(self.error_messages['invalid'])
    566             except: # urllib2.URLError, httplib.InvalidURL, etc.
    567                 raise ValidationError(self.error_messages['invalid_link'])
    568         return value
     580            # we cannot put this in self.validators because its conditional
     581            validators.validate_existing_url(value, self.error_messages)
    569582
    570583class BooleanField(Field):
    571584    widget = CheckboxInput
    572585
    573     def clean(self, value):
     586    def to_python(self, value):
    574587        """Returns a Python boolean object."""
    575588        # Explicitly check for the string 'False', which is what a hidden field
    576589        # will submit for False. Because bool("True") == True, we don't need to
    class BooleanField(Field):  
    579592            value = False
    580593        else:
    581594            value = bool(value)
    582         super(BooleanField, self).clean(value)
    583         if not value and self.required:
    584             raise ValidationError(self.error_messages['required'])
    585595        return value
    586596
     597    def validate(self, value):
     598        if self.required and not value:
     599            raise ValidationError(self.error_messages['required'])
     600
     601
    587602class NullBooleanField(BooleanField):
    588603    """
    589604    A field whose valid values are None, True and False. Invalid values are
    590605    cleaned to None.
     606
     607    Note that validation doesn't apply here.
    591608    """
    592609    widget = NullBooleanSelect
    593610
    594     def clean(self, value):
     611    def to_python(self, value):
    595612        return {True: True, False: False}.get(value, None)
    596613
     614    def validate(self, value):
     615        pass
     616
     617
    597618class ChoiceField(Field):
    598619    widget = Select
    599620    default_error_messages = {
    class ChoiceField(Field):  
    617638
    618639    choices = property(_get_choices, _set_choices)
    619640
    620     def clean(self, value):
    621         """
    622         Validates that the input is in self.choices.
    623         """
    624         value = super(ChoiceField, self).clean(value)
    625         if value in EMPTY_VALUES:
    626             value = u''
    627         value = smart_unicode(value)
    628         if value == u'':
    629             return value
     641    def validate(self, value):
     642        super(ChoiceField, self).validate(value)
     643        if value is None and not self.required:
     644            return
    630645        if not self.valid_value(value):
    631646            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
    632         return value
    633647
    634648    def valid_value(self, value):
    635649        "Check to see if the provided value is a valid choice"
    class MultipleChoiceField(ChoiceField):  
    652666        'invalid_list': _(u'Enter a list of values.'),
    653667    }
    654668
    655     def clean(self, value):
     669    def to_python(self, value):
    656670        """
    657671        Validates that the input is a list or tuple.
    658672        """
    659         if self.required and not value:
    660             raise ValidationError(self.error_messages['required'])
    661         elif not self.required and not value:
     673        if not value:
    662674            return []
    663675        if not isinstance(value, (list, tuple)):
    664676            raise ValidationError(self.error_messages['invalid_list'])
    665         new_value = [smart_unicode(val) for val in value]
     677        return [smart_unicode(val) for val in value]
     678
     679    def validate(self, value):
    666680        # Validate that each value in the value list is in self.choices.
    667         for val in new_value:
     681        if self.required and value == []:
     682            raise ValidationError(self.error_messages['required'])
     683        for val in value:
    668684            if not self.valid_value(val):
    669685                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
    670         return new_value
    671686
    672687class ComboField(Field):
    673688    """
    class ComboField(Field):  
    682697            f.required = False
    683698        self.fields = fields
    684699
    685     def clean(self, value):
     700    def to_python(self, value):
     701        for field in self.fields:
     702            value = field.to_python(value)
     703        return value
     704
     705    def validate(self, value):
    686706        """
    687707        Validates the given value against all of self.fields, which is a
    688708        list of Field instances.
    689709        """
    690         super(ComboField, self).clean(value)
     710        super(ComboField, self).validate(value)
    691711        for field in self.fields:
    692             value = field.clean(value)
    693         return value
     712            field.validate(value)
    694713
    695714class MultiValueField(Field):
    696715    """
    class MultiValueField(Field):  
    722741            f.required = False
    723742        self.fields = fields
    724743
    725     def clean(self, value):
     744    def to_python(self, value):
    726745        """
    727746        Validates every value in the given list. A value is validated against
    728747        the corresponding Field in self.fields.
    class MultiValueField(Field):  
    736755        if not value or isinstance(value, (list, tuple)):
    737756            if not value or not [v for v in value if v not in EMPTY_VALUES]:
    738757                if self.required:
    739                     raise ValidationError(self.error_messages['required'])
     758                    return None
    740759                else:
    741760                    return self.compress([])
    742761        else:
    743762            raise ValidationError(self.error_messages['invalid'])
     763
    744764        for i, field in enumerate(self.fields):
    745765            try:
    746766                field_value = value[i]
    class SplitDateTimeField(MultiValueField):  
    824844            return datetime.datetime.combine(*data_list)
    825845        return None
    826846
    827 ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
    828 
    829 class IPAddressField(RegexField):
     847class IPAddressField(CharField):
    830848    default_error_messages = {
    831849        'invalid': _(u'Enter a valid IPv4 address.'),
    832850    }
    833 
    834     def __init__(self, *args, **kwargs):
    835         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
     851    validators = [validators.validate_ip_address4]
  • django/forms/forms.py

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

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

    diff --git a/django/forms/models.py b/django/forms/models.py
    index 1f807f2..da9c4f0 100644
    a b from warnings import warn  
    88from django.utils.translation import ugettext_lazy as _
    99from django.utils.encoding import smart_unicode
    1010from django.utils.datastructures import SortedDict
     11from django.core.exceptions import ImproperlyConfigured, ValidationError
     12from django.forms.util import ErrorList
    1113
    12 from util import ValidationError, ErrorList
    1314from forms import BaseForm, get_declared_fields
    1415from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
    1516from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
    class BaseModelForm(BaseForm):  
    262263        super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
    263264                                            error_class, label_suffix, empty_permitted)
    264265
     266    def validate(self):
     267        super(BaseModelForm, self).validate()
     268        if self._errors:
     269            return
     270        self.instance.clean(self.cleaned_data)
     271
    265272    def save(self, commit=True):
    266273        """
    267274        Saves this ``form``'s cleaned_data into model instance
    class BaseInlineFormset(BaseModelFormSet):  
    410417        # is there a better way to get the object descriptor?
    411418        self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
    412419        super(BaseInlineFormset, self).__init__(data, files, prefix=prefix or self.rel_name)
     420
     421    def _construct_form(self, i, **kwargs):
     422        form = super(BaseInlineFormset, self)._construct_form(i, **kwargs)
     423        setattr(form.instance, self.fk.get_attname(), self.instance.pk)
     424        return form
    413425   
    414426    def _construct_forms(self):
    415427        if self.save_as_new:
    class ModelChoiceField(ChoiceField):  
    572584
    573585    choices = property(_get_choices, ChoiceField._set_choices)
    574586
    575     def clean(self, value):
    576         Field.clean(self, value)
    577         if value in EMPTY_VALUES:
     587    def to_python(self, value):
     588        if self.required and value in EMPTY_VALUES:
     589            raise ValidationError(self.error_messages['required'])
     590        elif value in EMPTY_VALUES:
    578591            return None
    579592        try:
    580593            value = self.queryset.get(pk=value)
    class ModelChoiceField(ChoiceField):  
    582595            raise ValidationError(self.error_messages['invalid_choice'])
    583596        return value
    584597
     598    def validate(self, value):
     599        pass
     600
    585601class ModelMultipleChoiceField(ModelChoiceField):
    586602    """A MultipleChoiceField whose choices are a model QuerySet."""
    587603    hidden_widget = MultipleHiddenInput
    class ModelMultipleChoiceField(ModelChoiceField):  
    598614            cache_choices, required, widget, label, initial, help_text,
    599615            *args, **kwargs)
    600616
    601     def clean(self, value):
     617    def to_python(self, value):
    602618        if self.required and not value:
    603619            raise ValidationError(self.error_messages['required'])
    604620        elif not self.required and not value:
    class ModelMultipleChoiceField(ModelChoiceField):  
    614630            else:
    615631                final_values.append(obj)
    616632        return final_values
     633
  • django/forms/util.py

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

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

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

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

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

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

    diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
    index 7ed9d66..4d08650 100644
    a b  
    33
    44This is an experimental feature!
    55
    6 Each model instance has a validate() method that returns a dictionary of
     6Each model instance has a clean() method that returns a dictionary of
    77validation errors in the instance's fields. This method has a side effect
    88of converting each field to its appropriate Python data type.
    99"""
    class Person(models.Model):  
    1515    name = models.CharField(max_length=20)
    1616    birthdate = models.DateField()
    1717    favorite_moment = models.DateTimeField()
    18     email = models.EmailField()
     18    email = models.EmailField(unique=True)
    1919    best_time = models.TimeField()
    2020
     21    class Meta:
     22        unique_together = (('name', 'is_child'),)
    2123    def __unicode__(self):
    2224        return self.name
    2325
    __test__ = {'API_TESTS':"""  
    3234...     'email': 'john@example.com',
    3335...     'best_time': datetime.time(16, 20),
    3436... }
    35 >>> p = Person(**valid_params)
    36 >>> p.validate()
    37 {}
    38 
    39 >>> p = Person(**dict(valid_params, id='23'))
    40 >>> p.validate()
    41 {}
     37>>> p = Person(**dict(valid_params, email='john@e.com', name='Jack'))
     38>>> p.clean()
     39>>> p.save()
     40
     41>>> p = Person(**dict(valid_params, email='john@e.com'))
     42>>> p.clean()
     43Traceback (most recent call last):
     44...
     45ValidationError: {'email': [u'This field must be unique']}
     46
     47>>> p = Person(**dict(valid_params, id='23', name='Jack'))
     48>>> p.clean()
     49Traceback (most recent call last):
     50...
     51ValidationError: {'__all__': u'Fields name, is_child must be unique.'}
    4252>>> p.id
    435323
    4454
    45 >>> p = Person(**dict(valid_params, id='foo'))
    46 >>> p.validate()['id']
    47 [u'This value must be an integer.']
     55# when type coercion fails, no other validation is done
     56>>> p = Person(**dict(valid_params, email='john@e.com', id='foo'))
     57>>> p.clean()
     58Traceback (most recent call last):
     59...
     60ValidationError: {'id': [u'This value must be an integer.']}
    4861
    4962>>> p = Person(**dict(valid_params, id=None))
    50 >>> p.validate()
    51 {}
     63>>> p.clean()
    5264>>> repr(p.id)
    5365'None'
    5466
    5567>>> p = Person(**dict(valid_params, is_child='t'))
    56 >>> p.validate()
    57 {}
     68>>> p.clean()
    5869>>> p.is_child
    5970True
    6071
    6172>>> p = Person(**dict(valid_params, is_child='f'))
    62 >>> p.validate()
    63 {}
     73>>> p.clean()
    6474>>> p.is_child
    6575False
    6676
    6777>>> p = Person(**dict(valid_params, is_child=True))
    68 >>> p.validate()
    69 {}
     78>>> p.clean()
    7079>>> p.is_child
    7180True
    7281
    7382>>> p = Person(**dict(valid_params, is_child=False))
    74 >>> p.validate()
    75 {}
     83>>> p.clean()
    7684>>> p.is_child
    7785False
    7886
    7987>>> p = Person(**dict(valid_params, is_child='foo'))
    80 >>> p.validate()['is_child']
    81 [u'This value must be either True or False.']
     88>>> p.clean()
     89Traceback (most recent call last):
     90...
     91ValidationError: {'is_child': [u'This value must be either True or False.']}
    8292
    8393>>> p = Person(**dict(valid_params, name=u'Jose'))
    84 >>> p.validate()
    85 {}
     94>>> p.clean()
    8695>>> p.name
    8796u'Jose'
    8897
    8998>>> p = Person(**dict(valid_params, name=227))
    90 >>> p.validate()
    91 {}
     99>>> p.clean()
    92100>>> p.name
    93101u'227'
    94102
    95103>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
    96 >>> p.validate()
    97 {}
     104>>> p.clean()
    98105>>> p.birthdate
    99106datetime.date(2000, 5, 3)
    100107
    101108>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
    102 >>> p.validate()
    103 {}
     109>>> p.clean()
    104110>>> p.birthdate
    105111datetime.date(2000, 5, 3)
    106112
    107113>>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
    108 >>> p.validate()
    109 {}
     114>>> p.clean()
    110115>>> p.birthdate
    111116datetime.date(2000, 5, 3)
    112117
    113118>>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
    114 >>> p.validate()
    115 {}
     119>>> p.clean()
    116120>>> p.birthdate
    117121datetime.date(2000, 5, 3)
    118122
    119123>>> p = Person(**dict(valid_params, birthdate='foo'))
    120 >>> p.validate()['birthdate']
    121 [u'Enter a valid date in YYYY-MM-DD format.']
     124>>> p.clean()
     125Traceback (most recent call last):
     126...
     127ValidationError: {'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
    122128
    123129>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
    124 >>> p.validate()
    125 {}
     130>>> p.clean()
    126131>>> p.favorite_moment
    127132datetime.datetime(2002, 4, 3, 13, 23)
    128133
    129134>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
    130 >>> p.validate()
    131 {}
     135>>> p.clean()
    132136>>> p.favorite_moment
    133137datetime.datetime(2002, 4, 3, 0, 0)
    134138
    135139>>> p = Person(**dict(valid_params, best_time='16:20:00'))
    136 >>> p.validate()
    137 {}
     140>>> p.clean()
    138141>>> p.best_time
    139142datetime.time(16, 20)
    140143
    141144>>> p = Person(**dict(valid_params, best_time='16:20'))
    142 >>> p.validate()
    143 {}
     145>>> p.clean()
    144146>>> p.best_time
    145147datetime.time(16, 20)
    146148
    147149>>> p = Person(**dict(valid_params, best_time='bar'))
    148 >>> p.validate()['best_time']
    149 [u'Enter a valid time in HH:MM[:ss[.uuuuuu]] format.']
     150>>> p.clean()['best_time']
     151Traceback (most recent call last):
     152...
     153ValidationError: {'best_time': [u'Enter a valid time in HH:MM[:ss[.uuuuuu]] format.']}
    150154
    151155>>> p = Person(**dict(valid_params, email='john@example.com'))
    152 >>> p.validate()
    153 {}
     156>>> p.clean()
    154157>>> p.email
    155158'john@example.com'
    156159
    157160>>> p = Person(**dict(valid_params, email=u'john@example.com'))
    158 >>> p.validate()
    159 {}
     161>>> p.clean()
    160162>>> p.email
    161163u'john@example.com'
    162164
    163165>>> p = Person(**dict(valid_params, email=22))
    164 >>> p.validate()['email']
    165 [u'Enter a valid e-mail address.']
     166>>> p.clean()
     167Traceback (most recent call last):
     168...
     169ValidationError: {'email': [u'Enter a valid e-mail address.']}
    166170
    167171# Make sure that Date and DateTime return validation errors and don't raise Python errors.
    168 >>> p = Person(name='John Doe', is_child=True, email='abc@def.com')
    169 >>> errors = p.validate()
    170 >>> errors['favorite_moment']
    171 [u'This field is required.']
    172 >>> errors['birthdate']
     172>>> from django.core.exceptions import ValidationError
     173>>> try:
     174...     Person(name='John Doe', is_child=True, email='abc@def.com').clean()
     175... except ValidationError, e:
     176...     e.message_dict['favorite_moment']
     177...     e.message_dict['birthdate']
    173178[u'This field is required.']
    174 >>> errors['best_time']
    175179[u'This field is required.']
    176180
    177181"""}
  • new file tests/regressiontests/core/models.py

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

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

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

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

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

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

    diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
    index 68c082c..5272b2c 100644
    a b u' id="header"'  
    1818u' class="news" title="Read this"'
    1919>>> flatatt({})
    2020u''
    21 
    22 ###################
    23 # ValidationError #
    24 ###################
    25 
    26 # Can take a string.
    27 >>> print ValidationError("There was an error.").messages
    28 <ul class="errorlist"><li>There was an error.</li></ul>
    29 
    30 # Can take a unicode string.
    31 >>> print ValidationError(u"Not \u03C0.").messages
    32 <ul class="errorlist"><li>Not π.</li></ul>
    33 
    34 # Can take a lazy string.
    35 >>> print ValidationError(ugettext_lazy("Error.")).messages
    36 <ul class="errorlist"><li>Error.</li></ul>
    37 
    38 # Can take a list.
    39 >>> print ValidationError(["Error one.", "Error two."]).messages
    40 <ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
    41 
    42 # Can take a mixture in a list.
    43 >>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
    44 <ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
    45 
    46 >>> class VeryBadError:
    47 ...     def __unicode__(self): return u"A very bad error."
    48 
    49 # Can take a non-string.
    50 >>> print ValidationError(VeryBadError()).messages
    51 <ul class="errorlist"><li>A very bad error.</li></ul>
    5221"""
Back to Top