Ticket #6845: 6845-against-django-7338.diff

File 6845-against-django-7338.diff, 133.7 KB (added by Honza_Kral, 7 years ago)
  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 2329c8b..fed146e 100644
    a b answer newbie questions, and generally made Django that much better: 
    379379    ymasuda@ethercube.com
    380380    Jarek Zgoda <jarek.zgoda@gmail.com>
    381381    Cheng Zhang
     382    Honza Kral <Honza.Kral@gmail.com>
    382383
    383384A big THANK YOU goes to:
    384385
  • 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/create_superuser.py

    diff --git a/django/contrib/auth/create_superuser.py b/django/contrib/auth/create_superuser.py
    index 7b6cefd..0d08ff1 100644
    a b def createsuperuser(username=None, email=None, password=None): 
    6161            if not email:
    6262                email = raw_input('E-mail address: ')
    6363            try:
    64                 validators.isValidEmail(email, None)
     64                validators.validate_email(email)
    6565            except validators.ValidationError:
    6666                sys.stderr.write("Error: That e-mail address is invalid.\n")
    6767                email = None
  • django/contrib/auth/forms.py

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

    diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
    index 4b15902..aa2eb5e 100644
    a b  
    11from django.contrib import auth
    2 from django.core import validators
     2from django.oldforms import validators
    33from django.core.exceptions import ImproperlyConfigured
    44from django.db import models
    55from django.db.models.manager import EmptyManager
    class User(models.Model): 
    128128
    129129    Username and password are required. Other fields are optional.
    130130    """
    131     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)."))
     131    username = models.CharField(_('username'), max_length=30, unique=True, validators=[validators.isAlphaNumeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
    132132    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    133133    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    134134    email = models.EmailField(_('e-mail address'), blank=True)
  • django/contrib/comments/views/comments.py

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

    diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
    index 36327c8..ada2c5f 100644
    a b  
    1 from django.core import validators
     1from django.oldforms import validators
    22from django.db import models
    33from django.contrib.sites.models import Site
    44from django.utils.translation import ugettext_lazy as _
    55
    66class FlatPage(models.Model):
    7     url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
     7    url = models.CharField(_('URL'), max_length=100, validators=[validators.isAlphaNumericURL], db_index=True,
    88        help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
    99    title = models.CharField(_('title'), max_length=200)
    1010    content = models.TextField(_('content'))
  • django/contrib/localflavor/jp/forms.py

    diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
    index d726f82..4d334aa 100644
    a b  
    22JP-specific Form helpers
    33"""
    44
    5 from django.core import validators
    65from django.newforms import ValidationError
    76from django.utils.translation import ugettext
    87from django.newforms.fields import RegexField, Select
  • django/core/exceptions.py

    diff --git a/django/core/exceptions.py b/django/core/exceptions.py
    index d9fc326..06c92ac 100644
    a b class MiddlewareNotUsed(Exception): 
    2727class ImproperlyConfigured(Exception):
    2828    "Django is somehow improperly configured"
    2929    pass
     30
  • new file django/core/validation.py

    diff --git a/django/core/validation.py b/django/core/validation.py
    new file mode 100644
    index 0000000..8fe3822
    - +  
     1from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
     2from django.utils.safestring import mark_safe
     3
     4NON_FIELD_ERRORS = '__all__'
     5
     6class ErrorList(list, StrAndUnicode):
     7    """
     8    A collection of errors that knows how to display itself in various formats.
     9    """
     10    def __unicode__(self):
     11        return self.as_ul()
     12
     13    def as_ul(self):
     14        if not self: return u''
     15        return mark_safe(u'<ul class="errorlist">%s</ul>'
     16                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
     17
     18    def as_text(self):
     19        if not self: return u''
     20        return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
     21
     22    def __repr__(self):
     23        return repr([force_unicode(e) for e in self])
     24
     25class ValidationError(Exception):
     26    def __init__(self, message):
     27        """
     28        ValidationError can be passed any object that can be printed (usually
     29        a string) or a list of objects.
     30        """
     31        if hasattr(message, '__iter__'):
     32            self.messages = ErrorList([smart_unicode(msg) for msg in message])
     33        else:
     34            message = smart_unicode(message)
     35            self.messages = ErrorList([message])
     36
     37        if isinstance(message, dict):
     38            self.message_dict = message
     39
     40    def __str__(self):
     41        # This is needed because, without a __str__(), printing an exception
     42        # instance would result in this:
     43        # AttributeError: ValidationError instance has no attribute 'args'
     44        # See http://www.python.org/doc/current/tut/node10.html#handling
     45        if hasattr(self, 'message_dict'):
     46            return repr(self.message_dict)
     47        return repr(self.messages)
     48
     49class TypeCoersionError(ValidationError):
     50    pass
  • django/core/validators.py

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

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

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 4f034bc..af9caf1 100644
    a b  
    11import django.db.models.manipulators
    22import django.db.models.manager
    3 from django.core import validators
     3from django.core.validation import ValidationError, NON_FIELD_ERRORS
    44from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
    55from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
    66from django.db.models.fields.related import OneToOneRel, ManyToOneRel
    from django.dispatch import dispatcher 
    1313from django.utils.datastructures import SortedDict
    1414from django.utils.functional import curry
    1515from django.utils.encoding import smart_str, force_unicode, smart_unicode
     16from django.utils.translation import ugettext as _
    1617from django.conf import settings
    1718from itertools import izip
    1819import types
    class Model(object): 
    277278
    278279    save.alters_data = True
    279280
    280     def validate(self):
     281    def validate(self, form_data=None):
    281282        """
    282283        First coerces all fields on this instance to their proper Python types.
    283284        Then runs validation on every field. Returns a dictionary of
    284285        field_name -> error_list.
    285286        """
     287        if form_data is not None:
     288            def get_value(f):
     289                if f.name in form_data:
     290                    return form_data[f.name]
     291                return getattr(self, f.attname, f.get_default())
     292        else:
     293            get_value = lambda f: getattr(self, f.attname, f.get_default())
    286294        error_dict = {}
    287         invalid_python = {}
    288295        for f in self._meta.fields:
    289296            try:
    290                 setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default())))
    291             except validators.ValidationError, e:
     297                value = f.to_python(get_value(f))
     298                f.validate(value, instance=self)
     299                if hasattr(self, 'validate_%s' % f.name):
     300                    getattr(self, 'validate_%s' % f.name)(value)
     301            except ValidationError, e:
    292302                error_dict[f.name] = e.messages
    293                 invalid_python[f.name] = 1
    294         for f in self._meta.fields:
    295             if f.name in invalid_python:
    296                 continue
    297             errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__)
    298             if errors:
    299                 error_dict[f.name] = errors
    300         return error_dict
     303
     304        for un_together in self._meta.unique_together:
     305            lookup = {}
     306            for name in un_together:
     307                if name in error_dict:
     308                    break
     309                f = self._meta.get_field(name)
     310                lookup['%s__exact' % name] = f.to_python(get_value(f))
     311            try:
     312                qset = self.__class__._default_manager.all()
     313                if self.pk:
     314                    qset = qset.exclude(pk=self.pk)
     315                obj = qset.get(**lookup)
     316                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together)
     317            except self.DoesNotExist:
     318                pass
     319
     320        if error_dict:
     321            raise ValidationError(error_dict)
    301322
    302323    def _collect_sub_objects(self, seen_objs):
    303324        """
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 13a84ec..1233f53 100644
    a b from django.db import get_creation_module 
    1010from django.db.models import signals
    1111from django.dispatch import dispatcher
    1212from django.conf import settings
     13from django.oldforms import validators as oldvalidators
    1314from django.core import validators
    1415from django import oldforms
    1516from django import newforms as forms
    class Field(object): 
    7879    # Tracks each time a Field instance is created. Used to retain order.
    7980    creation_counter = 0
    8081
     82    validators = []
     83
    8184    def __init__(self, verbose_name=None, name=None, primary_key=False,
    8285        max_length=None, unique=False, blank=False, null=False, db_index=False,
    8386        core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
    8487        prepopulate_from=None, unique_for_date=None, unique_for_month=None,
    8588        unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
    86         help_text='', db_column=None, db_tablespace=None):
     89        help_text='', db_column=None, db_tablespace=None, validators=[]):
    8790        self.name = name
    8891        self.verbose_name = verbose_name
    8992        self.primary_key = primary_key
    class Field(object): 
    9699        self.core, self.rel, self.default = core, rel, default
    97100        self.editable = editable
    98101        self.serialize = serialize
     102        self.validators = validators + self.validators
    99103        self.validator_list = validator_list or []
    100104        self.prepopulate_from = prepopulate_from
    101105        self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
    class Field(object): 
    151155            return None
    152156        return data_types[internal_type] % self.__dict__
    153157
    154     def validate_full(self, field_data, all_data):
    155         """
    156         Returns a list of errors for this field. This is the main interface,
    157         as it encapsulates some basic validation logic used by all fields.
    158         Subclasses should implement validate(), not validate_full().
    159         """
    160         if not self.blank and not field_data:
    161             return [_('This field is required.')]
    162         try:
    163             self.validate(field_data, all_data)
    164         except validators.ValidationError, e:
    165             return e.messages
    166         return []
    167 
    168     def validate(self, field_data, all_data):
     158    def validate(self, value, instance=None):
    169159        """
    170         Raises validators.ValidationError if field_data has any errors.
     160        Raises validators.ValidationError if value has any errors.
    171161        Subclasses should override this to specify field-specific validation
    172         logic. This method should assume field_data has already been converted
     162        logic. This method should assume value has already been converted
    173163        into the appropriate data type by Field.to_python().
    174164        """
    175         pass
     165        if not self.blank and self.editable and not value:
     166            raise validators.ValidationError(_('This field is required.'))
     167        elist = []
     168        for validator in self.validators:
     169            validator(value)   
     170        if self.unique and instance:
     171            try:
     172                qset = instance.__class__._default_manager.all()
     173                if instance.pk:
     174                    qset = qset.exclude(pk=instance.pk)
     175                obj = qset.get(**{'%s__exact' % self.name : value})
     176                raise validators.ValidationError(_('This field must be unique'))
     177            except instance.DoesNotExist:
     178                pass
    176179
    177180    def set_attributes_from_name(self, name):
    178181        self.name = name
    class Field(object): 
    324327                    core_field_names.extend(f.get_manipulator_field_names(name_prefix))
    325328            # Now, if there are any, add the validator to this FormField.
    326329            if core_field_names:
    327                 params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
     330                params['validator_list'].append(oldvalidators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
    328331
    329332        # Finally, add the field_names.
    330333        field_names = self.get_manipulator_field_names(name_prefix)
    class DateField(Field): 
    520523            return value.date()
    521524        if isinstance(value, datetime.date):
    522525            return value
    523         validators.isValidANSIDate(value, None)
    524526        try:
    525527            return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
    526528        except ValueError:
    class DecimalField(Field): 
    713715        return super(DecimalField, self).formfield(**defaults)
    714716
    715717class EmailField(CharField):
     718    validators = [validators.validate_email]
    716719    def __init__(self, *args, **kwargs):
    717720        kwargs['max_length'] = kwargs.get('max_length', 75)
    718721        CharField.__init__(self, *args, **kwargs)
    class EmailField(CharField): 
    720723    def get_manipulator_field_objs(self):
    721724        return [oldforms.EmailField]
    722725
    723     def validate(self, field_data, all_data):
    724         validators.isValidEmail(field_data, all_data)
    725 
    726726    def formfield(self, **kwargs):
    727727        defaults = {'form_class': forms.EmailField}
    728728        defaults.update(kwargs)
    class FileField(Field): 
    756756                        self.always_test = True
    757757                    def __call__(self, field_data, all_data):
    758758                        if not all_data.get(self.other_file_field_name, False):
    759                             c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
     759                            c = oldvalidators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
    760760                            c(field_data, all_data)
    761761                # First, get the core fields, if any.
    762762                core_field_names = []
    class FileField(Field): 
    767767                if core_field_names:
    768768                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
    769769            else:
    770                 v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
     770                v = oldvalidators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
    771771                v.always_test = True
    772772                field_list[0].validator_list.append(v)
    773773                field_list[0].is_required = field_list[1].is_required = False
    class IntegerField(Field): 
    925925
    926926class IPAddressField(Field):
    927927    empty_strings_allowed = False
     928    validators = [validators.validate_ip_address4]
     929
    928930    def __init__(self, *args, **kwargs):
    929931        kwargs['max_length'] = 15
    930932        Field.__init__(self, *args, **kwargs)
    class IPAddressField(Field): 
    935937    def get_internal_type(self):
    936938        return "IPAddressField"
    937939
    938     def validate(self, field_data, all_data):
    939         validators.isValidIPAddress4(field_data, None)
    940 
    941940    def formfield(self, **kwargs):
    942941        defaults = {'form_class': forms.IPAddressField}
    943942        defaults.update(kwargs)
    class NullBooleanField(Field): 
    968967        return super(NullBooleanField, self).formfield(**defaults)
    969968
    970969class PhoneNumberField(IntegerField):
     970    validators = [validators.validate_phone_number]
     971
    971972    def get_manipulator_field_objs(self):
    972973        return [oldforms.PhoneNumberField]
    973974
    974975    def get_internal_type(self):
    975976        return "PhoneNumberField"
    976977
    977     def validate(self, field_data, all_data):
    978         validators.isValidPhone(field_data, all_data)
    979 
    980978    def formfield(self, **kwargs):
    981979        from django.contrib.localflavor.us.forms import USPhoneNumberField
    982980        defaults = {'form_class': USPhoneNumberField}
    class PositiveSmallIntegerField(IntegerField): 
    10081006        return super(PositiveSmallIntegerField, self).formfield(**defaults)
    10091007
    10101008class SlugField(CharField):
     1009    validators = [validators.validate_slug]
    10111010    def __init__(self, *args, **kwargs):
    10121011        kwargs['max_length'] = kwargs.get('max_length', 50)
    1013         kwargs.setdefault('validator_list', []).append(validators.isSlug)
    10141012        # Set db_index=True unless it's been set manually.
    10151013        if 'db_index' not in kwargs:
    10161014            kwargs['db_index'] = True
    class URLField(CharField): 
    11061104    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
    11071105        kwargs['max_length'] = kwargs.get('max_length', 200)
    11081106        if verify_exists:
    1109             kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
     1107            kwargs.setdefault('validators', []).append(validators.validate_existing_url)
    11101108        self.verify_exists = verify_exists
    11111109        CharField.__init__(self, verbose_name, name, **kwargs)
    11121110
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index c87fdd9..5bcd38c 100644
    a b class ManyToManyField(RelatedField, Field): 
    747747        objects = mod._default_manager.in_bulk(pks)
    748748        if len(objects) != len(pks):
    749749            badkeys = [k for k in pks if k not in objects]
    750             raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
     750            raise validator_list.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
    751751                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
    752752                'self': self.verbose_name,
    753753                'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
  • django/newforms/__init__.py

    diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
    index 0d9c68f..6f881f0 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.validation import ValidationError
    1414from widgets import *
    1515from fields import *
    1616from forms import *
  • django/newforms/fields.py

    diff --git a/django/newforms/fields.py b/django/newforms/fields.py
    index 08e8b84..61e815d 100644
    a b except NameError: 
    1919
    2020from django.utils.translation import ugettext_lazy as _
    2121from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
     22from django.core.validation import ValidationError, ErrorList, TypeCoersionError
    2223
    23 from util import ErrorList, ValidationError
    2424from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
    2525
    2626
    EMPTY_VALUES = (None, '') 
    4141
    4242class Field(object):
    4343    widget = TextInput # Default widget to use when rendering this type of Field.
     44    validators = []
    4445    hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
    4546    default_error_messages = {
    4647        'required': _(u'This field is required.'),
    class Field(object): 
    5152    creation_counter = 0
    5253
    5354    def __init__(self, required=True, widget=None, label=None, initial=None,
    54                  help_text=None, error_messages=None):
     55                 help_text=None, error_messages=None, validators=[]):
    5556        # required -- Boolean that specifies whether the field is required.
    5657        #             True by default.
    5758        # widget -- A Widget class, or instance of a Widget class, that should
    class Field(object): 
    6566        # initial -- A value to use in this Field's initial display. This value
    6667        #            is *not* used as a fallback if data isn't given.
    6768        # help_text -- An optional string to use as "help text" for this Field.
     69        # validators -- Optional list of additional validator functions
    6870        if label is not None:
    6971            label = smart_unicode(label)
     72        self.validators = self.validators + validators
    7073        self.required, self.label, self.initial = required, label, initial
    7174        self.help_text = smart_unicode(help_text or '')
    7275        widget = widget or self.widget
    class Field(object): 
    9497        messages.update(error_messages or {})
    9598        self.error_messages = messages
    9699
     100    def to_python(self, value):
     101        return self.clean(value)
     102
     103    def validate(self, value):
     104        if self.required and value in EMPTY_VALUES:
     105            raise ValidationError(self.error_messages['required'])
     106        elist = ErrorList()
     107        for validator in self.validators:
     108            try:
     109                validator(value)
     110            except ValidationError, e:
     111                elist.extend(e.messages)
     112        if elist:
     113            raise ValidationError(elist)
     114
    97115    def clean(self, value):
    98116        """
    99117        Validates the given value and returns its "cleaned" value as an
    class Field(object): 
    119137        result.widget = copy.deepcopy(self.widget, memo)
    120138        return result
    121139
    122 class CharField(Field):
     140class LegacyField(Field):
     141    def to_python(self, value):
     142        if value in EMPTY_VALUES:
     143            return u''
     144        return smart_unicode(value)
     145
     146    def clean(self, value):
     147        value = self.to_python(value)
     148        self.validate(value)
     149        return value
     150
     151class CharField(LegacyField):
    123152    default_error_messages = {
    124153        'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
    125154        'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
    class CharField(Field): 
    129158        self.max_length, self.min_length = max_length, min_length
    130159        super(CharField, self).__init__(*args, **kwargs)
    131160
    132     def clean(self, value):
     161    def validate(self, value):
    133162        "Validates max_length and min_length. Returns a Unicode object."
    134         super(CharField, self).clean(value)
    135         if value in EMPTY_VALUES:
    136             return u''
    137         value = smart_unicode(value)
     163        super(CharField, self).validate(value)
    138164        value_length = len(value)
     165        if value_length == 0 and not self.required:
     166            return
    139167        if self.max_length is not None and value_length > self.max_length:
    140168            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
    141169        if self.min_length is not None and value_length < self.min_length:
    142170            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
    143         return value
    144171
    145172    def widget_attrs(self, widget):
    146173        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
    147174            # The HTML attribute is maxlength, not max_length.
    148175            return {'maxlength': str(self.max_length)}
    149176
    150 class IntegerField(Field):
     177class IntegerField(LegacyField):
    151178    default_error_messages = {
    152179        'invalid': _(u'Enter a whole number.'),
    153180        'max_value': _(u'Ensure this value is less than or equal to %s.'),
    class IntegerField(Field): 
    158185        self.max_value, self.min_value = max_value, min_value
    159186        super(IntegerField, self).__init__(*args, **kwargs)
    160187
    161     def clean(self, value):
    162         """
    163         Validates that int() can be called on the input. Returns the result
    164         of int(). Returns None for empty values.
    165         """
    166         super(IntegerField, self).clean(value)
     188    def to_python(self, value):
    167189        if value in EMPTY_VALUES:
    168190            return None
    169191        try:
    170             value = int(str(value))
     192            return int(smart_str(value))
    171193        except (ValueError, TypeError):
    172             raise ValidationError(self.error_messages['invalid'])
     194            raise TypeCoersionError(self.error_messages['invalid'])
     195
     196    def validate(self, value):
     197        """
     198        Validates that int() can be called on the input. Returns the result
     199        of int(). Returns None for empty values.
     200        """
     201        super(IntegerField, self).validate(value)
     202        if value is None: return
    173203        if self.max_value is not None and value > self.max_value:
    174204            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    175205        if self.min_value is not None and value < self.min_value:
    176206            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    177         return value
    178207
    179 class FloatField(Field):
     208class FloatField(LegacyField):
    180209    default_error_messages = {
    181210        'invalid': _(u'Enter a number.'),
    182211        'max_value': _(u'Ensure this value is less than or equal to %s.'),
    class FloatField(Field): 
    187216        self.max_value, self.min_value = max_value, min_value
    188217        Field.__init__(self, *args, **kwargs)
    189218
    190     def clean(self, value):
     219    def to_python(self, value):
    191220        """
    192221        Validates that float() can be called on the input. Returns a float.
    193222        Returns None for empty values.
    194223        """
    195         super(FloatField, self).clean(value)
    196         if not self.required and value in EMPTY_VALUES:
     224        if value in EMPTY_VALUES:
    197225            return None
    198226        try:
    199             value = float(value)
     227            return float(value)
    200228        except (ValueError, TypeError):
    201             raise ValidationError(self.error_messages['invalid'])
     229            raise TypeCoersionError(self.error_messages['invalid'])
     230
     231    def validate(self, value):
     232        super(FloatField, self).validate(value)
     233        if value is None: return
    202234        if self.max_value is not None and value > self.max_value:
    203235            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    204236        if self.min_value is not None and value < self.min_value:
    205237            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    206         return value
    207238
    208 class DecimalField(Field):
     239class DecimalField(LegacyField):
    209240    default_error_messages = {
    210241        'invalid': _(u'Enter a number.'),
    211242        'max_value': _(u'Ensure this value is less than or equal to %s.'),
    class DecimalField(Field): 
    220251        self.max_digits, self.decimal_places = max_digits, decimal_places
    221252        Field.__init__(self, *args, **kwargs)
    222253
    223     def clean(self, value):
     254    def to_python(self, value):
    224255        """
    225256        Validates that the input is a decimal number. Returns a Decimal
    226257        instance. Returns None for empty values. Ensures that there are no more
    227258        than max_digits in the number, and no more than decimal_places digits
    228259        after the decimal point.
    229260        """
    230         super(DecimalField, self).clean(value)
    231         if not self.required and value in EMPTY_VALUES:
     261        if value in EMPTY_VALUES:
    232262            return None
    233263        value = smart_str(value).strip()
    234264        try:
    235             value = Decimal(value)
     265            return Decimal(value)
    236266        except DecimalException:
    237             raise ValidationError(self.error_messages['invalid'])
     267            raise TypeCoersionError(self.error_messages['invalid'])
     268
     269    def validate(self, value):
     270        super(DecimalField, self).validate(value)
     271        if value is None: return
    238272        pieces = str(value).lstrip("-").split('.')
    239273        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
    240274        digits = len(pieces[0])
    class DecimalField(Field): 
    248282            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
    249283        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
    250284            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
    251         return value
    252285
    253286DEFAULT_DATE_INPUT_FORMATS = (
    254287    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
    DEFAULT_DATE_INPUT_FORMATS = ( 
    258291    '%d %B %Y', '%d %B, %Y',            # '25 October 2006', '25 October, 2006'
    259292)
    260293
    261 class DateField(Field):
     294class DateField(LegacyField):
    262295    default_error_messages = {
    263296        'invalid': _(u'Enter a valid date.'),
    264297    }
    class DateField(Field): 
    267300        super(DateField, self).__init__(*args, **kwargs)
    268301        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
    269302
    270     def clean(self, value):
     303    def to_python(self, value):
    271304        """
    272305        Validates that the input can be converted to a date. Returns a Python
    273306        datetime.date object.
    274307        """
    275         super(DateField, self).clean(value)
    276308        if value in EMPTY_VALUES:
    277309            return None
    278310        if isinstance(value, datetime.datetime):
    class DateField(Field): 
    284316                return datetime.date(*time.strptime(value, format)[:3])
    285317            except ValueError:
    286318                continue
    287         raise ValidationError(self.error_messages['invalid'])
     319        raise TypeCoersionError(self.error_messages['invalid'])
    288320
    289321DEFAULT_TIME_INPUT_FORMATS = (
    290322    '%H:%M:%S',     # '14:30:59'
    291323    '%H:%M',        # '14:30'
    292324)
    293325
    294 class TimeField(Field):
     326class TimeField(LegacyField):
    295327    default_error_messages = {
    296328        'invalid': _(u'Enter a valid time.')
    297329    }
    class TimeField(Field): 
    300332        super(TimeField, self).__init__(*args, **kwargs)
    301333        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
    302334
    303     def clean(self, value):
     335    def to_python(self, value):
    304336        """
    305337        Validates that the input can be converted to a time. Returns a Python
    306338        datetime.time object.
    307339        """
    308         super(TimeField, self).clean(value)
    309340        if value in EMPTY_VALUES:
    310341            return None
    311342        if isinstance(value, datetime.time):
    class TimeField(Field): 
    315346                return datetime.time(*time.strptime(value, format)[3:6])
    316347            except ValueError:
    317348                continue
    318         raise ValidationError(self.error_messages['invalid'])
     349        raise TypeCoersionError(self.error_messages['invalid'])
    319350
    320351DEFAULT_DATETIME_INPUT_FORMATS = (
    321352    '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
    DEFAULT_DATETIME_INPUT_FORMATS = ( 
    329360    '%m/%d/%y',              # '10/25/06'
    330361)
    331362
    332 class DateTimeField(Field):
     363class DateTimeField(LegacyField):
    333364    widget = DateTimeInput
    334365    default_error_messages = {
    335366        'invalid': _(u'Enter a valid date/time.'),
    class DateTimeField(Field): 
    339370        super(DateTimeField, self).__init__(*args, **kwargs)
    340371        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
    341372
    342     def clean(self, value):
     373    def to_python(self, value):
    343374        """
    344375        Validates that the input can be converted to a datetime. Returns a
    345376        Python datetime.datetime object.
    346377        """
    347         super(DateTimeField, self).clean(value)
    348378        if value in EMPTY_VALUES:
    349379            return None
    350380        if isinstance(value, datetime.datetime):
    class DateTimeField(Field): 
    362392                return datetime.datetime(*time.strptime(value, format)[:6])
    363393            except ValueError:
    364394                continue
    365         raise ValidationError(self.error_messages['invalid'])
     395        raise TypeCoersionError(self.error_messages['invalid'])
    366396
    367397class RegexField(CharField):
    368398    def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
    class RegexField(CharField): 
    381411            regex = re.compile(regex)
    382412        self.regex = regex
    383413
    384     def clean(self, value):
     414    def validate(self, value):
    385415        """
    386416        Validates that the input matches the regular expression. Returns a
    387417        Unicode object.
    388418        """
    389         value = super(RegexField, self).clean(value)
    390         if value == u'':
    391             return value
     419        super(RegexField, self).validate(value)
     420        if value in EMPTY_VALUES:
     421            return u''
    392422        if not self.regex.search(value):
    393423            raise ValidationError(self.error_messages['invalid'])
    394         return value
    395424
    396425email_re = re.compile(
    397426    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
    class UploadedFile(StrAndUnicode): 
    427456        """
    428457        return self.filename
    429458
    430 class FileField(Field):
     459class FileField(LegacyField):
    431460    widget = FileInput
    432461    default_error_messages = {
    433462        'invalid': _(u"No file was submitted. Check the encoding type on the form."),
    434         'missing': _(u"No file was submitted."),
    435463        'empty': _(u"The submitted file is empty."),
    436464    }
    437465
    438     def __init__(self, *args, **kwargs):
    439         super(FileField, self).__init__(*args, **kwargs)
    440 
    441     def clean(self, data, initial=None):
    442         super(FileField, self).clean(initial or data)
     466    def to_python(self, data, initial=None):
    443467        if not self.required and data in EMPTY_VALUES:
    444468            return None
    445469        elif not data and initial:
    446470            return initial
     471        elif not data:
     472            return None
     473
    447474        try:
    448475            f = UploadedFile(data['filename'], data['content'])
    449476        except TypeError:
    450             raise ValidationError(self.error_messages['invalid'])
     477            raise TypeCoersionError(self.error_messages['invalid'])
    451478        except KeyError:
    452             raise ValidationError(self.error_messages['missing'])
     479            raise ValidationError(self.error_messages['required'])
    453480        if not f.content:
    454481            raise ValidationError(self.error_messages['empty'])
    455482        return f
    456483
     484    def clean(self, data, initial=None):
     485        value = self.to_python(data, initial)
     486        self.validate(value)
     487        return value
     488
    457489class ImageField(FileField):
    458490    default_error_messages = {
    459491        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
    460492    }
    461493
    462     def clean(self, data, initial=None):
     494    def validate(self, value):
    463495        """
    464496        Checks that the file-upload field data contains a valid image (GIF, JPG,
    465497        PNG, possibly others -- whatever the Python Imaging Library supports).
    466498        """
    467         f = super(ImageField, self).clean(data, initial)
    468         if f is None:
    469             return None
    470         elif not data and initial:
    471             return initial
     499        super(ImageField, self).validate(value)
     500
     501        if value is None:
     502            return
    472503        from PIL import Image
    473504        from cStringIO import StringIO
    474505        try:
    475506            # load() is the only method that can spot a truncated JPEG,
    476507            #  but it cannot be called sanely after verify()
    477             trial_image = Image.open(StringIO(f.content))
     508            trial_image = Image.open(StringIO(value.content))
    478509            trial_image.load()
    479510            # verify() is the only method that can spot a corrupt PNG,
    480511            #  but it must be called immediately after the constructor
    481             trial_image = Image.open(StringIO(f.content))
     512            trial_image = Image.open(StringIO(value.content))
    482513            trial_image.verify()
    483514        except Exception: # Python Imaging Library doesn't recognize it as an image
    484515            raise ValidationError(self.error_messages['invalid_image'])
    485         return f
    486516
    487517url_re = re.compile(
    488518    r'^https?://' # http:// or https://
    class URLField(RegexField): 
    505535        self.verify_exists = verify_exists
    506536        self.user_agent = validator_user_agent
    507537
    508     def clean(self, value):
    509         # If no URL scheme given, assume http://
     538    def to_python(self, value):
     539        value = super(URLField, self).to_python(value)
    510540        if value and '://' not in value:
    511541            value = u'http://%s' % value
    512         value = super(URLField, self).clean(value)
    513         if value == u'':
    514             return value
     542        return value
     543
     544    def validate(self, value):
     545        # If no URL scheme given, assume http://
     546        super(URLField, self).validate(value)
     547        if value in EMPTY_VALUES:
     548            return
    515549        if self.verify_exists:
    516550            import urllib2
    517551            from django.conf import settings
    class URLField(RegexField): 
    529563                raise ValidationError(self.error_messages['invalid'])
    530564            except: # urllib2.URLError, httplib.InvalidURL, etc.
    531565                raise ValidationError(self.error_messages['invalid_link'])
    532         return value
    533566
    534 class BooleanField(Field):
     567class BooleanField(LegacyField):
    535568    widget = CheckboxInput
    536569
    537     def clean(self, value):
     570    def to_python(self, value):
    538571        """Returns a Python boolean object."""
    539         super(BooleanField, self).clean(value)
     572        if self.required and value in EMPTY_VALUES:
     573            raise ValidationError(self.error_messages['required'])
    540574        # Explicitly check for the string 'False', which is what a hidden field
    541575        # will submit for False. Because bool("True") == True, we don't need to
    542576        # handle that explicitly.
    class BooleanField(Field): 
    544578            return False
    545579        return bool(value)
    546580
     581
    547582class NullBooleanField(BooleanField):
    548583    """
    549584    A field whose valid values are None, True and False. Invalid values are
    550585    cleaned to None.
     586
     587    Note that validation doesn't apply here.
    551588    """
    552589    widget = NullBooleanSelect
    553590
    554     def clean(self, value):
     591    def to_python(self, value):
    555592        return {True: True, False: False}.get(value, None)
    556593
    557 class ChoiceField(Field):
     594    def validate(self, value):
     595        pass
     596
     597
     598class ChoiceField(LegacyField):
    558599    widget = Select
    559600    default_error_messages = {
    560601        'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'),
    class ChoiceField(Field): 
    577618
    578619    choices = property(_get_choices, _set_choices)
    579620
    580     def clean(self, value):
    581         """
    582         Validates that the input is in self.choices.
    583         """
    584         value = super(ChoiceField, self).clean(value)
    585         if value in EMPTY_VALUES:
    586             value = u''
    587         value = smart_unicode(value)
    588         if value == u'':
    589             return value
     621    def validate(self, value):
     622        super(ChoiceField, self).validate(value)
     623        if value in EMPTY_VALUES and not self.required:
     624            return u''
    590625        valid_values = set([smart_unicode(k) for k, v in self.choices])
    591626        if value not in valid_values:
    592627            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
    593         return value
    594628
    595629class MultipleChoiceField(ChoiceField):
    596630    hidden_widget = MultipleHiddenInput
    class MultipleChoiceField(ChoiceField): 
    600634        'invalid_list': _(u'Enter a list of values.'),
    601635    }
    602636
    603     def clean(self, value):
     637    def to_python(self, value):
    604638        """
    605639        Validates that the input is a list or tuple.
    606640        """
    607         if self.required and not value:
    608             raise ValidationError(self.error_messages['required'])
    609         elif not self.required and not value:
     641        if not value:
    610642            return []
    611643        if not isinstance(value, (list, tuple)):
    612             raise ValidationError(self.error_messages['invalid_list'])
    613         new_value = [smart_unicode(val) for val in value]
     644            raise TypeCoersionError(self.error_messages['invalid_list'])
     645        return [smart_unicode(val) for val in value]
     646
     647    def validate(self, value):
    614648        # Validate that each value in the value list is in self.choices.
     649        if self.required and value == []:
     650            raise ValidationError(self.error_messages['required'])
    615651        valid_values = set([smart_unicode(k) for k, v in self.choices])
    616         for val in new_value:
     652        for val in value:
    617653            if val not in valid_values:
    618654                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
    619         return new_value
    620655
    621 class ComboField(Field):
     656class ComboField(LegacyField):
    622657    """
    623658    A Field whose clean() method calls multiple Field clean() methods.
    624659    """
    class ComboField(Field): 
    631666            f.required = False
    632667        self.fields = fields
    633668
    634     def clean(self, value):
     669    def to_python(self, value):
     670        for field in self.fields:
     671            value = field.to_python(value)
     672        return value
     673
     674    def validate(self, value):
    635675        """
    636676        Validates the given value against all of self.fields, which is a
    637677        list of Field instances.
    638678        """
    639         super(ComboField, self).clean(value)
     679        super(ComboField, self).validate(value)
    640680        for field in self.fields:
    641             value = field.clean(value)
    642         return value
     681            field.validate(value)
    643682
    644 class MultiValueField(Field):
     683class MultiValueField(LegacyField):
    645684    """
    646685    A Field that aggregates the logic of multiple Fields.
    647686
    class MultiValueField(Field): 
    671710            f.required = False
    672711        self.fields = fields
    673712
    674     def clean(self, value):
     713    def to_python(self, value):
    675714        """
    676715        Validates every value in the given list. A value is validated against
    677716        the corresponding Field in self.fields.
    class MultiValueField(Field): 
    685724        if not value or isinstance(value, (list, tuple)):
    686725            if not value or not [v for v in value if v not in EMPTY_VALUES]:
    687726                if self.required:
    688                     raise ValidationError(self.error_messages['required'])
     727                    return None
    689728                else:
    690729                    return self.compress([])
    691730        else:
    692             raise ValidationError(self.error_messages['invalid'])
     731            raise TypeCoersionError(self.error_messages['invalid'])
     732
    693733        for i, field in enumerate(self.fields):
    694734            try:
    695735                field_value = value[i]
  • django/newforms/forms.py

    diff --git a/django/newforms/forms.py b/django/newforms/forms.py
    index 2c481e4..f797b0b 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.core.validation import ValidationError, ErrorList, NON_FIELD_ERRORS
    1112
    1213from fields import Field, FileField
    1314from widgets import TextInput, Textarea
    14 from util import flatatt, ErrorDict, ErrorList, ValidationError
     15from util import flatatt, ErrorDict
    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): 
    201201            try:
    202202                if isinstance(field, FileField):
    203203                    initial = self.initial.get(name, field.initial)
    204                     value = field.clean(value, initial)
     204                    value = field.to_python(value, initial)
    205205                else:
    206                     value = field.clean(value)
     206                    value = field.to_python(value)
     207                field.validate(value)
    207208                self.cleaned_data[name] = value
     209                # FIXME deprecated - keeping this here for backwards compatibility
    208210                if hasattr(self, 'clean_%s' % name):
    209211                    value = getattr(self, 'clean_%s' % name)()
    210212                    self.cleaned_data[name] = value
     213
     214                if hasattr(self, 'validate_%s' % name):
     215                    getattr(self, 'validate_%s' % name)(value)
    211216            except ValidationError, e:
    212217                self._errors[name] = e.messages
    213218                if name in self.cleaned_data:
    214219                    del self.cleaned_data[name]
    215220        try:
    216             self.cleaned_data = self.clean()
     221            self.validate()
    217222        except ValidationError, e:
    218             self._errors[NON_FIELD_ERRORS] = e.messages
     223            if hasattr(e, 'message_dict'):
     224                for k, v in e.message_dict.items():
     225                    self._errors.setdefault(k, []).extend(v)
     226            else:
     227                self._errors[NON_FIELD_ERRORS] = e.messages
    219228        if self._errors:
    220229            delattr(self, 'cleaned_data')
    221230
    222231    def clean(self):
    223232        """
     233        FIXME: deprecated, use validate() instead
     234
    224235        Hook for doing any extra form-wide cleaning after Field.clean() been
    225236        called on every field. Any ValidationError raised by this method will
    226237        not be associated with a particular field; it will have a special-case
    class BaseForm(StrAndUnicode): 
    228239        """
    229240        return self.cleaned_data
    230241
     242    def validate(self):
     243        self.cleaned_data = self.clean()
     244
    231245    def is_multipart(self):
    232246        """
    233247        Returns True if the form needs to be multipart-encrypted, i.e. it has
  • django/newforms/models.py

    diff --git a/django/newforms/models.py b/django/newforms/models.py
    index 0590839..5c4b0a4 100644
    a b from django.utils.translation import ugettext_lazy as _ 
    99from django.utils.encoding import smart_unicode
    1010from django.utils.datastructures import SortedDict
    1111from django.core.exceptions import ImproperlyConfigured
     12from django.core.validation import ValidationError, ErrorList, TypeCoersionError
    1213
    13 from util import ValidationError, ErrorList
    1414from forms import BaseForm, get_declared_fields
    1515from fields import Field, ChoiceField, EMPTY_VALUES
    1616from widgets import Select, SelectMultiple, MultipleHiddenInput
    class BaseModelForm(BaseForm): 
    257257            object_data.update(initial)
    258258        BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
    259259
     260    def validate(self):
     261        super(BaseModelForm, self).validate()
     262        if self._errors:
     263            return
     264        self.instance.validate(self.cleaned_data)
     265
    260266    def save(self, commit=True):
    261267        """
    262268        Saves this ``form``'s cleaned_data into model instance
    class ModelChoiceField(ChoiceField): 
    354360
    355361    choices = property(_get_choices, _set_choices)
    356362
    357     def clean(self, value):
    358         Field.clean(self, value)
    359         if value in EMPTY_VALUES:
     363    def to_python(self, value):
     364        if self.required and value in EMPTY_VALUES:
     365            raise ValidationError(self.error_messages['required'])
     366        elif value in EMPTY_VALUES:
    360367            return None
    361368        try:
    362369            value = self.queryset.get(pk=value)
    class ModelChoiceField(ChoiceField): 
    364371            raise ValidationError(self.error_messages['invalid_choice'])
    365372        return value
    366373
     374    def validate(self, value):
     375        pass
     376
    367377class ModelMultipleChoiceField(ModelChoiceField):
    368378    """A MultipleChoiceField whose choices are a model QuerySet."""
    369379    hidden_widget = MultipleHiddenInput
    class ModelMultipleChoiceField(ModelChoiceField): 
    380390            cache_choices, required, widget, label, initial, help_text,
    381391            *args, **kwargs)
    382392
    383     def clean(self, value):
     393    def to_python(self, value):
    384394        if self.required and not value:
    385395            raise ValidationError(self.error_messages['required'])
    386396        elif not self.required and not value:
    387397            return []
    388398        if not isinstance(value, (list, tuple)):
    389             raise ValidationError(self.error_messages['list'])
     399            raise TypeCoersionError(self.error_messages['list'])
    390400        final_values = []
    391401        for val in value:
    392402            try:
    class ModelMultipleChoiceField(ModelChoiceField): 
    396406            else:
    397407                final_values.append(obj)
    398408        return final_values
     409
  • django/newforms/util.py

    diff --git a/django/newforms/util.py b/django/newforms/util.py
    index b3edf41..89b93d6 100644
    a b class ErrorDict(dict, StrAndUnicode): 
    3030    def as_text(self):
    3131        return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u'  * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
    3232
    33 class ErrorList(list, StrAndUnicode):
    34     """
    35     A collection of errors that knows how to display itself in various formats.
    36     """
    37     def __unicode__(self):
    38         return self.as_ul()
    39 
    40     def as_ul(self):
    41         if not self: return u''
    42         return mark_safe(u'<ul class="errorlist">%s</ul>'
    43                 % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
    44 
    45     def as_text(self):
    46         if not self: return u''
    47         return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
    48 
    49     def __repr__(self):
    50         return repr([force_unicode(e) for e in self])
    51 
    52 class ValidationError(Exception):
    53     def __init__(self, message):
    54         """
    55         ValidationError can be passed any object that can be printed (usually
    56         a string) or a list of objects.
    57         """
    58         if isinstance(message, list):
    59             self.messages = ErrorList([smart_unicode(msg) for msg in message])
    60         else:
    61             message = smart_unicode(message)
    62             self.messages = ErrorList([message])
    63 
    64     def __str__(self):
    65         # This is needed because, without a __str__(), printing an exception
    66         # instance would result in this:
    67         # AttributeError: ValidationError instance has no attribute 'args'
    68         # See http://www.python.org/doc/current/tut/node10.html#handling
    69         return repr(self.messages)
  • django/oldforms/__init__.py

    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index fc87271..b7b0d10 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..0ff85c7
    - +  
     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.validation 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    if not alnum_re.search(field_data):
     59        raise ValidationError, _("This value must contain only letters, numbers and underscores.")
     60
     61def isAlphaNumericURL(field_data, all_data):
     62    if not alnumurl_re.search(field_data):
     63        raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
     64
     65def isSlug(field_data, all_data):
     66    if not slug_re.search(field_data):
     67        raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
     68
     69def isLowerCase(field_data, all_data):
     70    if field_data.lower() != field_data:
     71        raise ValidationError, _("Uppercase letters are not allowed here.")
     72
     73def isUpperCase(field_data, all_data):
     74    if field_data.upper() != field_data:
     75        raise ValidationError, _("Lowercase letters are not allowed here.")
     76
     77def isCommaSeparatedIntegerList(field_data, all_data):
     78    for supposed_int in field_data.split(','):
     79        try:
     80            int(supposed_int)
     81        except ValueError:
     82            raise ValidationError, _("Enter only digits separated by commas.")
     83
     84def isCommaSeparatedEmailList(field_data, all_data):
     85    """
     86    Checks that field_data is a string of e-mail addresses separated by commas.
     87    Blank field_data values will not throw a validation error, and whitespace
     88    is allowed around the commas.
     89    """
     90    for supposed_email in field_data.split(','):
     91        try:
     92            isValidEmail(supposed_email.strip(), '')
     93        except ValidationError:
     94            raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
     95
     96def isValidIPAddress4(field_data, all_data):
     97    if not ip4_re.search(field_data):
     98        raise ValidationError, _("Please enter a valid IP address.")
     99
     100def isNotEmpty(field_data, all_data):
     101    if field_data.strip() == '':
     102        raise ValidationError, _("Empty values are not allowed here.")
     103
     104def isOnlyDigits(field_data, all_data):
     105    if not field_data.isdigit():
     106        raise ValidationError, _("Non-numeric characters aren't allowed here.")
     107
     108def isNotOnlyDigits(field_data, all_data):
     109    if field_data.isdigit():
     110        raise ValidationError, _("This value can't be comprised solely of digits.")
     111
     112def isInteger(field_data, all_data):
     113    # This differs from isOnlyDigits because this accepts the negative sign
     114    if not integer_re.search(field_data):
     115        raise ValidationError, _("Enter a whole number.")
     116
     117def isOnlyLetters(field_data, all_data):
     118    if not field_data.isalpha():
     119        raise ValidationError, _("Only alphabetical characters are allowed here.")
     120
     121def _isValidDate(date_string):
     122    """
     123    A helper function used by isValidANSIDate and isValidANSIDatetime to
     124    check if the date is valid.  The date string is assumed to already be in
     125    YYYY-MM-DD format.
     126    """
     127    from datetime import date
     128    # Could use time.strptime here and catch errors, but datetime.date below
     129    # produces much friendlier error messages.
     130    year, month, day = map(int, date_string.split('-'))
     131    # This check is needed because strftime is used when saving the date
     132    # value to the database, and strftime requires that the year be >=1900.
     133    if year < 1900:
     134        raise ValidationError, _('Year must be 1900 or later.')
     135    try:
     136        date(year, month, day)
     137    except ValueError, e:
     138        msg = _('Invalid date: %s') % _(str(e))
     139        raise ValidationError, msg
     140
     141def isValidANSIDate(field_data, all_data):
     142    if not ansi_date_re.search(field_data):
     143        raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
     144    _isValidDate(field_data)
     145
     146def isValidANSITime(field_data, all_data):
     147    if not ansi_time_re.search(field_data):
     148        raise ValidationError, _('Enter a valid time in HH:MM format.')
     149
     150def isValidANSIDatetime(field_data, all_data):
     151    if not ansi_datetime_re.search(field_data):
     152        raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
     153    _isValidDate(field_data.split()[0])
     154
     155def isValidEmail(field_data, all_data):
     156    if not email_re.search(field_data):
     157        raise ValidationError, _('Enter a valid e-mail address.')
     158
     159def isValidImage(field_data, all_data):
     160    """
     161    Checks that the file-upload field data contains a valid image (GIF, JPG,
     162    PNG, possibly others -- whatever the Python Imaging Library supports).
     163    """
     164    from PIL import Image
     165    from cStringIO import StringIO
     166    try:
     167        content = field_data['content']
     168    except TypeError:
     169        raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
     170    try:
     171        # load() is the only method that can spot a truncated JPEG,
     172        #  but it cannot be called sanely after verify()
     173        trial_image = Image.open(StringIO(content))
     174        trial_image.load()
     175        # verify() is the only method that can spot a corrupt PNG,
     176        #  but it must be called immediately after the constructor
     177        trial_image = Image.open(StringIO(content))
     178        trial_image.verify()
     179    except Exception: # Python Imaging Library doesn't recognize it as an image
     180        raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
     181
     182def isValidImageURL(field_data, all_data):
     183    uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
     184    try:
     185        uc(field_data, all_data)
     186    except URLMimeTypeCheck.InvalidContentType:
     187        raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
     188
     189def isValidPhone(field_data, all_data):
     190    if not phone_re.search(field_data):
     191        raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
     192
     193def isValidQuicktimeVideoURL(field_data, all_data):
     194    "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
     195    uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
     196    try:
     197        uc(field_data, all_data)
     198    except URLMimeTypeCheck.InvalidContentType:
     199        raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
     200
     201def isValidURL(field_data, all_data):
     202    if not url_re.search(field_data):
     203        raise ValidationError, _("A valid URL is required.")
     204
     205def isValidHTML(field_data, all_data):
     206    import urllib, urllib2
     207    try:
     208        u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
     209    except:
     210        # Validator or Internet connection is unavailable. Fail silently.
     211        return
     212    html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
     213    if html_is_valid:
     214        return
     215    from xml.dom.minidom import parseString
     216    error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
     217    raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
     218
     219def isWellFormedXml(field_data, all_data):
     220    from xml.dom.minidom import parseString
     221    try:
     222        parseString(field_data)
     223    except Exception, e: # Naked except because we're not sure what will be thrown
     224        raise ValidationError, _("Badly formed XML: %s") % str(e)
     225
     226def isWellFormedXmlFragment(field_data, all_data):
     227    isWellFormedXml('<root>%s</root>' % field_data, all_data)
     228
     229def isExistingURL(field_data, all_data):
     230    try:
     231        headers = {
     232            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
     233            "Accept-Language" : "en-us,en;q=0.5",
     234            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
     235            "Connection" : "close",
     236            "User-Agent": settings.URL_VALIDATOR_USER_AGENT
     237            }
     238        req = urllib2.Request(field_data,None, headers)
     239        u = urllib2.urlopen(req)
     240    except ValueError:
     241        raise ValidationError, _("Invalid URL: %s") % field_data
     242    except urllib2.HTTPError, e:
     243        # 401s are valid; they just mean authorization is required.
     244        # 301 and 302 are redirects; they just mean look somewhere else.
     245        if str(e.code) not in ('401','301','302'):
     246            raise ValidationError, _("The URL %s is a broken link.") % field_data
     247    except: # urllib2.URLError, httplib.InvalidURL, etc.
     248        raise ValidationError, _("The URL %s is a broken link.") % field_data
     249
     250def isValidUSState(field_data, all_data):
     251    "Checks that the given string is a valid two-letter U.S. state abbreviation"
     252    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']
     253    if field_data.upper() not in states:
     254        raise ValidationError, _("Enter a valid U.S. state abbreviation.")
     255
     256def hasNoProfanities(field_data, all_data):
     257    """
     258    Checks that the given string has no profanities in it. This does a simple
     259    check for whether each profanity exists within the string, so 'fuck' will
     260    catch 'motherfucker' as well. Raises a ValidationError such as:
     261        Watch your mouth! The words "f--k" and "s--t" are not allowed here.
     262    """
     263    field_data = field_data.lower() # normalize
     264    words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
     265    if words_seen:
     266        from django.utils.text import get_text_list
     267        plural = len(words_seen)
     268        raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
     269            "Watch your mouth! The words %s are not allowed here.", plural) % \
     270            get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], _('and'))
     271
     272class AlwaysMatchesOtherField(object):
     273    def __init__(self, other_field_name, error_message=None):
     274        self.other = other_field_name
     275        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
     276        self.always_test = True
     277
     278    def __call__(self, field_data, all_data):
     279        if field_data != all_data[self.other]:
     280            raise ValidationError, self.error_message
     281
     282class ValidateIfOtherFieldEquals(object):
     283    def __init__(self, other_field, other_value, validator_list):
     284        self.other_field, self.other_value = other_field, other_value
     285        self.validator_list = validator_list
     286        self.always_test = True
     287
     288    def __call__(self, field_data, all_data):
     289        if self.other_field in all_data and all_data[self.other_field] == self.other_value:
     290            for v in self.validator_list:
     291                v(field_data, all_data)
     292
     293class RequiredIfOtherFieldNotGiven(object):
     294    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
     295        self.other, self.error_message = other_field_name, error_message
     296        self.always_test = True
     297
     298    def __call__(self, field_data, all_data):
     299        if not all_data.get(self.other, False) and not field_data:
     300            raise ValidationError, self.error_message
     301
     302class RequiredIfOtherFieldsGiven(object):
     303    def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
     304        self.other, self.error_message = other_field_names, error_message
     305        self.always_test = True
     306
     307    def __call__(self, field_data, all_data):
     308        for field in self.other:
     309            if all_data.get(field, False) and not field_data:
     310                raise ValidationError, self.error_message
     311
     312class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
     313    "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
     314    def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
     315        RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
     316
     317class RequiredIfOtherFieldEquals(object):
     318    def __init__(self, other_field, other_value, error_message=None, other_label=None):
     319        self.other_field = other_field
     320        self.other_value = other_value
     321        other_label = other_label or other_value
     322        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
     323            'field': other_field, 'value': other_label})
     324        self.always_test = True
     325
     326    def __call__(self, field_data, all_data):
     327        if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
     328            raise ValidationError(self.error_message)
     329
     330class RequiredIfOtherFieldDoesNotEqual(object):
     331    def __init__(self, other_field, other_value, other_label=None, error_message=None):
     332        self.other_field = other_field
     333        self.other_value = other_value
     334        other_label = other_label or other_value
     335        self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
     336            'field': other_field, 'value': other_label})
     337        self.always_test = True
     338
     339    def __call__(self, field_data, all_data):
     340        if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
     341            raise ValidationError(self.error_message)
     342
     343class IsLessThanOtherField(object):
     344    def __init__(self, other_field_name, error_message):
     345        self.other, self.error_message = other_field_name, error_message
     346
     347    def __call__(self, field_data, all_data):
     348        if field_data > all_data[self.other]:
     349            raise ValidationError, self.error_message
     350
     351class UniqueAmongstFieldsWithPrefix(object):
     352    def __init__(self, field_name, prefix, error_message):
     353        self.field_name, self.prefix = field_name, prefix
     354        self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
     355
     356    def __call__(self, field_data, all_data):
     357        for field_name, value in all_data.items():
     358            if field_name != self.field_name and value == field_data:
     359                raise ValidationError, self.error_message
     360
     361class NumberIsInRange(object):
     362    """
     363    Validator that tests if a value is in a range (inclusive).
     364    """
     365    def __init__(self, lower=None, upper=None, error_message=''):
     366        self.lower, self.upper = lower, upper
     367        if not error_message:
     368            if lower and upper:
     369                 self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
     370            elif lower:
     371                self.error_message = _("This value must be at least %s.") % lower
     372            elif upper:
     373                self.error_message = _("This value must be no more than %s.") % upper
     374        else:
     375            self.error_message = error_message
     376
     377    def __call__(self, field_data, all_data):
     378        # Try to make the value numeric. If this fails, we assume another
     379        # validator will catch the problem.
     380        try:
     381            val = float(field_data)
     382        except ValueError:
     383            return
     384
     385        # Now validate
     386        if self.lower and self.upper and (val < self.lower or val > self.upper):
     387            raise ValidationError(self.error_message)
     388        elif self.lower and val < self.lower:
     389            raise ValidationError(self.error_message)
     390        elif self.upper and val > self.upper:
     391            raise ValidationError(self.error_message)
     392
     393class IsAPowerOf(object):
     394    """
     395    Usage: If you create an instance of the IsPowerOf validator:
     396        v = IsAPowerOf(2)
     397   
     398    The following calls will succeed:
     399        v(4, None)
     400        v(8, None)
     401        v(16, None)
     402   
     403    But this call:
     404        v(17, None)
     405    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
     406    """
     407    def __init__(self, power_of):
     408        self.power_of = power_of
     409
     410    def __call__(self, field_data, all_data):
     411        from math import log
     412        val = log(int(field_data)) / log(self.power_of)
     413        if val != int(val):
     414            raise ValidationError, _("This value must be a power of %s.") % self.power_of
     415
     416class IsValidDecimal(object):
     417    def __init__(self, max_digits, decimal_places):
     418        self.max_digits, self.decimal_places = max_digits, decimal_places
     419
     420    def __call__(self, field_data, all_data):
     421        try:
     422            val = Decimal(field_data)
     423        except DecimalException:
     424            raise ValidationError, _("Please enter a valid decimal number.")
     425
     426        pieces = str(val).lstrip("-").split('.')
     427        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
     428        digits = len(pieces[0])
     429
     430        if digits + decimals > self.max_digits:
     431            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
     432                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
     433        if digits > (self.max_digits - self.decimal_places):
     434            raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
     435                "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)
     436        if decimals > self.decimal_places:
     437            raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
     438                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
     439
     440def isValidFloat(field_data, all_data):
     441    data = smart_str(field_data)
     442    try:
     443        float(data)
     444    except ValueError:
     445        raise ValidationError, _("Please enter a valid floating point number.")
     446
     447class HasAllowableSize(object):
     448    """
     449    Checks that the file-upload field data is a certain size. min_size and
     450    max_size are measurements in bytes.
     451    """
     452    def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
     453        self.min_size, self.max_size = min_size, max_size
     454        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)
     455        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)
     456
     457    def __call__(self, field_data, all_data):
     458        try:
     459            content = field_data['content']
     460        except TypeError:
     461            raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
     462        if self.min_size is not None and len(content) < self.min_size:
     463            raise ValidationError, self.min_error_message
     464        if self.max_size is not None and len(content) > self.max_size:
     465            raise ValidationError, self.max_error_message
     466
     467class MatchesRegularExpression(object):
     468    """
     469    Checks that the field matches the given regular-expression. The regex
     470    should be in string format, not already compiled.
     471    """
     472    def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
     473        self.regexp = re.compile(regexp)
     474        self.error_message = error_message
     475
     476    def __call__(self, field_data, all_data):
     477        if not self.regexp.search(field_data):
     478            raise ValidationError(self.error_message)
     479
     480class AnyValidator(object):
     481    """
     482    This validator tries all given validators. If any one of them succeeds,
     483    validation passes. If none of them succeeds, the given message is thrown
     484    as a validation error. The message is rather unspecific, so it's best to
     485    specify one on instantiation.
     486    """
     487    def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
     488        if validator_list is None: validator_list = []
     489        self.validator_list = validator_list
     490        self.error_message = error_message
     491        for v in validator_list:
     492            if hasattr(v, 'always_test'):
     493                self.always_test = True
     494
     495    def __call__(self, field_data, all_data):
     496        for v in self.validator_list:
     497            try:
     498                v(field_data, all_data)
     499                return
     500            except ValidationError, e:
     501                pass
     502        raise ValidationError(self.error_message)
     503
     504class URLMimeTypeCheck(object):
     505    "Checks that the provided URL points to a document with a listed mime type"
     506    class CouldNotRetrieve(ValidationError):
     507        pass
     508    class InvalidContentType(ValidationError):
     509        pass
     510
     511    def __init__(self, mime_type_list):
     512        self.mime_type_list = mime_type_list
     513
     514    def __call__(self, field_data, all_data):
     515        import urllib2
     516        try:
     517            isValidURL(field_data, all_data)
     518        except ValidationError:
     519            raise
     520        try:
     521            info = urllib2.urlopen(field_data).info()
     522        except (urllib2.HTTPError, urllib2.URLError):
     523            raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
     524        content_type = info['content-type']
     525        if content_type not in self.mime_type_list:
     526            raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
     527                'url': field_data, 'contenttype': content_type}
     528
     529class RelaxNGCompact(object):
     530    "Validate against a Relax NG compact schema"
     531    def __init__(self, schema_path, additional_root_element=None):
     532        self.schema_path = schema_path
     533        self.additional_root_element = additional_root_element
     534
     535    def __call__(self, field_data, all_data):
     536        import os, tempfile
     537        if self.additional_root_element:
     538            field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
     539                'are': self.additional_root_element,
     540                'data': field_data
     541            }
     542        filename = tempfile.mktemp() # Insecure, but nothing else worked
     543        fp = open(filename, 'w')
     544        fp.write(field_data)
     545        fp.close()
     546        if not os.path.exists(settings.JING_PATH):
     547            raise Exception, "%s not found!" % settings.JING_PATH
     548        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
     549        errors = [line.strip() for line in p.readlines()]
     550        p.close()
     551        os.unlink(filename)
     552        display_errors = []
     553        lines = field_data.split('\n')
     554        for error in errors:
     555            ignored, line, level, message = error.split(':', 3)
     556            # Scrape the Jing error messages to reword them more nicely.
     557            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
     558            if m:
     559                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
     560                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
     561                continue
     562            if message.strip() == 'text not allowed here':
     563                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
     564                    {'line':line, 'start':lines[int(line) - 1][:30]})
     565                continue
     566            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
     567            if m:
     568                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
     569                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
     570                continue
     571            m = re.search(r'\s*unknown element "(.*?)"', message)
     572            if m:
     573                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
     574                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
     575                continue
     576            if message.strip() == 'required attributes missing':
     577                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
     578                    {'line':line, 'start':lines[int(line) - 1][:30]})
     579                continue
     580            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
     581            if m:
     582                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
     583                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
     584                continue
     585            # Failing all those checks, use the default error message.
     586            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
     587            display_errors.append(display_error)
     588        if len(display_errors) > 0:
     589            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 470312f..864d3ab 100644
    a b Create a new article, with categories, via the form. 
    478478...         model = Article
    479479>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
    480480...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
     481>>> f.is_valid()
     482True
    481483>>> new_art = f.save()
    482484>>> new_art.id
    4834852
    ValidationError: [u'Select a valid choice. 100 is not one of the available choic 
    705707>>> f.clean('hello')
    706708Traceback (most recent call last):
    707709...
    708 ValidationError: [u'Enter a list of values.']
     710TypeCoersionError: [u'Enter a list of values.']
    709711
    710712# Add a Category object *after* the ModelMultipleChoiceField has already been
    711713# instantiated. This proves clean() checks the database during clean() rather
    u'...test2.txt' 
    829831>>> instance.delete()
    830832
    831833# Test the non-required FileField
    832 
     834# It should fail since the field IS required on the model
    833835>>> f = TextFileForm(data={'description': u'Assistance'})
    834836>>> f.fields['file'].required = False
    835837>>> f.is_valid()
    836 True
    837 >>> instance = f.save()
    838 >>> instance.file
    839 ''
     838False
     839>>> f.errors
     840{'file': [u'This field is required.']}
    840841
    841842>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
    842843>>> f.is_valid()
    u'...test2.png' 
    899900>>> f = ImageFileForm(data={'description': u'Test'})
    900901>>> f.fields['image'].required = False
    901902>>> f.is_valid()
    902 True
    903 >>> instance = f.save()
    904 >>> instance.image
    905 ''
     903False
     904>>> f.errors
     905{'image': [u'This field is required.']}
     906
    906907
    907908>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
    908909>>> f.is_valid()
  • tests/modeltests/validation/models.py

    diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
    index 63f9f7a..13416db 100644
    a b 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)
     35>>> p = Person(**dict(valid_params, email='john@e.com', name='Jack'))
     36>>> p.validate()
     37>>> p.save()
     38
     39>>> p = Person(**dict(valid_params, email='john@e.com'))
    3440>>> p.validate()
    35 {}
     41Traceback (most recent call last):
     42...
     43ValidationError: {'email': [u'This field must be unique']}
    3644
    37 >>> p = Person(**dict(valid_params, id='23'))
     45>>> p = Person(**dict(valid_params, id='23', name='Jack'))
    3846>>> p.validate()
    39 {}
     47Traceback (most recent call last):
     48...
     49ValidationError: {'__all__': u'Fields name, is_child must be unique.'}
    4050>>> p.id
    41 23
     51'23'
    4252
    43 >>> p = Person(**dict(valid_params, id='foo'))
    44 >>> p.validate()['id']
    45 [u'This value must be an integer.']
     53>>> p = Person(**dict(valid_params, email='john@e.com', id='foo'))
     54>>> p.validate()
     55Traceback (most recent call last):
     56...
     57ValidationError: {'id': [u'This value must be an integer.'], 'email': [u'This field must be unique']}
    4658
    4759>>> p = Person(**dict(valid_params, id=None))
    4860>>> p.validate()
    49 {}
    5061>>> repr(p.id)
    5162'None'
    5263
    5364>>> p = Person(**dict(valid_params, is_child='t'))
    5465>>> p.validate()
    55 {}
    5666>>> p.is_child
    57 True
     67't'
    5868
    5969>>> p = Person(**dict(valid_params, is_child='f'))
    6070>>> p.validate()
    61 {}
    62 >>> p.is_child
    63 False
    6471
    6572>>> p = Person(**dict(valid_params, is_child=True))
    6673>>> p.validate()
    67 {}
    6874>>> p.is_child
    6975True
    7076
    7177>>> p = Person(**dict(valid_params, is_child=False))
    7278>>> p.validate()
    73 {}
    7479>>> p.is_child
    7580False
    7681
    7782>>> p = Person(**dict(valid_params, is_child='foo'))
    78 >>> p.validate()['is_child']
    79 [u'This value must be either True or False.']
     83>>> p.validate()
     84Traceback (most recent call last):
     85...
     86ValidationError: {'is_child': [u'This value must be either True or False.']}
    8087
    8188>>> p = Person(**dict(valid_params, name=u'Jose'))
    8289>>> p.validate()
    83 {}
    8490>>> p.name
    8591u'Jose'
    8692
    8793>>> p = Person(**dict(valid_params, name=227))
    8894>>> p.validate()
    89 {}
    90 >>> p.name
    91 u'227'
    9295
    9396>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
    9497>>> p.validate()
    95 {}
    9698>>> p.birthdate
    9799datetime.date(2000, 5, 3)
    98100
    99101>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
    100102>>> p.validate()
    101 {}
    102103>>> p.birthdate
    103 datetime.date(2000, 5, 3)
     104datetime.datetime(2000, 5, 3, 0, 0)
    104105
    105106>>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
    106107>>> p.validate()
    107 {}
    108108>>> p.birthdate
    109 datetime.date(2000, 5, 3)
     109'2000-05-03'
    110110
    111111>>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
    112112>>> p.validate()
    113 {}
    114113>>> p.birthdate
    115 datetime.date(2000, 5, 3)
     114'2000-5-3'
    116115
    117116>>> p = Person(**dict(valid_params, birthdate='foo'))
    118 >>> p.validate()['birthdate']
    119 [u'Enter a valid date in YYYY-MM-DD format.']
     117>>> p.validate()
     118Traceback (most recent call last):
     119...
     120ValidationError: {'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
    120121
    121122>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
    122123>>> p.validate()
    123 {}
    124124>>> p.favorite_moment
    125125datetime.datetime(2002, 4, 3, 13, 23)
    126126
    127127>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
    128128>>> p.validate()
    129 {}
    130129>>> p.favorite_moment
    131130datetime.datetime(2002, 4, 3, 0, 0)
    132131
    133132>>> p = Person(**dict(valid_params, email='john@example.com'))
    134133>>> p.validate()
    135 {}
    136134>>> p.email
    137135'john@example.com'
    138136
    139137>>> p = Person(**dict(valid_params, email=u'john@example.com'))
    140138>>> p.validate()
    141 {}
    142139>>> p.email
    143140u'john@example.com'
    144141
    145142>>> p = Person(**dict(valid_params, email=22))
    146 >>> p.validate()['email']
    147 [u'Enter a valid e-mail address.']
     143>>> p.validate()
     144Traceback (most recent call last):
     145...
     146ValidationError: {'email': [u'Enter a valid e-mail address.']}
    148147
    149148# 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']
     149>>> from django.core.validation import ValidationError
     150>>> try:
     151...     Person(name='John Doe', is_child=True, email='abc@def.com').validate()
     152... except ValidationError, e:
     153...     e.message_dict['favorite_moment']
     154...     e.message_dict['birthdate']
    153155[u'This field is required.']
    154 >>> errors['birthdate']
    155156[u'This field is required.']
    156157
    157158"""}
  • 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 9f972f5..d2e4662 100644
    a b ValidationError: [u'REQUIRED'] 
    3535>>> f.clean('abc')
    3636Traceback (most recent call last):
    3737...
    38 ValidationError: [u'INVALID']
     38TypeCoersionError: [u'INVALID']
    3939>>> f.clean('4')
    4040Traceback (most recent call last):
    4141...
    ValidationError: [u'REQUIRED'] 
    5959>>> f.clean('abc')
    6060Traceback (most recent call last):
    6161...
    62 ValidationError: [u'INVALID']
     62TypeCoersionError: [u'INVALID']
    6363>>> f.clean('4')
    6464Traceback (most recent call last):
    6565...
    ValidationError: [u'REQUIRED'] 
    8787>>> f.clean('abc')
    8888Traceback (most recent call last):
    8989...
    90 ValidationError: [u'INVALID']
     90TypeCoersionError: [u'INVALID']
    9191>>> f.clean('4')
    9292Traceback (most recent call last):
    9393...
    ValidationError: [u'REQUIRED'] 
    121121>>> f.clean('abc')
    122122Traceback (most recent call last):
    123123...
    124 ValidationError: [u'INVALID']
     124TypeCoersionError: [u'INVALID']
    125125
    126126# TimeField ###################################################################
    127127
    ValidationError: [u'REQUIRED'] 
    135135>>> f.clean('abc')
    136136Traceback (most recent call last):
    137137...
    138 ValidationError: [u'INVALID']
     138TypeCoersionError: [u'INVALID']
    139139
    140140# DateTimeField ###############################################################
    141141
    ValidationError: [u'REQUIRED'] 
    149149>>> f.clean('abc')
    150150Traceback (most recent call last):
    151151...
    152 ValidationError: [u'INVALID']
     152TypeCoersionError: [u'INVALID']
    153153
    154154# RegexField ##################################################################
    155155
    ValidationError: [u'LENGTH 11, MAX LENGTH 10'] 
    203203
    204204>>> e = {'required': 'REQUIRED'}
    205205>>> e['invalid'] = 'INVALID'
    206 >>> e['missing'] = 'MISSING'
    207206>>> e['empty'] = 'EMPTY FILE'
    208207>>> f = FileField(error_messages=e)
    209208>>> f.clean('')
    ValidationError: [u'REQUIRED'] 
    213212>>> f.clean('abc')
    214213Traceback (most recent call last):
    215214...
    216 ValidationError: [u'INVALID']
     215TypeCoersionError: [u'INVALID']
    217216>>> f.clean({})
    218217Traceback (most recent call last):
    219218...
    220 ValidationError: [u'MISSING']
     219ValidationError: [u'REQUIRED']
    221220>>> f.clean({'filename': 'name', 'content':''})
    222221Traceback (most recent call last):
    223222...
    ValidationError: [u'REQUIRED'] 
    278277>>> f.clean('b')
    279278Traceback (most recent call last):
    280279...
    281 ValidationError: [u'NOT A LIST']
     280TypeCoersionError: [u'NOT A LIST']
    282281>>> f.clean(['b'])
    283282Traceback (most recent call last):
    284283...
    ValidationError: [u'REQUIRED'] 
    352351>>> f.clean('3')
    353352Traceback (most recent call last):
    354353...
    355 ValidationError: [u'NOT A LIST OF VALUES']
     354TypeCoersionError: [u'NOT A LIST OF VALUES']
    356355>>> f.clean(['4'])
    357356Traceback (most recent call last):
    358357...
  • tests/regressiontests/forms/extra.py

    diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py
    index a8b3697..7e381db 100644
    a b u'sirrobin' 
    424424# Test overriding ErrorList in a form #
    425425#######################################
    426426
    427 >>> from django.newforms.util import ErrorList
     427>>> from django.core.validation import ErrorList
    428428>>> class DivErrorList(ErrorList):
    429429...     def __unicode__(self):
    430430...         return self.as_divs()
  • tests/regressiontests/forms/fields.py

    diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
    index 9421d8c..d4f7e1a 100644
    a b Each Field's __init__() takes at least these parameters: 
    3131             field name, if the Field is part of a Form.
    3232    initial -- A value to use in this Field's initial display. This value is
    3333               *not* used as a fallback if data isn't given.
     34    validators -- Optional list of additional validator functions
    3435
    3536Other than that, the Field subclasses have class-specific options for
    3637__init__(). For example, CharField has a max_length option.
    u'1234567890' 
    103104>>> f.clean('1234567890a')
    104105u'1234567890a'
    105106
     107# Custom validator functions ##################################################
     108
     109>>> def validator(value): raise ValidationError('validator failed')
     110>>> f = CharField(min_length=10, validators=[validator])
     111>>> f.clean('aa')
     112Traceback (most recent call last):
     113...
     114ValidationError: [u'validator failed']
     115
     116>>> def validator2(value): raise ValidationError('validator2 failed')
     117>>> f = CharField(min_length=10, validators=[validator, validator, validator2])
     118>>> f.clean('aa')
     119Traceback (most recent call last):
     120...
     121ValidationError: [u'validator failed', u'validator failed', u'validator2 failed']
     122
     123>>> class MyCharField(CharField):
     124...     validators = [validator]
     125>>> f = MyCharField()
     126>>> f.clean('aa')
     127Traceback (most recent call last):
     128...
     129ValidationError: [u'validator failed']
     130
    106131# IntegerField ################################################################
    107132
    108133>>> f = IntegerField()
    True 
    123148>>> f.clean('a')
    124149Traceback (most recent call last):
    125150...
    126 ValidationError: [u'Enter a whole number.']
     151TypeCoersionError: [u'Enter a whole number.']
    127152>>> f.clean(42)
    12815342
    129154>>> f.clean(3.14)
    130155Traceback (most recent call last):
    131156...
    132 ValidationError: [u'Enter a whole number.']
     157TypeCoersionError: [u'Enter a whole number.']
    133158>>> f.clean('1 ')
    1341591
    135160>>> f.clean(' 1')
    ValidationError: [u'Enter a whole number.'] 
    139164>>> f.clean('1a')
    140165Traceback (most recent call last):
    141166...
    142 ValidationError: [u'Enter a whole number.']
     167TypeCoersionError: [u'Enter a whole number.']
    143168
    144169>>> f = IntegerField(required=False)
    145170>>> f.clean('')
    True 
    157182>>> f.clean('a')
    158183Traceback (most recent call last):
    159184...
    160 ValidationError: [u'Enter a whole number.']
     185TypeCoersionError: [u'Enter a whole number.']
    161186>>> f.clean('1 ')
    1621871
    163188>>> f.clean(' 1')
    ValidationError: [u'Enter a whole number.'] 
    167192>>> f.clean('1a')
    168193Traceback (most recent call last):
    169194...
    170 ValidationError: [u'Enter a whole number.']
     195TypeCoersionError: [u'Enter a whole number.']
    171196
    172197IntegerField accepts an optional max_value parameter:
    173198>>> f = IntegerField(max_value=10)
    True 
    260285>>> f.clean('a')
    261286Traceback (most recent call last):
    262287...
    263 ValidationError: [u'Enter a number.']
     288TypeCoersionError: [u'Enter a number.']
    264289>>> f.clean('1.0 ')
    2652901.0
    266291>>> f.clean(' 1.0')
    ValidationError: [u'Enter a number.'] 
    270295>>> f.clean('1.0a')
    271296Traceback (most recent call last):
    272297...
    273 ValidationError: [u'Enter a number.']
     298TypeCoersionError: [u'Enter a number.']
    274299
    275300>>> f = FloatField(required=False)
    276301>>> f.clean('')
    Decimal("3.14") 
    322347>>> f.clean('a')
    323348Traceback (most recent call last):
    324349...
    325 ValidationError: [u'Enter a number.']
     350TypeCoersionError: [u'Enter a number.']
    326351>>> f.clean(u'łąść')
    327352Traceback (most recent call last):
    328353...
    329 ValidationError: [u'Enter a number.']
     354TypeCoersionError: [u'Enter a number.']
    330355>>> f.clean('1.0 ')
    331356Decimal("1.0")
    332357>>> f.clean(' 1.0')
    Decimal("1.0") 
    336361>>> f.clean('1.0a')
    337362Traceback (most recent call last):
    338363...
    339 ValidationError: [u'Enter a number.']
     364TypeCoersionError: [u'Enter a number.']
    340365>>> f.clean('123.45')
    341366Traceback (most recent call last):
    342367...
    ValidationError: [u'Ensure that there are no more than 4 digits in total.'] 
    372397>>> f.clean('--0.12')
    373398Traceback (most recent call last):
    374399...
    375 ValidationError: [u'Enter a number.']
     400TypeCoersionError: [u'Enter a number.']
    376401
    377402>>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
    378403>>> f.clean('')
    datetime.date(2006, 10, 25) 
    433458>>> f.clean('2006-4-31')
    434459Traceback (most recent call last):
    435460...
    436 ValidationError: [u'Enter a valid date.']
     461TypeCoersionError: [u'Enter a valid date.']
    437462>>> f.clean('200a-10-25')
    438463Traceback (most recent call last):
    439464...
    440 ValidationError: [u'Enter a valid date.']
     465TypeCoersionError: [u'Enter a valid date.']
    441466>>> f.clean('25/10/06')
    442467Traceback (most recent call last):
    443468...
    444 ValidationError: [u'Enter a valid date.']
     469TypeCoersionError: [u'Enter a valid date.']
    445470>>> f.clean(None)
    446471Traceback (most recent call last):
    447472...
    so the default formats won't work unless you specify them: 
    469494>>> f.clean('2006-10-25')
    470495Traceback (most recent call last):
    471496...
    472 ValidationError: [u'Enter a valid date.']
     497TypeCoersionError: [u'Enter a valid date.']
    473498>>> f.clean('10/25/2006')
    474499Traceback (most recent call last):
    475500...
    476 ValidationError: [u'Enter a valid date.']
     501TypeCoersionError: [u'Enter a valid date.']
    477502>>> f.clean('10/25/06')
    478503Traceback (most recent call last):
    479504...
    480 ValidationError: [u'Enter a valid date.']
     505TypeCoersionError: [u'Enter a valid date.']
    481506
    482507# TimeField ###################################################################
    483508
    datetime.time(14, 25, 59) 
    494519>>> f.clean('hello')
    495520Traceback (most recent call last):
    496521...
    497 ValidationError: [u'Enter a valid time.']
     522TypeCoersionError: [u'Enter a valid time.']
    498523>>> f.clean('1:24 p.m.')
    499524Traceback (most recent call last):
    500525...
    501 ValidationError: [u'Enter a valid time.']
     526TypeCoersionError: [u'Enter a valid time.']
    502527
    503528TimeField accepts an optional input_formats parameter:
    504529>>> f = TimeField(input_formats=['%I:%M %p'])
    so the default formats won't work unless you specify them: 
    516541>>> f.clean('14:30:45')
    517542Traceback (most recent call last):
    518543...
    519 ValidationError: [u'Enter a valid time.']
     544TypeCoersionError: [u'Enter a valid time.']
    520545
    521546# DateTimeField ###############################################################
    522547
    datetime.datetime(2006, 10, 25, 0, 0) 
    557582>>> f.clean('hello')
    558583Traceback (most recent call last):
    559584...
    560 ValidationError: [u'Enter a valid date/time.']
     585TypeCoersionError: [u'Enter a valid date/time.']
    561586>>> f.clean('2006-10-25 4:30 p.m.')
    562587Traceback (most recent call last):
    563588...
    564 ValidationError: [u'Enter a valid date/time.']
     589TypeCoersionError: [u'Enter a valid date/time.']
    565590
    566591DateField accepts an optional input_formats parameter:
    567592>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
    so the default formats won't work unless you specify them: 
    581606>>> f.clean('2006-10-25 14:30:45')
    582607Traceback (most recent call last):
    583608...
    584 ValidationError: [u'Enter a valid date/time.']
     609TypeCoersionError: [u'Enter a valid date/time.']
    585610
    586611>>> f = DateTimeField(required=False)
    587612>>> f.clean(None)
    ValidationError: [u'This field is required.'] 
    773798>>> f.clean({})
    774799Traceback (most recent call last):
    775800...
    776 ValidationError: [u'No file was submitted.']
     801ValidationError: [u'This field is required.']
    777802
    778803>>> f.clean({}, '')
    779804Traceback (most recent call last):
    780805...
    781 ValidationError: [u'No file was submitted.']
     806ValidationError: [u'This field is required.']
    782807
    783808>>> f.clean({}, 'files/test3.pdf')
    784809'files/test3.pdf'
    ValidationError: [u'No file was submitted.'] 
    786811>>> f.clean('some content that is not a file')
    787812Traceback (most recent call last):
    788813...
    789 ValidationError: [u'No file was submitted. Check the encoding type on the form.']
     814TypeCoersionError: [u'No file was submitted. Check the encoding type on the form.']
    790815
    791816>>> f.clean({'filename': 'name', 'content': None})
    792817Traceback (most recent call last):
    ValidationError: [u'This field is required.'] 
    10511076>>> f.clean('hello')
    10521077Traceback (most recent call last):
    10531078...
    1054 ValidationError: [u'Enter a list of values.']
     1079TypeCoersionError: [u'Enter a list of values.']
    10551080>>> f.clean([])
    10561081Traceback (most recent call last):
    10571082...
    ValidationError: [u'Select a valid choice. 3 is not one of the available choices 
    10831108>>> f.clean('hello')
    10841109Traceback (most recent call last):
    10851110...
    1086 ValidationError: [u'Enter a list of values.']
     1111TypeCoersionError: [u'Enter a list of values.']
    10871112>>> f.clean([])
    10881113[]
    10891114>>> f.clean(())
    ValidationError: [u'This field is required.'] 
    11761201>>> f.clean('hello')
    11771202Traceback (most recent call last):
    11781203...
    1179 ValidationError: [u'Enter a list of values.']
     1204TypeCoersionError: [u'Enter a list of values.']
    11801205>>> f.clean(['hello', 'there'])
    11811206Traceback (most recent call last):
    11821207...
    datetime.datetime(2006, 1, 10, 7, 30) 
    12021227>>> f.clean('hello')
    12031228Traceback (most recent call last):
    12041229...
    1205 ValidationError: [u'Enter a list of values.']
     1230TypeCoersionError: [u'Enter a list of values.']
    12061231>>> f.clean(['hello', 'there'])
    12071232Traceback (most recent call last):
    12081233...
  • tests/regressiontests/forms/localflavor/generic.py

    diff --git a/tests/regressiontests/forms/localflavor/generic.py b/tests/regressiontests/forms/localflavor/generic.py
    index 0dbe30d..1c200d0 100644
    a b datetime.date(2006, 10, 25) 
    3838>>> f.clean('2006-4-31')
    3939Traceback (most recent call last):
    4040...
    41 ValidationError: [u'Enter a valid date.']
     41TypeCoersionError: [u'Enter a valid date.']
    4242>>> f.clean('200a-10-25')
    4343Traceback (most recent call last):
    4444...
    45 ValidationError: [u'Enter a valid date.']
     45TypeCoersionError: [u'Enter a valid date.']
    4646>>> f.clean('10/25/06')
    4747Traceback (most recent call last):
    4848...
    49 ValidationError: [u'Enter a valid date.']
     49TypeCoersionError: [u'Enter a valid date.']
    5050>>> f.clean(None)
    5151Traceback (most recent call last):
    5252...
    so the default formats won't work unless you specify them: 
    7474>>> f.clean('2006-10-25')
    7575Traceback (most recent call last):
    7676...
    77 ValidationError: [u'Enter a valid date.']
     77TypeCoersionError: [u'Enter a valid date.']
    7878>>> f.clean('25/10/2006')
    7979Traceback (most recent call last):
    8080...
    81 ValidationError: [u'Enter a valid date.']
     81TypeCoersionError: [u'Enter a valid date.']
    8282>>> f.clean('25/10/06')
    8383Traceback (most recent call last):
    8484...
    85 ValidationError: [u'Enter a valid date.']
     85TypeCoersionError: [u'Enter a valid date.']
    8686
    8787## Generic DateTimeField ######################################################
    8888
    datetime.datetime(2006, 10, 25, 0, 0) 
    126126>>> f.clean('hello')
    127127Traceback (most recent call last):
    128128...
    129 ValidationError: [u'Enter a valid date/time.']
     129TypeCoersionError: [u'Enter a valid date/time.']
    130130>>> f.clean('2006-10-25 4:30 p.m.')
    131131Traceback (most recent call last):
    132132...
    133 ValidationError: [u'Enter a valid date/time.']
     133TypeCoersionError: [u'Enter a valid date/time.']
    134134
    135135DateField accepts an optional input_formats parameter:
    136136>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
    so the default formats won't work unless you specify them: 
    150150>>> f.clean('2006-10-25 14:30:45')
    151151Traceback (most recent call last):
    152152...
    153 ValidationError: [u'Enter a valid date/time.']
     153TypeCoersionError: [u'Enter a valid date/time.']
    154154
    155155>>> f = DateTimeField(required=False)
    156156>>> f.clean(None)
  • tests/regressiontests/forms/util.py

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