Permalink
Fetching contributors…
Cannot retrieve contributors at this time
200 lines (155 sloc) 6.9 KB
import warnings
from tg._compat import unicode_text
from .i18n import _formencode_gettext, lazy_ugettext
try:
from tw2.core import ValidationError as _Tw2ValidationError
except ImportError: #pragma: no cover
class _Tw2ValidationError(Exception):
"""ToscaWidgets2 Validation Error"""
try:
from formencode.api import Invalid as _FormEncodeValidationError
from formencode.api import Validator as _FormEncodeValidator
from formencode import Schema as _FormEncodeSchema
except ImportError: #pragma: no cover
class _FormEncodeValidationError(Exception):
"""FormEncode Invalid"""
class _FormEncodeValidator(object):
"""FormEncode Validator"""
class _FormEncodeSchema(object):
"""FormEncode Schema"""
class _ValidationStatus(object):
"""Current request parameters validation status.
Keeps track of currently validated values, errors and
the ValidationIntent that caused the validation process.
"""
__slots__ = ('errors', 'values', 'exception', 'intent')
def __init__(self, errors=None, values=None, exception=None, intent=None):
self.errors = errors or {}
self.values = values or {}
self.exception = exception
self.intent = intent
@property
def error_handler(self):
if self.intent is None:
return None
return self.intent.error_handler
def __getitem__(self, item):
warnings.warn("Accessing validation status properties with [] syntax is deprecated. "
" Please use dot notation instead", DeprecationWarning, stacklevel=2)
try:
return getattr(self, item)
except AttributeError:
raise KeyError
class _ValidationIntent(object):
"""Details of validation intention.
Describes how a validation should happen and how
errors should be handled. It also performs the
validation itself on the parameters for a given
controller method.
"""
def __init__(self, validators, error_handler):
self.validators = validators
self.error_handler = error_handler
def check(self, method, params):
validators = self.validators
if not validators:
return params
# An object used by FormEncode to get translator function
formencode_state = type('state', (), {'_': staticmethod(_formencode_gettext)})
validated_params = {}
# The validator may be a dictionary, a FormEncode Schema object, or any
# object with a "validate" method.
if isinstance(validators, dict):
# TG developers can pass in a dict of param names and FormEncode
# validators. They are applied one by one and builds up a new set
# of validated params.
errors = {}
for field, validator in validators.items():
try:
if isinstance(validator, _FormEncodeValidator):
validated_params[field] = validator.to_python(params.get(field), formencode_state)
else:
validated_params[field] = validator.to_python(params.get(field))
# catch individual validation errors into the errors dictionary
except validation_errors as inv:
errors[field] = inv
# Parameters that don't have validators are returned verbatim
for param, param_value in params.items():
if param not in validated_params:
validated_params[param] = param_value
# If there are errors, create a compound validation error based on
# the errors dictionary, and raise it as an exception
if errors:
raise TGValidationError(TGValidationError.make_compound_message(errors),
value=params,
error_dict=errors)
elif isinstance(validators, _FormEncodeSchema):
# A FormEncode Schema object - to_python converts the incoming
# parameters to sanitized Python values
validated_params = validators.to_python(params, formencode_state)
elif hasattr(validators, 'validate') and getattr(self, 'needs_controller', False):
# An object with a "validate" method - call it with the parameters
validated_params = validators.validate(method, params, formencode_state)
elif hasattr(validators, 'validate'):
# An object with a "validate" method - call it with the parameters
validated_params = validators.validate(params, formencode_state)
return validated_params
def _navigate_tw2form_children(w):
if getattr(w, 'compound_key', None):
# If we have a compound_key it's a leaf widget with form values
yield w
else:
child = getattr(w, 'child', None)
if child:
# Widgets with "child" don't have children, but their child has
w = child
for c in getattr(w, 'children', []):
for cc in _navigate_tw2form_children(c):
yield cc
class TGValidationError(Exception):
"""Invalid data was encountered during validation.
The constructor can be passed a short message with
the reason of the failed validation.
"""
def __init__(self, msg, value=None, error_dict=None):
super(TGValidationError, self).__init__(msg)
self.msg = msg
self.value = value
self.error_dict = error_dict
@classmethod
def make_compound_message(cls, error_dict):
return unicode_text('\n').join(
unicode_text("%s: %s") % errorinfo for errorinfo in error_dict.items()
)
def __str__(self):
return str(self.msg)
def __unicode__(self):
return unicode_text(self.msg)
class Convert(object):
"""Applies a conversion function as a validator.
This is meant to implement simple validation mechanism.
Any callable can be used for ``func`` as far as it accepts an argument and
returns the converted object. In case of exceptions the validation
is considered failed and the ``msg`` parameter is displayed as
an error.
A ``default`` value can be provided for values that are missing
(evaluate to false) which will be used in place of the missing value.
Example::
@expose()
@validate({
'num': Convert(int, 'Must be a number')
}, error_handler=insert_number)
def post_pow2(self, num):
return str(num*num)
"""
def __init__(self, func, msg=lazy_ugettext('Invalid'), default=None):
self._func = func
self._msg = msg
self._default = default
def to_python(self, value, state=None):
value = value or self._default
try:
return self._func(value)
except:
raise TGValidationError(self._msg, value)
validation_errors = (_Tw2ValidationError, _FormEncodeValidationError, TGValidationError)