2005-07-13 09:25:57 +08:00
"""
A library of validators that return None and raise ValidationError when the
provided data isn ' t valid.
Validators may be callable classes , and they may have an ' always_test '
attribute . If an ' always_test ' attribute exists ( regardless of value ) , the
validator will * always * be run , regardless of whether its associated
form field is required .
"""
2006-11-07 10:20:08 +08:00
import urllib2
2006-05-02 09:31:56 +08:00
from django . conf import settings
from django . utils . translation import gettext , gettext_lazy , ngettext
from django . utils . functional import Promise , lazy
2005-07-13 09:25:57 +08:00
import re
2006-09-24 20:09:32 +08:00
_datere = r ' \ d {4} - \ d { 1,2}- \ d { 1,2} '
2005-07-13 09:25:57 +08:00
_timere = r ' (?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])? '
alnum_re = re . compile ( r ' ^ \ w+$ ' )
2006-02-19 06:36:16 +08:00
alnumurl_re = re . compile ( r ' ^[- \ w/]+$ ' )
2005-07-13 09:25:57 +08:00
ansi_date_re = re . compile ( ' ^ %s $ ' % _datere )
ansi_time_re = re . compile ( ' ^ %s $ ' % _timere )
ansi_datetime_re = re . compile ( ' ^ %s %s $ ' % ( _datere , _timere ) )
2006-06-01 02:08:28 +08:00
email_re = re . compile (
r " (^[-!#$ % & ' *+/=?^_` {} |~0-9A-Z]+( \ .[-!#$ % & ' *+/=?^_` {} |~0-9A-Z]+)* " # dot-atom
r ' |^ " ([ \ 001- \ 010 \ 013 \ 014 \ 016- \ 037!#- \ [ \ ]- \ 177]| \\ [ \ 001-011 \ 013 \ 014 \ 016- \ 177])* " ' # quoted-string
2006-07-10 10:32:54 +08:00
r ' )@(?:[A-Z0-9-]+ \ .)+[A-Z] { 2,6}$ ' , re . IGNORECASE ) # domain
2005-07-13 09:25:57 +08:00
integer_re = re . compile ( r ' ^-? \ d+$ ' )
2005-11-07 01:55:39 +08:00
ip4_re = re . compile ( r ' ^(25[0-5]|2[0-4] \ d|[0-1]? \ d? \ d)( \ .(25[0-5]|2[0-4] \ d|[0-1]? \ d? \ d)) {3} $ ' )
2005-07-13 09:25:57 +08:00
phone_re = re . compile ( r ' ^[A-PR-Y0-9] {3} -[A-PR-Y0-9] {3} -[A-PR-Y0-9] {4} $ ' , re . IGNORECASE )
2005-10-20 12:20:52 +08:00
slug_re = re . compile ( r ' ^[- \ w]+$ ' )
2006-02-10 23:38:54 +08:00
url_re = re . compile ( r ' ^https?:// \ S+$ ' )
2005-07-13 09:25:57 +08:00
2005-11-21 19:10:19 +08:00
lazy_inter = lazy ( lambda a , b : str ( a ) % b , str )
2005-07-13 09:25:57 +08:00
class ValidationError ( Exception ) :
def __init__ ( self , message ) :
" ValidationError can be passed a string or a list. "
if isinstance ( message , list ) :
self . messages = message
else :
2005-11-21 18:41:54 +08:00
assert isinstance ( message , ( basestring , Promise ) ) , ( " %s should be a string " % repr ( message ) )
2005-07-13 09:25:57 +08:00
self . messages = [ message ]
def __str__ ( self ) :
# This is needed because, without a __str__(), printing an exception
# instance would result in this:
# AttributeError: ValidationError instance has no attribute 'args'
# See http://www.python.org/doc/current/tut/node10.html#handling
return str ( self . messages )
class CriticalValidationError ( Exception ) :
def __init__ ( self , message ) :
" ValidationError can be passed a string or a list. "
if isinstance ( message , list ) :
self . messages = message
else :
2005-11-21 18:41:54 +08:00
assert isinstance ( message , ( basestring , Promise ) ) , ( " ' %s ' should be a string " % message )
2005-07-13 09:25:57 +08:00
self . messages = [ message ]
def __str__ ( self ) :
return str ( self . messages )
def isAlphaNumeric ( field_data , all_data ) :
if not alnum_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " This value must contain only letters, numbers and underscores. " )
2005-07-13 09:25:57 +08:00
def isAlphaNumericURL ( field_data , all_data ) :
if not alnumurl_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " This value must contain only letters, numbers, underscores, dashes or slashes. " )
2005-10-20 12:20:52 +08:00
def isSlug ( field_data , all_data ) :
if not slug_re . search ( field_data ) :
2006-08-22 01:43:34 +08:00
raise ValidationError , gettext ( " This value must contain only letters, numbers, underscores or hyphens. " )
2005-07-13 09:25:57 +08:00
def isLowerCase ( field_data , all_data ) :
if field_data . lower ( ) != field_data :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Uppercase letters are not allowed here. " )
2005-07-13 09:25:57 +08:00
def isUpperCase ( field_data , all_data ) :
if field_data . upper ( ) != field_data :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Lowercase letters are not allowed here. " )
2005-07-13 09:25:57 +08:00
def isCommaSeparatedIntegerList ( field_data , all_data ) :
for supposed_int in field_data . split ( ' , ' ) :
try :
int ( supposed_int )
except ValueError :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Enter only digits separated by commas. " )
2005-07-13 09:25:57 +08:00
def isCommaSeparatedEmailList ( field_data , all_data ) :
"""
Checks that field_data is a string of e - mail addresses separated by commas .
Blank field_data values will not throw a validation error , and whitespace
is allowed around the commas .
"""
for supposed_email in field_data . split ( ' , ' ) :
try :
isValidEmail ( supposed_email . strip ( ) , ' ' )
except ValidationError :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Enter valid e-mail addresses separated by commas. " )
2005-07-13 09:25:57 +08:00
2005-09-23 10:02:15 +08:00
def isValidIPAddress4 ( field_data , all_data ) :
2005-11-07 01:55:39 +08:00
if not ip4_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Please enter a valid IP address. " )
2005-09-23 10:02:15 +08:00
2005-07-13 09:25:57 +08:00
def isNotEmpty ( field_data , all_data ) :
if field_data . strip ( ) == ' ' :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Empty values are not allowed here. " )
2005-07-13 09:25:57 +08:00
def isOnlyDigits ( field_data , all_data ) :
if not field_data . isdigit ( ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Non-numeric characters aren ' t allowed here. " )
2005-07-13 09:25:57 +08:00
def isNotOnlyDigits ( field_data , all_data ) :
if field_data . isdigit ( ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " This value can ' t be comprised solely of digits. " )
2005-07-13 09:25:57 +08:00
def isInteger ( field_data , all_data ) :
# This differs from isOnlyDigits because this accepts the negative sign
if not integer_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Enter a whole number. " )
2005-07-13 09:25:57 +08:00
def isOnlyLetters ( field_data , all_data ) :
if not field_data . isalpha ( ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Only alphabetical characters are allowed here. " )
2005-07-13 09:25:57 +08:00
2006-09-24 20:09:32 +08:00
def _isValidDate ( date_string ) :
"""
A helper function used by isValidANSIDate and isValidANSIDatetime to
check if the date is valid . The date string is assumed to already be in
YYYY - MM - DD format .
"""
from datetime import date
# Could use time.strptime here and catch errors, but datetime.date below
# produces much friendlier error messages.
year , month , day = map ( int , date_string . split ( ' - ' ) )
# This check is needed because strftime is used when saving the date
# value to the database, and strftime requires that the year be >=1900.
if year < 1900 :
raise ValidationError , gettext ( ' Year must be 1900 or later. ' )
try :
date ( year , month , day )
except ValueError , e :
2007-03-11 18:37:33 +08:00
msg = gettext ( ' Invalid date: %s ' ) % gettext ( str ( e ) )
raise ValidationError , msg
2006-09-24 20:09:32 +08:00
2005-07-13 09:25:57 +08:00
def isValidANSIDate ( field_data , all_data ) :
if not ansi_date_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( ' Enter a valid date in YYYY-MM-DD format. ' )
2006-09-24 20:09:32 +08:00
_isValidDate ( field_data )
2005-07-13 09:25:57 +08:00
def isValidANSITime ( field_data , all_data ) :
if not ansi_time_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( ' Enter a valid time in HH:MM format. ' )
2005-07-13 09:25:57 +08:00
def isValidANSIDatetime ( field_data , all_data ) :
if not ansi_datetime_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( ' Enter a valid date/time in YYYY-MM-DD HH:MM format. ' )
2006-09-24 20:09:32 +08:00
_isValidDate ( field_data . split ( ) [ 0 ] )
2005-07-13 09:25:57 +08:00
def isValidEmail ( field_data , all_data ) :
if not email_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( ' Enter a valid e-mail address. ' )
2005-07-13 09:25:57 +08:00
def isValidImage ( field_data , all_data ) :
"""
Checks that the file - upload field data contains a valid image ( GIF , JPG ,
PNG , possibly others - - whatever the Python Imaging Library supports ) .
"""
from PIL import Image
from cStringIO import StringIO
try :
2006-06-02 04:47:34 +08:00
content = field_data [ ' content ' ]
except TypeError :
raise ValidationError , gettext ( " No file was submitted. Check the encoding type on the form. " )
try :
Image . open ( StringIO ( content ) )
2005-07-13 09:25:57 +08:00
except IOError : # Python Imaging Library doesn't recognize it as an image
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Upload a valid image. The file you uploaded was either not an image or a corrupted image. " )
2005-07-13 09:25:57 +08:00
def isValidImageURL ( field_data , all_data ) :
uc = URLMimeTypeCheck ( ( ' image/jpeg ' , ' image/gif ' , ' image/png ' ) )
try :
uc ( field_data , all_data )
except URLMimeTypeCheck . InvalidContentType :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " The URL %s does not point to a valid image. " ) % field_data
2005-07-13 09:25:57 +08:00
def isValidPhone ( field_data , all_data ) :
if not phone_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( ' Phone numbers must be in XXX-XXX-XXXX format. " %s " is invalid. ' ) % field_data
2005-07-13 09:25:57 +08:00
def isValidQuicktimeVideoURL ( field_data , all_data ) :
" Checks that the given URL is a video that can be played by QuickTime (qt, mpeg) "
uc = URLMimeTypeCheck ( ( ' video/quicktime ' , ' video/mpeg ' , ) )
try :
uc ( field_data , all_data )
except URLMimeTypeCheck . InvalidContentType :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " The URL %s does not point to a valid QuickTime video. " ) % field_data
2005-07-13 09:25:57 +08:00
def isValidURL ( field_data , all_data ) :
if not url_re . search ( field_data ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " A valid URL is required. " )
2005-07-13 09:25:57 +08:00
2005-09-22 22:51:57 +08:00
def isValidHTML ( field_data , all_data ) :
import urllib , urllib2
try :
u = urllib2 . urlopen ( ' http://validator.w3.org/check ' , urllib . urlencode ( { ' fragment ' : field_data , ' output ' : ' xml ' } ) )
except :
# Validator or Internet connection is unavailable. Fail silently.
return
html_is_valid = ( u . headers . get ( ' x-w3c-validator-status ' , ' Invalid ' ) == ' Valid ' )
if html_is_valid :
return
from xml . dom . minidom import parseString
error_messages = [ e . firstChild . wholeText for e in parseString ( u . read ( ) ) . getElementsByTagName ( ' messages ' ) [ 0 ] . getElementsByTagName ( ' msg ' ) ]
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Valid HTML is required. Specific errors are: \n %s " ) % " \n " . join ( error_messages )
2005-09-22 22:51:57 +08:00
2005-07-13 09:25:57 +08:00
def isWellFormedXml ( field_data , all_data ) :
from xml . dom . minidom import parseString
try :
parseString ( field_data )
except Exception , e : # Naked except because we're not sure what will be thrown
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Badly formed XML: %s " ) % str ( e )
2005-07-13 09:25:57 +08:00
def isWellFormedXmlFragment ( field_data , all_data ) :
isWellFormedXml ( ' <root> %s </root> ' % field_data , all_data )
def isExistingURL ( field_data , all_data ) :
try :
2006-11-07 10:20:08 +08:00
headers = {
" Accept " : " text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 " ,
" Accept-Language " : " en-us,en;q=0.5 " ,
" Accept-Charset " : " ISO-8859-1,utf-8;q=0.7,*;q=0.7 " ,
" Connection " : " close " ,
" User-Agent " : settings . URL_VALIDATOR_USER_AGENT
}
req = urllib2 . Request ( field_data , None , headers )
u = urllib2 . urlopen ( req )
2005-07-13 09:25:57 +08:00
except ValueError :
2007-02-12 07:50:35 +08:00
raise ValidationError , _ ( " Invalid URL: %s " ) % field_data
2005-11-13 04:25:47 +08:00
except urllib2 . HTTPError , e :
# 401s are valid; they just mean authorization is required.
2006-11-07 10:20:08 +08:00
# 301 and 302 are redirects; they just mean look somewhere else.
if str ( e . code ) not in ( ' 401 ' , ' 301 ' , ' 302 ' ) :
2007-02-12 07:50:35 +08:00
raise ValidationError , _ ( " The URL %s is a broken link. " ) % field_data
2005-11-13 04:25:47 +08:00
except : # urllib2.URLError, httplib.InvalidURL, etc.
2007-02-12 07:50:35 +08:00
raise ValidationError , _ ( " The URL %s is a broken link. " ) % field_data
2006-11-07 10:20:08 +08:00
2005-07-13 09:25:57 +08:00
def isValidUSState ( field_data , all_data ) :
" Checks that the given string is a valid two-letter U.S. state abbreviation "
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 ' ]
if field_data . upper ( ) not in states :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Enter a valid U.S. state abbreviation. " )
2005-07-13 09:25:57 +08:00
def hasNoProfanities ( field_data , all_data ) :
"""
Checks that the given string has no profanities in it . This does a simple
check for whether each profanity exists within the string , so ' fuck ' will
catch ' motherfucker ' as well . Raises a ValidationError such as :
Watch your mouth ! The words " f--k " and " s--t " are not allowed here .
"""
field_data = field_data . lower ( ) # normalize
2006-09-26 01:38:35 +08:00
words_seen = [ w for w in settings . PROFANITIES_LIST if w in field_data ]
2005-07-13 09:25:57 +08:00
if words_seen :
from django . utils . text import get_text_list
plural = len ( words_seen ) > 1
2005-11-04 12:59:46 +08:00
raise ValidationError , ngettext ( " Watch your mouth! The word %s is not allowed here. " ,
" Watch your mouth! The words %s are not allowed here. " , plural ) % \
get_text_list ( [ ' " %s %s %s " ' % ( i [ 0 ] , ' - ' * ( len ( i ) - 2 ) , i [ - 1 ] ) for i in words_seen ] , ' and ' )
2005-07-13 09:25:57 +08:00
2006-06-08 13:00:13 +08:00
class AlwaysMatchesOtherField ( object ) :
2005-07-13 09:25:57 +08:00
def __init__ ( self , other_field_name , error_message = None ) :
self . other = other_field_name
2005-11-21 19:10:19 +08:00
self . error_message = error_message or lazy_inter ( gettext_lazy ( " This field must match the ' %s ' field. " ) , self . other )
2005-07-13 09:25:57 +08:00
self . always_test = True
def __call__ ( self , field_data , all_data ) :
if field_data != all_data [ self . other ] :
raise ValidationError , self . error_message
2006-06-08 13:00:13 +08:00
class ValidateIfOtherFieldEquals ( object ) :
2005-08-04 23:12:11 +08:00
def __init__ ( self , other_field , other_value , validator_list ) :
self . other_field , self . other_value = other_field , other_value
self . validator_list = validator_list
self . always_test = True
def __call__ ( self , field_data , all_data ) :
if all_data . has_key ( self . other_field ) and all_data [ self . other_field ] == self . other_value :
for v in self . validator_list :
v ( field_data , all_data )
2006-06-08 13:00:13 +08:00
class RequiredIfOtherFieldNotGiven ( object ) :
2005-11-04 12:59:46 +08:00
def __init__ ( self , other_field_name , error_message = gettext_lazy ( " Please enter something for at least one field. " ) ) :
2005-08-04 23:05:55 +08:00
self . other , self . error_message = other_field_name , error_message
2005-07-13 09:25:57 +08:00
self . always_test = True
def __call__ ( self , field_data , all_data ) :
if not all_data . get ( self . other , False ) and not field_data :
raise ValidationError , self . error_message
2006-06-08 13:00:13 +08:00
class RequiredIfOtherFieldsGiven ( object ) :
2005-11-04 12:59:46 +08:00
def __init__ ( self , other_field_names , error_message = gettext_lazy ( " Please enter both fields or leave them both empty. " ) ) :
2005-08-04 23:05:55 +08:00
self . other , self . error_message = other_field_names , error_message
2005-07-13 09:25:57 +08:00
self . always_test = True
def __call__ ( self , field_data , all_data ) :
for field in self . other :
2005-08-05 23:41:58 +08:00
if all_data . get ( field , False ) and not field_data :
2005-07-13 09:25:57 +08:00
raise ValidationError , self . error_message
2005-08-04 23:05:15 +08:00
class RequiredIfOtherFieldGiven ( RequiredIfOtherFieldsGiven ) :
" Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list. "
2005-11-04 12:59:46 +08:00
def __init__ ( self , other_field_name , error_message = gettext_lazy ( " Please enter both fields or leave them both empty. " ) ) :
2005-08-04 23:05:15 +08:00
RequiredIfOtherFieldsGiven . __init__ ( self , [ other_field_name ] , error_message )
2006-06-08 13:00:13 +08:00
class RequiredIfOtherFieldEquals ( object ) :
2007-02-26 01:16:38 +08:00
def __init__ ( self , other_field , other_value , error_message = None , other_label = None ) :
2005-07-13 09:25:57 +08:00
self . other_field = other_field
self . other_value = other_value
2007-02-26 01:16:38 +08:00
other_label = other_label or other_value
2005-11-21 19:10:19 +08:00
self . error_message = error_message or lazy_inter ( gettext_lazy ( " This field must be given if %(field)s is %(value)s " ) , {
2007-02-26 01:16:38 +08:00
' field ' : other_field , ' value ' : other_label } )
2005-07-13 09:25:57 +08:00
self . always_test = True
def __call__ ( self , field_data , all_data ) :
if all_data . has_key ( self . other_field ) and all_data [ self . other_field ] == self . other_value and not field_data :
raise ValidationError ( self . error_message )
2006-06-08 13:00:13 +08:00
class RequiredIfOtherFieldDoesNotEqual ( object ) :
2007-02-26 01:16:38 +08:00
def __init__ ( self , other_field , other_value , other_label = None , error_message = None ) :
2005-07-13 09:25:57 +08:00
self . other_field = other_field
self . other_value = other_value
2007-02-26 01:16:38 +08:00
other_label = other_label or other_value
2005-11-21 19:10:19 +08:00
self . error_message = error_message or lazy_inter ( gettext_lazy ( " This field must be given if %(field)s is not %(value)s " ) , {
2007-02-26 01:16:38 +08:00
' field ' : other_field , ' value ' : other_label } )
2005-07-13 09:25:57 +08:00
self . always_test = True
def __call__ ( self , field_data , all_data ) :
if all_data . has_key ( self . other_field ) and all_data [ self . other_field ] != self . other_value and not field_data :
raise ValidationError ( self . error_message )
2006-06-08 13:00:13 +08:00
class IsLessThanOtherField ( object ) :
2005-07-13 09:25:57 +08:00
def __init__ ( self , other_field_name , error_message ) :
self . other , self . error_message = other_field_name , error_message
def __call__ ( self , field_data , all_data ) :
if field_data > all_data [ self . other ] :
raise ValidationError , self . error_message
2006-06-08 13:00:13 +08:00
class UniqueAmongstFieldsWithPrefix ( object ) :
2005-07-13 09:25:57 +08:00
def __init__ ( self , field_name , prefix , error_message ) :
self . field_name , self . prefix = field_name , prefix
2005-11-04 12:59:46 +08:00
self . error_message = error_message or gettext_lazy ( " Duplicate values are not allowed. " )
2005-07-13 09:25:57 +08:00
def __call__ ( self , field_data , all_data ) :
for field_name , value in all_data . items ( ) :
if field_name != self . field_name and value == field_data :
raise ValidationError , self . error_message
2006-11-07 12:29:07 +08:00
class NumberIsInRange ( object ) :
"""
Validator that tests if a value is in a range ( inclusive ) .
"""
def __init__ ( self , lower = None , upper = None , error_message = ' ' ) :
self . lower , self . upper = lower , upper
if not error_message :
if lower and upper :
2007-03-12 16:35:15 +08:00
self . error_message = gettext ( " This value must be between %(lower)s and %(upper)s . " ) % { ' lower ' : lower , ' upper ' : upper }
2006-11-07 12:29:07 +08:00
elif lower :
self . error_message = gettext ( " This value must be at least %s . " ) % lower
elif upper :
self . error_message = gettext ( " This value must be no more than %s . " ) % upper
else :
self . error_message = error_message
def __call__ ( self , field_data , all_data ) :
# Try to make the value numeric. If this fails, we assume another
# validator will catch the problem.
try :
val = float ( field_data )
except ValueError :
return
# Now validate
if self . lower and self . upper and ( val < self . lower or val > self . upper ) :
raise ValidationError ( self . error_message )
elif self . lower and val < self . lower :
raise ValidationError ( self . error_message )
elif self . upper and val > self . upper :
raise ValidationError ( self . error_message )
2006-06-08 13:00:13 +08:00
class IsAPowerOf ( object ) :
2005-07-13 09:25:57 +08:00
"""
>> > v = IsAPowerOf ( 2 )
>> > v ( 4 , None )
>> > v ( 8 , None )
>> > v ( 16 , None )
>> > v ( 17 , None )
django . core . validators . ValidationError : [ ' This value must be a power of 2. ' ]
"""
def __init__ ( self , power_of ) :
self . power_of = power_of
def __call__ ( self , field_data , all_data ) :
from math import log
val = log ( int ( field_data ) ) / log ( self . power_of )
if val != int ( val ) :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " This value must be a power of %s . " ) % self . power_of
2005-07-13 09:25:57 +08:00
2006-06-08 13:00:13 +08:00
class IsValidFloat ( object ) :
2005-07-13 09:25:57 +08:00
def __init__ ( self , max_digits , decimal_places ) :
self . max_digits , self . decimal_places = max_digits , decimal_places
def __call__ ( self , field_data , all_data ) :
data = str ( field_data )
try :
float ( data )
except ValueError :
2006-05-02 09:31:56 +08:00
raise ValidationError , gettext ( " Please enter a valid decimal number. " )
2006-09-22 11:42:31 +08:00
# Negative floats require more space to input.
max_allowed_length = data . startswith ( ' - ' ) and ( self . max_digits + 2 ) or ( self . max_digits + 1 )
if len ( data ) > max_allowed_length :
2006-05-02 09:31:56 +08:00
raise ValidationError , ngettext ( " Please enter a valid decimal number with at most %s total digit. " ,
2005-11-04 12:59:46 +08:00
" Please enter a valid decimal number with at most %s total digits. " , self . max_digits ) % self . max_digits
2006-09-26 09:57:26 +08:00
if ( not ' . ' in data and len ( data ) > ( max_allowed_length - self . decimal_places - 1 ) ) or ( ' . ' in data and len ( data ) > ( max_allowed_length - ( self . decimal_places - len ( data . split ( ' . ' ) [ 1 ] ) ) ) ) :
2006-06-19 09:09:00 +08:00
raise ValidationError , ngettext ( " Please enter a valid decimal number with a whole part of at most %s digit. " ,
" 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 )
2005-07-13 09:25:57 +08:00
if ' . ' in data and len ( data . split ( ' . ' ) [ 1 ] ) > self . decimal_places :
2005-11-04 12:59:46 +08:00
raise ValidationError , ngettext ( " Please enter a valid decimal number with at most %s decimal place. " ,
" Please enter a valid decimal number with at most %s decimal places. " , self . decimal_places ) % self . decimal_places
2005-07-13 09:25:57 +08:00
2006-06-08 13:00:13 +08:00
class HasAllowableSize ( object ) :
2005-07-13 09:25:57 +08:00
"""
Checks that the file - upload field data is a certain size . min_size and
max_size are measurements in bytes .
"""
def __init__ ( self , min_size = None , max_size = None , min_error_message = None , max_error_message = None ) :
self . min_size , self . max_size = min_size , max_size
2005-11-21 19:10:19 +08:00
self . min_error_message = min_error_message or lazy_inter ( gettext_lazy ( " Make sure your uploaded file is at least %s bytes big. " ) , min_size )
2005-12-07 13:13:18 +08:00
self . max_error_message = max_error_message or lazy_inter ( gettext_lazy ( " Make sure your uploaded file is at most %s bytes big. " ) , max_size )
2005-07-13 09:25:57 +08:00
def __call__ ( self , field_data , all_data ) :
2006-06-02 04:47:34 +08:00
try :
content = field_data [ ' content ' ]
except TypeError :
raise ValidationError , gettext_lazy ( " No file was submitted. Check the encoding type on the form. " )
if self . min_size is not None and len ( content ) < self . min_size :
2005-07-13 09:25:57 +08:00
raise ValidationError , self . min_error_message
2006-06-02 04:47:34 +08:00
if self . max_size is not None and len ( content ) > self . max_size :
2005-07-13 09:25:57 +08:00
raise ValidationError , self . max_error_message
2006-06-08 13:00:13 +08:00
class MatchesRegularExpression ( object ) :
2005-08-04 22:42:01 +08:00
"""
Checks that the field matches the given regular - expression . The regex
should be in string format , not already compiled .
"""
2005-11-04 12:59:46 +08:00
def __init__ ( self , regexp , error_message = gettext_lazy ( " The format for this field is wrong. " ) ) :
2005-08-04 22:42:01 +08:00
self . regexp = re . compile ( regexp )
self . error_message = error_message
def __call__ ( self , field_data , all_data ) :
2006-03-29 05:39:15 +08:00
if not self . regexp . search ( field_data ) :
2005-08-04 22:57:20 +08:00
raise ValidationError ( self . error_message )
2006-06-08 13:00:13 +08:00
class AnyValidator ( object ) :
2005-08-04 22:57:20 +08:00
"""
This validator tries all given validators . If any one of them succeeds ,
validation passes . If none of them succeeds , the given message is thrown
as a validation error . The message is rather unspecific , so it ' s best to
specify one on instantiation .
"""
2006-06-03 21:37:34 +08:00
def __init__ ( self , validator_list = None , error_message = gettext_lazy ( " This field is invalid. " ) ) :
if validator_list is None : validator_list = [ ]
2005-08-04 22:57:20 +08:00
self . validator_list = validator_list
self . error_message = error_message
for v in validator_list :
if hasattr ( v , ' always_test ' ) :
self . always_test = True
def __call__ ( self , field_data , all_data ) :
for v in self . validator_list :
try :
v ( field_data , all_data )
return
except ValidationError , e :
pass
raise ValidationError ( self . error_message )
2005-08-04 22:42:01 +08:00
2006-06-08 13:00:13 +08:00
class URLMimeTypeCheck ( object ) :
2005-07-13 09:25:57 +08:00
" Checks that the provided URL points to a document with a listed mime type "
class CouldNotRetrieve ( ValidationError ) :
pass
class InvalidContentType ( ValidationError ) :
pass
def __init__ ( self , mime_type_list ) :
self . mime_type_list = mime_type_list
def __call__ ( self , field_data , all_data ) :
import urllib2
try :
isValidURL ( field_data , all_data )
except ValidationError :
raise
try :
info = urllib2 . urlopen ( field_data ) . info ( )
except ( urllib2 . HTTPError , urllib2 . URLError ) :
2006-05-02 09:31:56 +08:00
raise URLMimeTypeCheck . CouldNotRetrieve , gettext ( " Could not retrieve anything from %s . " ) % field_data
2005-07-13 09:25:57 +08:00
content_type = info [ ' content-type ' ]
if content_type not in self . mime_type_list :
2006-05-02 09:31:56 +08:00
raise URLMimeTypeCheck . InvalidContentType , gettext ( " The URL %(url)s returned the invalid Content-Type header ' %(contenttype)s ' . " ) % {
2005-11-04 12:59:46 +08:00
' url ' : field_data , ' contenttype ' : content_type }
2005-07-13 09:25:57 +08:00
2006-06-08 13:00:13 +08:00
class RelaxNGCompact ( object ) :
2005-07-13 09:25:57 +08:00
" Validate against a Relax NG compact schema "
def __init__ ( self , schema_path , additional_root_element = None ) :
self . schema_path = schema_path
self . additional_root_element = additional_root_element
def __call__ ( self , field_data , all_data ) :
import os , tempfile
if self . additional_root_element :
field_data = ' < %(are)s > %(data)s \n </ %(are)s > ' % {
' are ' : self . additional_root_element ,
' data ' : field_data
}
filename = tempfile . mktemp ( ) # Insecure, but nothing else worked
fp = open ( filename , ' w ' )
fp . write ( field_data )
fp . close ( )
2006-05-02 09:31:56 +08:00
if not os . path . exists ( settings . JING_PATH ) :
raise Exception , " %s not found! " % settings . JING_PATH
p = os . popen ( ' %s -c %s %s ' % ( settings . JING_PATH , self . schema_path , filename ) )
2005-07-13 09:25:57 +08:00
errors = [ line . strip ( ) for line in p . readlines ( ) ]
p . close ( )
os . unlink ( filename )
display_errors = [ ]
lines = field_data . split ( ' \n ' )
for error in errors :
2005-11-22 11:39:39 +08:00
ignored , line , level , message = error . split ( ' : ' , 3 )
2005-07-13 09:25:57 +08:00
# Scrape the Jing error messages to reword them more nicely.
m = re . search ( r ' Expected " (.*?) " to terminate element starting on line ( \ d+) ' , message )
if m :
2007-02-12 07:50:35 +08:00
display_errors . append ( _ ( ' Please close the unclosed %(tag)s tag from line %(line)s . (Line starts with " %(start)s " .) ' ) % \
2005-11-04 12:59:46 +08:00
{ ' tag ' : m . group ( 1 ) . replace ( ' / ' , ' ' ) , ' line ' : m . group ( 2 ) , ' start ' : lines [ int ( m . group ( 2 ) ) - 1 ] [ : 30 ] } )
2005-07-13 09:25:57 +08:00
continue
if message . strip ( ) == ' text not allowed here ' :
2007-02-12 07:50:35 +08:00
display_errors . append ( _ ( ' Some text starting on line %(line)s is not allowed in that context. (Line starts with " %(start)s " .) ' ) % \
2005-11-04 12:59:46 +08:00
{ ' line ' : line , ' start ' : lines [ int ( line ) - 1 ] [ : 30 ] } )
2005-07-13 09:25:57 +08:00
continue
m = re . search ( r ' \ s*attribute " (.*?) " not allowed at this point; ignored ' , message )
if m :
2007-02-12 07:50:35 +08:00
display_errors . append ( _ ( ' " %(attr)s " on line %(line)s is an invalid attribute. (Line starts with " %(start)s " .) ' ) % \
2005-11-04 12:59:46 +08:00
{ ' attr ' : m . group ( 1 ) , ' line ' : line , ' start ' : lines [ int ( line ) - 1 ] [ : 30 ] } )
2005-07-13 09:25:57 +08:00
continue
m = re . search ( r ' \ s*unknown element " (.*?) " ' , message )
if m :
2007-02-12 07:50:35 +08:00
display_errors . append ( _ ( ' " < %(tag)s > " on line %(line)s is an invalid tag. (Line starts with " %(start)s " .) ' ) % \
2005-11-04 12:59:46 +08:00
{ ' tag ' : m . group ( 1 ) , ' line ' : line , ' start ' : lines [ int ( line ) - 1 ] [ : 30 ] } )
2005-07-13 09:25:57 +08:00
continue
if message . strip ( ) == ' required attributes missing ' :
2007-02-12 07:50:35 +08:00
display_errors . append ( _ ( ' A tag on line %(line)s is missing one or more required attributes. (Line starts with " %(start)s " .) ' ) % \
2005-11-04 12:59:46 +08:00
{ ' line ' : line , ' start ' : lines [ int ( line ) - 1 ] [ : 30 ] } )
2005-07-13 09:25:57 +08:00
continue
m = re . search ( r ' \ s*bad value for attribute " (.*?) " ' , message )
if m :
2007-02-12 07:50:35 +08:00
display_errors . append ( _ ( ' The " %(attr)s " attribute on line %(line)s has an invalid value. (Line starts with " %(start)s " .) ' ) % \
2005-11-04 12:59:46 +08:00
{ ' attr ' : m . group ( 1 ) , ' line ' : line , ' start ' : lines [ int ( line ) - 1 ] [ : 30 ] } )
2005-07-13 09:25:57 +08:00
continue
# Failing all those checks, use the default error message.
display_error = ' Line %s : %s [ %s ] ' % ( line , message , level . strip ( ) )
display_errors . append ( display_error )
if len ( display_errors ) > 0 :
raise ValidationError , display_errors