witten
/
luminotes
Archived
1
0
Fork 0
This repository has been archived on 2023-12-16. You can view files and clone it, but cannot push or open issues or pull requests.
luminotes/controller/Validate.py

293 lines
9.3 KiB
Python

import cherrypy
import re
from cgi import escape
from Html_cleaner import Html_cleaner
class Validation_error( Exception ):
"""
An exception raised when form validation fails for some reason.
"""
MESSAGE_MAP = {
int: u"can only contain digits",
}
def __init__( self, name, value, value_type, message = None ):
Exception.__init__( self )
self.__name = name
self.__value = value
self.__value_type = value_type
if message is None:
# if the value's type has a message member, use that. otherwise, look up the type in a map
if hasattr( value_type, u"message" ):
self.__message = value_type.message
else:
self.__message = self.MESSAGE_MAP.get( value_type, u"is invalid" )
else:
self.__message = message
def __str__( self ):
return self.__message
def to_dict( self ):
return dict(
error = u"The %s %s." % ( self.__name.replace( u"_", " " ), self.__message ),
name = self.__name,
value = self.__value,
)
name = property( lambda self: self.__name )
value = property( lambda self: self.__value )
value_type = property( lambda self: self.__value_type )
message = property( lambda self: self.__message )
class Valid_string( object ):
"""
Validator for a string of certain minimum and maximum lengths.
"""
moron_map = {
u"\xa0": u" ",
u"\xa9": u"(c)",
u"\xae": u"(r)",
u"\xb7": u"*",
u"\u2002": u" ",
u"\u2003": u" ",
u"\u2009": u" ",
u"\u2010": u"-",
u"\u2011": u"-",
u"\u2013": u"-",
u"\u2014": u"--",
u"\u2015": u"--",
u"\u2016": u"--",
u"\u2017": u"||",
u"\u2018": u"'",
u"\u2019": u"'",
u"\u201a": u",",
u"\u201b": u"'",
u"\u201c": u'"',
u"\u201d": u'"',
u"\u201e": u",,",
u"\u201f": u'"',
u"\u2022": u"*",
u"\u2023": u"*",
u"\u2024": u".",
u"\u2025": u"..",
u"\u2026": u"...",
u"\u2027": u".",
u"\u2122": u"(tm)",
}
def __init__( self, min = None, max = None, escape_html = True, require_link_target = False ):
self.min = min
self.max = max
self.escape_html = escape_html
self.require_link_target = require_link_target
self.message = None
def __call__( self, value ):
value = self.__demoronize( value.strip() )
if self.min is not None and len( value ) < self.min:
if self.min == 1:
self.message = u"is missing"
else:
self.message = u"must be at least %s characters long" % self.min
raise ValueError()
# either escape all html completely or just clean up the html, stripping out everything that's
# not on a tag/attribute whitelist
if self.escape_html:
value = escape( value, quote = True )
else:
cleaner = Html_cleaner( self.require_link_target )
value = cleaner.strip( value )
# check for max length after cleaning html, as cleaning can reduce the html's size
if self.max is not None and len( value ) > self.max:
self.message = u"must be no longer than %s characters. Please try removing some of the text" % self.max
raise ValueError()
return value
def __demoronize( self, value ):
"""
Convert stupid Microsoft unicode symbols to saner, cross-platform equivalents.
"""
try:
for ( moron_symbol, replacement ) in self.moron_map.items():
value = value.replace( moron_symbol, replacement )
except:
import traceback
traceback.print_exc()
raise
return value
class Valid_bool( object ):
"""
Validator for a boolean value.
"""
def __init__( self, none_okay = False ):
self.__none_okay = none_okay
def __call__( self, value ):
value = value.strip()
if self.__none_okay and value in ( None, "None", "" ): return None
if value in ( u"True", u"true" ): return True
if value in ( u"False", u"false" ): return False
raise ValueError()
class Valid_int( object ):
"""
Validator for an integer value.
"""
def __init__( self, min = None, max = None, none_okay = False ):
self.min = min
self.max = max
self.message = None
self.__none_okay = none_okay
def __call__( self, value ):
if self.__none_okay and value in ( None, "None", "" ): return None
value = int( value )
if self.min is not None and value < self.min:
self.message = "is too small"
raise ValueError()
if self.max is not None and value > self.max:
self.message = "is too large"
raise ValueError()
return value
class Valid_friendly_id( object ):
FRIENDLY_ID_PATTERN = re.compile( "^[a-zA-Z0-9\-]+$" )
def __call__( self, value ):
if self.FRIENDLY_ID_PATTERN.search( value ):
return value
raise ValueError()
def validate( **expected ):
"""
validate() can be used to require that the arguments of the decorated method successfully pass
through particular validators. The validate() method itself is evaluated where it is used as a
decorator, which just returns decorate() to be used as the actual decorator.
Example usage:
@validate(
foo = Valid_string( min = 5, max = 10 ),
bar = int
)
def method( self, foo, bar ): pass
Note that validate() currently only works for instance methods (methods that take self as the
first argument). Also note that you can use multiple validators for a single argument.
Example usage:
@validate(
foo = Valid_string( min = 5, max = 10 ),
bar = ( int, valid_bar )
)
def method( self, foo, bar ): pass
"""
def decorate( function ):
"""
When the method being decorated is invoked, its decorator gets invoked instead and is supposed
to return a new function to use in place of the method being decorated (or a modified version
of that function). In this case, the decorator is our decorate() function, and the function it
returns is the check() function. decorate()'s first argument is the method being decorated.
"""
def check( *args, **kwargs ):
"""
check() pretends that it's the method being decorated. It takes the same arguments and then
invokes the actual method being decorated, passing in those arguments, but only after first
validating all of those arguments to that function. If validation fails, a Validation_error
is raised. Note that in Python, keyword argument names have to be str, not unicode.
"""
args = list( args )
args_index = 1 # skip the self argument
# make sure all kwarg names are lowercase and don't have dashes
for ( kwarg_name, value ) in kwargs.items():
new_kwarg_name = kwarg_name.replace( "-", "_" ).lower()
if new_kwarg_name != kwarg_name:
del( kwargs[ kwarg_name ] )
kwargs[ new_kwarg_name ] = value
# determine the expected argument names from the decorated function itself
code = function.func_code
expected_names = code.co_varnames[ : code.co_argcount ]
# validate each of the expected arguments
for expected_name in expected_names:
if expected_name == u"self": continue
expected_type = expected.get( expected_name )
# look for expected_name in kwargs and store the validated value there
if expected_name in kwargs:
value = kwargs.get( expected_name )
# if there's a tuple of multiple validators for this expected_name, use all of them
if isinstance( expected_type, tuple ):
for validator in expected_type:
try:
value = validator( value )
except ( ValueError, TypeError ):
raise Validation_error( expected_name, value, validator )
kwargs[ str( expected_name ) ] = value
# otherwise, there's just a single validator
else:
try:
kwargs[ str( expected_name ) ] = expected_type( value )
except ( ValueError, TypeError ):
raise Validation_error( expected_name, value, expected_type )
continue
# expected_name wasn't found in kwargs, so look for it in args. if it's not there either,
# raise unless there's a default value for the argument in the decorated function
if args_index >= len( args ):
if function.func_defaults and args_index >= len( args ) - len( function.func_defaults ):
continue
raise Validation_error( expected_name, None, expected_type, message = u"is required" )
value = args[ args_index ]
# if there's a tuple of multiple validators for this expected_name, use all of them
if isinstance( expected_type, tuple ):
for validator in expected_type:
try:
value = validator( value )
except ( ValueError, TypeError ):
raise Validation_error( expected_name, value, validator )
args[ args_index ] = value
# otherwise, there's just a single validator
else:
try:
args[ args_index ] = expected_type( value )
except ( ValueError, TypeError ):
raise Validation_error( expected_name, value, expected_type )
args_index += 1
# if there are any unexpected arguments, raise
for ( arg_name, arg_value ) in kwargs.items():
if not arg_name in expected_names:
raise Validation_error( arg_name, arg_value, None, message = u"is an unknown argument" )
return function( *args, **kwargs )
return check
return decorate