Ticket #6845: 6845-against-7877.2.patch

File 6845-against-7877.2.patch, 128.8 KB (added by Honza Král, 16 years ago)
  • AUTHORS

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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