Ticket #6845: 6845-against-8090.patch

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

new patch after 1.0 alpha

  • AUTHORS

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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