1import warnings 2 3from tg._compat import unicode_text 4 5from .i18n import _formencode_gettext, lazy_ugettext 6 7try: 8 from tw2.core import ValidationError as _Tw2ValidationError 9except ImportError: #pragma: no cover 10 class _Tw2ValidationError(Exception): 11 """ToscaWidgets2 Validation Error""" 12 13try: 14 from formencode.api import Invalid as _FormEncodeValidationError 15 from formencode.api import Validator as _FormEncodeValidator 16 from formencode import Schema as _FormEncodeSchema 17except ImportError: #pragma: no cover 18 class _FormEncodeValidationError(Exception): 19 """FormEncode Invalid""" 20 class _FormEncodeValidator(object): 21 """FormEncode Validator""" 22 class _FormEncodeSchema(object): 23 """FormEncode Schema""" 24 25 26class _ValidationStatus(object): 27 """Current request parameters validation status. 28 29 Keeps track of currently validated values, errors and 30 the ValidationIntent that caused the validation process. 31 """ 32 __slots__ = ('errors', 'values', 'exception', 'intent') 33 34 def __init__(self, errors=None, values=None, exception=None, intent=None): 35 self.errors = errors or {} 36 self.values = values or {} 37 self.exception = exception 38 self.intent = intent 39 40 @property 41 def error_handler(self): 42 if self.intent is None: 43 return None 44 return self.intent.error_handler 45 46 @property 47 def chain_validation(self): 48 if self.intent is None: 49 return False 50 return self.intent.chain_validation 51 52 def __getitem__(self, item): 53 warnings.warn("Accessing validation status properties with [] syntax is deprecated. " 54 " Please use dot notation instead", DeprecationWarning, stacklevel=2) 55 try: 56 return getattr(self, item) 57 except AttributeError: 58 raise KeyError 59 60 61class _ValidationIntent(object): 62 """Details of validation intention. 63 64 Describes how a validation should happen and how 65 errors should be handled. It also performs the 66 validation itself on the parameters for a given 67 controller method. 68 """ 69 def __init__(self, validators, error_handler, chain_validation): 70 self.validators = validators 71 self.error_handler = error_handler 72 self.chain_validation = chain_validation 73 74 def check(self, method, params): 75 validators = self.validators 76 if not validators: 77 return params 78 79 # An object used by FormEncode to get translator function 80 formencode_state = type('state', (), {'_': staticmethod(_formencode_gettext)}) 81 validated_params = {} 82 83 # The validator may be a dictionary, a FormEncode Schema object, or any 84 # object with a "validate" method. 85 if isinstance(validators, dict): 86 # TG developers can pass in a dict of param names and FormEncode 87 # validators. They are applied one by one and builds up a new set 88 # of validated params. 89 90 errors = {} 91 for field, validator in validators.items(): 92 try: 93 if isinstance(validator, _FormEncodeValidator): 94 validated_params[field] = validator.to_python(params.get(field), formencode_state) 95 else: 96 validated_params[field] = validator.to_python(params.get(field)) 97 # catch individual validation errors into the errors dictionary 98 except validation_errors as inv: 99 errors[field] = inv 100 101 # Parameters that don't have validators are returned verbatim 102 for param, param_value in params.items(): 103 if param not in validated_params: 104 validated_params[param] = param_value 105 106 # If there are errors, create a compound validation error based on 107 # the errors dictionary, and raise it as an exception 108 if errors: 109 raise TGValidationError(TGValidationError.make_compound_message(errors), 110 value=params, 111 error_dict=errors) 112 113 elif isinstance(validators, _FormEncodeSchema): 114 # A FormEncode Schema object - to_python converts the incoming 115 # parameters to sanitized Python values 116 validated_params = validators.to_python(params, formencode_state) 117 118 elif hasattr(validators, 'validate') and getattr(self, 'needs_controller', False): 119 # An object with a "validate" method - call it with the parameters 120 validated_params = validators.validate(method, params, formencode_state) 121 122 elif hasattr(validators, 'validate'): 123 # An object with a "validate" method - call it with the parameters 124 validated_params = validators.validate(params, formencode_state) 125 126 return validated_params 127 128 129def _navigate_tw2form_children(w): 130 if getattr(w, 'compound_key', None): 131 # If we have a compound_key it's a leaf widget with form values 132 yield w 133 else: 134 child = getattr(w, 'child', None) 135 if child: 136 # Widgets with "child" don't have children, but their child has 137 w = child 138 139 for c in getattr(w, 'children', []): 140 for cc in _navigate_tw2form_children(c): 141 yield cc 142 143 144class TGValidationError(Exception): 145 """Invalid data was encountered during validation. 146 147 The constructor can be passed a short message with 148 the reason of the failed validation. 149 """ 150 def __init__(self, msg, value=None, error_dict=None): 151 super(TGValidationError, self).__init__(msg) 152 self.msg = msg 153 self.value = value 154 self.error_dict = error_dict 155 156 @classmethod 157 def make_compound_message(cls, error_dict): 158 return unicode_text('\n').join( 159 unicode_text("%s: %s") % errorinfo for errorinfo in error_dict.items() 160 ) 161 162 def __str__(self): 163 return str(self.msg) 164 165 def __unicode__(self): 166 return unicode_text(self.msg) 167 168 169class Convert(object): 170 """Applies a conversion function as a validator. 171 172 This is meant to implement simple validation mechanism. 173 174 Any callable can be used for ``func`` as far as it accepts an argument and 175 returns the converted object. In case of exceptions the validation 176 is considered failed and the ``msg`` parameter is displayed as 177 an error. 178 179 A ``default`` value can be provided for values that are missing 180 (evaluate to false) which will be used in place of the missing value. 181 182 Example:: 183 184 @expose() 185 @validate({ 186 'num': Convert(int, 'Must be a number') 187 }, error_handler=insert_number) 188 def post_pow2(self, num): 189 return str(num*num) 190 """ 191 def __init__(self, func, msg=lazy_ugettext('Invalid'), default=None): 192 self._func = func 193 self._msg = msg 194 self._default = default 195 196 def to_python(self, value, state=None): 197 value = value or self._default 198 199 try: 200 return self._func(value) 201 except: 202 raise TGValidationError(self._msg, value) 203 204 205validation_errors = (_Tw2ValidationError, _FormEncodeValidationError, TGValidationError) 206