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

File 6845-against-django-7350.diff, 134.9 KB (added by Honza Král, 16 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/br/forms.py

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

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

    diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
    index 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..4de7c98
    - +  
     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 TypeCoercionError(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..225fa77 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 clean(self, new_data=None):
     282        self.to_python()
     283        self.validate(new_data)
     284
     285    def to_python(self):
     286        error_dict = {}
     287        for f in self._meta.fields:
     288            try:
     289                value = f.to_python(getattr(self, f.attname, f.get_default()))
     290                setattr(self, f.attname, value)
     291            except ValidationError, e:
     292                error_dict[f.name] = e.messages
     293        if error_dict:
     294            raise ValidationError(error_dict)
     295
     296    def validate(self, new_data=None):
    281297        """
    282298        First coerces all fields on this instance to their proper Python types.
    283299        Then runs validation on every field. Returns a dictionary of
    284300        field_name -> error_list.
    285301        """
     302        if new_data is not None:
     303            def get_value(f):
     304                if f.name in new_data:
     305                    return f.to_python(new_data[f.name])
     306                return getattr(self, f.attname, f.get_default())
     307        else:
     308            get_value = lambda f: getattr(self, f.attname, f.get_default())
    286309        error_dict = {}
    287         invalid_python = {}
    288310        for f in self._meta.fields:
    289311            try:
    290                 setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default())))
    291             except validators.ValidationError, e:
     312                value = get_value(f)
     313                f.validate(value, instance=self)
     314                if hasattr(self, 'validate_%s' % f.name):
     315                    getattr(self, 'validate_%s' % f.name)(value)
     316            except ValidationError, e:
    292317                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
     318
     319        for un_together in self._meta.unique_together:
     320            lookup = {}
     321            for name in un_together:
     322                if name in error_dict:
     323                    break
     324                f = self._meta.get_field(name)
     325                lookup['%s__exact' % name] = get_value(f)
     326            try:
     327                qset = self.__class__._default_manager.all()
     328                if self.pk:
     329                    qset = qset.exclude(pk=self.pk)
     330                obj = qset.get(**lookup)
     331                error_dict[NON_FIELD_ERRORS] = _('Fields %s must be unique.') % ', '.join(un_together)
     332            except self.DoesNotExist:
     333                pass
     334
     335        if error_dict:
     336            raise ValidationError(error_dict)
    301337
    302338    def _collect_sub_objects(self, seen_objs):
    303339        """
  • 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..a7426f7 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, TypeCoercionError
    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        if value in EMPTY_VALUES:
     102            return None
     103        return smart_unicode(value)
     104
     105    def validate(self, value):
     106        if self.required and value in EMPTY_VALUES:
     107            raise ValidationError(self.error_messages['required'])
     108        elist = ErrorList()
     109        for validator in self.validators:
     110            try:
     111                validator(value)
     112            except ValidationError, e:
     113                elist.extend(e.messages)
     114        if elist:
     115            raise ValidationError(elist)
     116
    97117    def clean(self, value):
    98118        """
    99119        Validates the given value and returns its "cleaned" value as an
    class Field(object):  
    101121
    102122        Raises ValidationError for any errors.
    103123        """
    104         if self.required and value in EMPTY_VALUES:
    105             raise ValidationError(self.error_messages['required'])
     124        value = self.to_python(value)
     125        self.validate(value)
    106126        return value
    107127
    108128    def widget_attrs(self, widget):
    class CharField(Field):  
    129149        self.max_length, self.min_length = max_length, min_length
    130150        super(CharField, self).__init__(*args, **kwargs)
    131151
    132     def clean(self, value):
    133         "Validates max_length and min_length. Returns a Unicode object."
    134         super(CharField, self).clean(value)
     152    def to_python(self, value):
    135153        if value in EMPTY_VALUES:
    136154            return u''
    137         value = smart_unicode(value)
     155        return smart_unicode(value)
     156
     157    def validate(self, value):
     158        "Validates max_length and min_length. Returns a Unicode object."
     159        super(CharField, self).validate(value)
    138160        value_length = len(value)
     161        if value_length == 0 and not self.required:
     162            return
    139163        if self.max_length is not None and value_length > self.max_length:
    140164            raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
    141165        if self.min_length is not None and value_length < self.min_length:
    142166            raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
    143         return value
    144167
    145168    def widget_attrs(self, widget):
    146169        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
    class IntegerField(Field):  
    158181        self.max_value, self.min_value = max_value, min_value
    159182        super(IntegerField, self).__init__(*args, **kwargs)
    160183
    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)
     184    def to_python(self, value):
    167185        if value in EMPTY_VALUES:
    168186            return None
    169187        try:
    170             value = int(str(value))
     188            return int(smart_str(value))
    171189        except (ValueError, TypeError):
    172             raise ValidationError(self.error_messages['invalid'])
     190            raise TypeCoercionError(self.error_messages['invalid'])
     191
     192    def validate(self, value):
     193        """
     194        Validates that int() can be called on the input. Returns the result
     195        of int(). Returns None for empty values.
     196        """
     197        super(IntegerField, self).validate(value)
     198        if value is None: return
    173199        if self.max_value is not None and value > self.max_value:
    174200            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    175201        if self.min_value is not None and value < self.min_value:
    176202            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    177         return value
    178203
    179204class FloatField(Field):
    180205    default_error_messages = {
    class FloatField(Field):  
    187212        self.max_value, self.min_value = max_value, min_value
    188213        Field.__init__(self, *args, **kwargs)
    189214
    190     def clean(self, value):
     215    def to_python(self, value):
    191216        """
    192217        Validates that float() can be called on the input. Returns a float.
    193218        Returns None for empty values.
    194219        """
    195         super(FloatField, self).clean(value)
    196         if not self.required and value in EMPTY_VALUES:
     220        if value in EMPTY_VALUES:
    197221            return None
    198222        try:
    199             value = float(value)
     223            return float(value)
    200224        except (ValueError, TypeError):
    201             raise ValidationError(self.error_messages['invalid'])
     225            raise TypeCoercionError(self.error_messages['invalid'])
     226
     227    def validate(self, value):
     228        super(FloatField, self).validate(value)
     229        if value is None: return
    202230        if self.max_value is not None and value > self.max_value:
    203231            raise ValidationError(self.error_messages['max_value'] % self.max_value)
    204232        if self.min_value is not None and value < self.min_value:
    205233            raise ValidationError(self.error_messages['min_value'] % self.min_value)
    206         return value
    207234
    208235class DecimalField(Field):
    209236    default_error_messages = {
    class DecimalField(Field):  
    220247        self.max_digits, self.decimal_places = max_digits, decimal_places
    221248        Field.__init__(self, *args, **kwargs)
    222249
    223     def clean(self, value):
     250    def to_python(self, value):
    224251        """
    225252        Validates that the input is a decimal number. Returns a Decimal
    226253        instance. Returns None for empty values. Ensures that there are no more
    227254        than max_digits in the number, and no more than decimal_places digits
    228255        after the decimal point.
    229256        """
    230         super(DecimalField, self).clean(value)
    231         if not self.required and value in EMPTY_VALUES:
     257        if value in EMPTY_VALUES:
    232258            return None
    233259        value = smart_str(value).strip()
    234260        try:
    235             value = Decimal(value)
     261            return Decimal(value)
    236262        except DecimalException:
    237             raise ValidationError(self.error_messages['invalid'])
     263            raise TypeCoercionError(self.error_messages['invalid'])
     264
     265    def validate(self, value):
     266        super(DecimalField, self).validate(value)
     267        if value is None: return
    238268        pieces = str(value).lstrip("-").split('.')
    239269        decimals = (len(pieces) == 2) and len(pieces[1]) or 0
    240270        digits = len(pieces[0])
    class DecimalField(Field):  
    248278            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
    249279        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
    250280            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
    251         return value
    252281
    253282DEFAULT_DATE_INPUT_FORMATS = (
    254283    '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
    class DateField(Field):  
    267296        super(DateField, self).__init__(*args, **kwargs)
    268297        self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
    269298
    270     def clean(self, value):
     299    def to_python(self, value):
    271300        """
    272301        Validates that the input can be converted to a date. Returns a Python
    273302        datetime.date object.
    274303        """
    275         super(DateField, self).clean(value)
    276304        if value in EMPTY_VALUES:
    277305            return None
    278306        if isinstance(value, datetime.datetime):
    class DateField(Field):  
    284312                return datetime.date(*time.strptime(value, format)[:3])
    285313            except ValueError:
    286314                continue
    287         raise ValidationError(self.error_messages['invalid'])
     315        raise TypeCoercionError(self.error_messages['invalid'])
    288316
    289317DEFAULT_TIME_INPUT_FORMATS = (
    290318    '%H:%M:%S',     # '14:30:59'
    class TimeField(Field):  
    300328        super(TimeField, self).__init__(*args, **kwargs)
    301329        self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
    302330
    303     def clean(self, value):
     331    def to_python(self, value):
    304332        """
    305333        Validates that the input can be converted to a time. Returns a Python
    306334        datetime.time object.
    307335        """
    308         super(TimeField, self).clean(value)
    309336        if value in EMPTY_VALUES:
    310337            return None
    311338        if isinstance(value, datetime.time):
    class TimeField(Field):  
    315342                return datetime.time(*time.strptime(value, format)[3:6])
    316343            except ValueError:
    317344                continue
    318         raise ValidationError(self.error_messages['invalid'])
     345        raise TypeCoercionError(self.error_messages['invalid'])
    319346
    320347DEFAULT_DATETIME_INPUT_FORMATS = (
    321348    '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
    class DateTimeField(Field):  
    339366        super(DateTimeField, self).__init__(*args, **kwargs)
    340367        self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
    341368
    342     def clean(self, value):
     369    def to_python(self, value):
    343370        """
    344371        Validates that the input can be converted to a datetime. Returns a
    345372        Python datetime.datetime object.
    346373        """
    347         super(DateTimeField, self).clean(value)
    348374        if value in EMPTY_VALUES:
    349375            return None
    350376        if isinstance(value, datetime.datetime):
    class DateTimeField(Field):  
    362388                return datetime.datetime(*time.strptime(value, format)[:6])
    363389            except ValueError:
    364390                continue
    365         raise ValidationError(self.error_messages['invalid'])
     391        raise TypeCoercionError(self.error_messages['invalid'])
    366392
    367393class RegexField(CharField):
    368394    def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
    class RegexField(CharField):  
    381407            regex = re.compile(regex)
    382408        self.regex = regex
    383409
    384     def clean(self, value):
     410    def validate(self, value):
    385411        """
    386412        Validates that the input matches the regular expression. Returns a
    387413        Unicode object.
    388414        """
    389         value = super(RegexField, self).clean(value)
    390         if value == u'':
    391             return value
     415        super(RegexField, self).validate(value)
     416        if value in EMPTY_VALUES:
     417            return u''
    392418        if not self.regex.search(value):
    393419            raise ValidationError(self.error_messages['invalid'])
    394         return value
    395420
    396421email_re = re.compile(
    397422    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
    class FileField(Field):  
    431456    widget = FileInput
    432457    default_error_messages = {
    433458        'invalid': _(u"No file was submitted. Check the encoding type on the form."),
    434         'missing': _(u"No file was submitted."),
    435459        'empty': _(u"The submitted file is empty."),
    436460    }
    437461
    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)
     462    def to_python(self, data, initial=None):
    443463        if not self.required and data in EMPTY_VALUES:
    444464            return None
    445465        elif not data and initial:
    446466            return initial
     467        elif not data:
     468            return None
     469
    447470        try:
    448471            f = UploadedFile(data['filename'], data['content'])
    449472        except TypeError:
    450             raise ValidationError(self.error_messages['invalid'])
     473            raise TypeCoercionError(self.error_messages['invalid'])
    451474        except KeyError:
    452             raise ValidationError(self.error_messages['missing'])
     475            raise ValidationError(self.error_messages['required'])
    453476        if not f.content:
    454477            raise ValidationError(self.error_messages['empty'])
    455478        return f
    456479
     480    def clean(self, data, initial=None):
     481        "overriden clean to provide extra argument initial"
     482        value = self.to_python(data, initial)
     483        self.validate(value)
     484        return value
     485
    457486class ImageField(FileField):
    458487    default_error_messages = {
    459488        'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
    460489    }
    461490
    462     def clean(self, data, initial=None):
     491    def validate(self, value):
    463492        """
    464493        Checks that the file-upload field data contains a valid image (GIF, JPG,
    465494        PNG, possibly others -- whatever the Python Imaging Library supports).
    466495        """
    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
     496        super(ImageField, self).validate(value)
     497
     498        if value is None:
     499            return
    472500        from PIL import Image
    473501        from cStringIO import StringIO
    474502        try:
    475503            # load() is the only method that can spot a truncated JPEG,
    476504            #  but it cannot be called sanely after verify()
    477             trial_image = Image.open(StringIO(f.content))
     505            trial_image = Image.open(StringIO(value.content))
    478506            trial_image.load()
    479507            # verify() is the only method that can spot a corrupt PNG,
    480508            #  but it must be called immediately after the constructor
    481             trial_image = Image.open(StringIO(f.content))
     509            trial_image = Image.open(StringIO(value.content))
    482510            trial_image.verify()
    483511        except Exception: # Python Imaging Library doesn't recognize it as an image
    484512            raise ValidationError(self.error_messages['invalid_image'])
    485         return f
    486513
    487514url_re = re.compile(
    488515    r'^https?://' # http:// or https://
    class URLField(RegexField):  
    505532        self.verify_exists = verify_exists
    506533        self.user_agent = validator_user_agent
    507534
    508     def clean(self, value):
    509         # If no URL scheme given, assume http://
     535    def to_python(self, value):
     536        value = super(URLField, self).to_python(value)
    510537        if value and '://' not in value:
    511538            value = u'http://%s' % value
    512         value = super(URLField, self).clean(value)
    513         if value == u'':
    514             return value
     539        return value
     540
     541    def validate(self, value):
     542        # If no URL scheme given, assume http://
     543        super(URLField, self).validate(value)
     544        if value in EMPTY_VALUES:
     545            return
    515546        if self.verify_exists:
    516547            import urllib2
    517548            from django.conf import settings
    class URLField(RegexField):  
    529560                raise ValidationError(self.error_messages['invalid'])
    530561            except: # urllib2.URLError, httplib.InvalidURL, etc.
    531562                raise ValidationError(self.error_messages['invalid_link'])
    532         return value
    533563
    534564class BooleanField(Field):
    535565    widget = CheckboxInput
    536566
    537     def clean(self, value):
     567    def to_python(self, value):
    538568        """Returns a Python boolean object."""
    539         super(BooleanField, self).clean(value)
     569        if self.required and value in EMPTY_VALUES:
     570            raise ValidationError(self.error_messages['required'])
    540571        # Explicitly check for the string 'False', which is what a hidden field
    541572        # will submit for False. Because bool("True") == True, we don't need to
    542573        # handle that explicitly.
    class BooleanField(Field):  
    544575            return False
    545576        return bool(value)
    546577
     578
    547579class NullBooleanField(BooleanField):
    548580    """
    549581    A field whose valid values are None, True and False. Invalid values are
    550582    cleaned to None.
     583
     584    Note that validation doesn't apply here.
    551585    """
    552586    widget = NullBooleanSelect
    553587
    554     def clean(self, value):
     588    def to_python(self, value):
    555589        return {True: True, False: False}.get(value, None)
    556590
     591    def validate(self, value):
     592        pass
     593
     594
    557595class ChoiceField(Field):
    558596    widget = Select
    559597    default_error_messages = {
    class ChoiceField(Field):  
    577615
    578616    choices = property(_get_choices, _set_choices)
    579617
    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
     618    def validate(self, value):
     619        super(ChoiceField, self).validate(value)
     620        if value in EMPTY_VALUES and not self.required:
     621            return u''
    590622        valid_values = set([smart_unicode(k) for k, v in self.choices])
    591623        if value not in valid_values:
    592624            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
    593         return value
    594625
    595626class MultipleChoiceField(ChoiceField):
    596627    hidden_widget = MultipleHiddenInput
    class MultipleChoiceField(ChoiceField):  
    600631        'invalid_list': _(u'Enter a list of values.'),
    601632    }
    602633
    603     def clean(self, value):
     634    def to_python(self, value):
    604635        """
    605636        Validates that the input is a list or tuple.
    606637        """
    607         if self.required and not value:
    608             raise ValidationError(self.error_messages['required'])
    609         elif not self.required and not value:
     638        if not value:
    610639            return []
    611640        if not isinstance(value, (list, tuple)):
    612             raise ValidationError(self.error_messages['invalid_list'])
    613         new_value = [smart_unicode(val) for val in value]
     641            raise TypeCoercionError(self.error_messages['invalid_list'])
     642        return [smart_unicode(val) for val in value]
     643
     644    def validate(self, value):
    614645        # Validate that each value in the value list is in self.choices.
     646        if self.required and value == []:
     647            raise ValidationError(self.error_messages['required'])
    615648        valid_values = set([smart_unicode(k) for k, v in self.choices])
    616         for val in new_value:
     649        for val in value:
    617650            if val not in valid_values:
    618651                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
    619         return new_value
    620652
    621653class ComboField(Field):
    622654    """
    class ComboField(Field):  
    631663            f.required = False
    632664        self.fields = fields
    633665
    634     def clean(self, value):
     666    def to_python(self, value):
     667        for field in self.fields:
     668            value = field.to_python(value)
     669        return value
     670
     671    def validate(self, value):
    635672        """
    636673        Validates the given value against all of self.fields, which is a
    637674        list of Field instances.
    638675        """
    639         super(ComboField, self).clean(value)
     676        super(ComboField, self).validate(value)
    640677        for field in self.fields:
    641             value = field.clean(value)
    642         return value
     678            field.validate(value)
    643679
    644680class MultiValueField(Field):
    645681    """
    class MultiValueField(Field):  
    671707            f.required = False
    672708        self.fields = fields
    673709
    674     def clean(self, value):
     710    def to_python(self, value):
    675711        """
    676712        Validates every value in the given list. A value is validated against
    677713        the corresponding Field in self.fields.
    class MultiValueField(Field):  
    685721        if not value or isinstance(value, (list, tuple)):
    686722            if not value or not [v for v in value if v not in EMPTY_VALUES]:
    687723                if self.required:
    688                     raise ValidationError(self.error_messages['required'])
     724                    return None
    689725                else:
    690726                    return self.compress([])
    691727        else:
    692             raise ValidationError(self.error_messages['invalid'])
     728            raise TypeCoercionError(self.error_messages['invalid'])
     729
    693730        for i, field in enumerate(self.fields):
    694731            try:
    695732                field_value = value[i]
  • django/newforms/forms.py

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

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

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

    diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
    index 9f972f5..44f5a25 100644
    a b ValidationError: [u'REQUIRED']  
    3535>>> f.clean('abc')
    3636Traceback (most recent call last):
    3737...
    38 ValidationError: [u'INVALID']
     38TypeCoercionError: [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']
     62TypeCoercionError: [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']
     90TypeCoercionError: [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']
     124TypeCoercionError: [u'INVALID']
    125125
    126126# TimeField ###################################################################
    127127
    ValidationError: [u'REQUIRED']  
    135135>>> f.clean('abc')
    136136Traceback (most recent call last):
    137137...
    138 ValidationError: [u'INVALID']
     138TypeCoercionError: [u'INVALID']
    139139
    140140# DateTimeField ###############################################################
    141141
    ValidationError: [u'REQUIRED']  
    149149>>> f.clean('abc')
    150150Traceback (most recent call last):
    151151...
    152 ValidationError: [u'INVALID']
     152TypeCoercionError: [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']
     215TypeCoercionError: [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']
     280TypeCoercionError: [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']
     354TypeCoercionError: [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..6b1413e 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.']
     151TypeCoercionError: [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.']
     157TypeCoercionError: [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.']
     167TypeCoercionError: [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.']
     185TypeCoercionError: [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.']
     195TypeCoercionError: [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.']
     288TypeCoercionError: [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.']
     298TypeCoercionError: [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.']
     350TypeCoercionError: [u'Enter a number.']
    326351>>> f.clean(u'łąść')
    327352Traceback (most recent call last):
    328353...
    329 ValidationError: [u'Enter a number.']
     354TypeCoercionError: [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.']
     364TypeCoercionError: [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.']
     400TypeCoercionError: [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.']
     461TypeCoercionError: [u'Enter a valid date.']
    437462>>> f.clean('200a-10-25')
    438463Traceback (most recent call last):
    439464...
    440 ValidationError: [u'Enter a valid date.']
     465TypeCoercionError: [u'Enter a valid date.']
    441466>>> f.clean('25/10/06')
    442467Traceback (most recent call last):
    443468...
    444 ValidationError: [u'Enter a valid date.']
     469TypeCoercionError: [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.']
     497TypeCoercionError: [u'Enter a valid date.']
    473498>>> f.clean('10/25/2006')
    474499Traceback (most recent call last):
    475500...
    476 ValidationError: [u'Enter a valid date.']
     501TypeCoercionError: [u'Enter a valid date.']
    477502>>> f.clean('10/25/06')
    478503Traceback (most recent call last):
    479504...
    480 ValidationError: [u'Enter a valid date.']
     505TypeCoercionError: [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.']
     522TypeCoercionError: [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.']
     526TypeCoercionError: [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.']
     544TypeCoercionError: [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.']
     585TypeCoercionError: [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.']
     589TypeCoercionError: [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.']
     609TypeCoercionError: [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.']
     814TypeCoercionError: [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'Select a valid choice. That choice is not one of the availab  
    9931018
    9941019>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
    9951020>>> f.clean('')
    996 u''
    9971021>>> f.clean(None)
    998 u''
    9991022>>> f.clean(1)
    10001023u'1'
    10011024>>> f.clean('1')
    ValidationError: [u'This field is required.']  
    10511074>>> f.clean('hello')
    10521075Traceback (most recent call last):
    10531076...
    1054 ValidationError: [u'Enter a list of values.']
     1077TypeCoercionError: [u'Enter a list of values.']
    10551078>>> f.clean([])
    10561079Traceback (most recent call last):
    10571080...
    ValidationError: [u'Select a valid choice. 3 is not one of the available choices  
    10831106>>> f.clean('hello')
    10841107Traceback (most recent call last):
    10851108...
    1086 ValidationError: [u'Enter a list of values.']
     1109TypeCoercionError: [u'Enter a list of values.']
    10871110>>> f.clean([])
    10881111[]
    10891112>>> f.clean(())
    ValidationError: [u'This field is required.']  
    11761199>>> f.clean('hello')
    11771200Traceback (most recent call last):
    11781201...
    1179 ValidationError: [u'Enter a list of values.']
     1202TypeCoercionError: [u'Enter a list of values.']
    11801203>>> f.clean(['hello', 'there'])
    11811204Traceback (most recent call last):
    11821205...
    datetime.datetime(2006, 1, 10, 7, 30)  
    12021225>>> f.clean('hello')
    12031226Traceback (most recent call last):
    12041227...
    1205 ValidationError: [u'Enter a list of values.']
     1228TypeCoercionError: [u'Enter a list of values.']
    12061229>>> f.clean(['hello', 'there'])
    12071230Traceback (most recent call last):
    12081231...
  • tests/regressiontests/forms/localflavor/br.py

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

    diff --git a/tests/regressiontests/forms/localflavor/generic.py b/tests/regressiontests/forms/localflavor/generic.py
    index 0dbe30d..cda4f89 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.']
     41TypeCoercionError: [u'Enter a valid date.']
    4242>>> f.clean('200a-10-25')
    4343Traceback (most recent call last):
    4444...
    45 ValidationError: [u'Enter a valid date.']
     45TypeCoercionError: [u'Enter a valid date.']
    4646>>> f.clean('10/25/06')
    4747Traceback (most recent call last):
    4848...
    49 ValidationError: [u'Enter a valid date.']
     49TypeCoercionError: [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.']
     77TypeCoercionError: [u'Enter a valid date.']
    7878>>> f.clean('25/10/2006')
    7979Traceback (most recent call last):
    8080...
    81 ValidationError: [u'Enter a valid date.']
     81TypeCoercionError: [u'Enter a valid date.']
    8282>>> f.clean('25/10/06')
    8383Traceback (most recent call last):
    8484...
    85 ValidationError: [u'Enter a valid date.']
     85TypeCoercionError: [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.']
     129TypeCoercionError: [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.']
     133TypeCoercionError: [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.']
     153TypeCoercionError: [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