Removing formencode and the built-in validation functionality.
Eventually, we'll replace formencode w/ something else (that's still being actively maintained).
This commit is contained in:
		| @@ -157,15 +157,9 @@ This is how it looks in the project template | ||||
| (``test_project.controllers.root.RootController``):: | ||||
|  | ||||
|     from pecan import expose | ||||
|     from formencode import Schema, validators as v | ||||
|     from webob.exc import status_map | ||||
|  | ||||
|  | ||||
|     class SampleForm(Schema): | ||||
|         name = v.String(not_empty=True) | ||||
|         age = v.Int(not_empty=True) | ||||
|  | ||||
|  | ||||
|     class RootController(object): | ||||
|  | ||||
|         @expose( | ||||
| @@ -175,13 +169,7 @@ This is how it looks in the project template | ||||
|         def index(self): | ||||
|             return dict() | ||||
|          | ||||
|         @index.when( | ||||
|             method          = 'POST', | ||||
|             template        = 'success.html', | ||||
|             schema          = SampleForm(), | ||||
|             error_handler   = '/index', | ||||
|             htmlfill        = dict(auto_insert_errors = True, prefix_error = False) | ||||
|         ) | ||||
|         @index.when(method='POST') | ||||
|         def index_post(self, name, age): | ||||
|             return dict(name=name) | ||||
|          | ||||
|   | ||||
| @@ -178,11 +178,6 @@ parameters, some of which can impact routing. | ||||
|  | ||||
|     expose(template        = None, | ||||
|            content_type    = 'text/html', | ||||
|            schema          = None, | ||||
|            json_schema     = None, | ||||
|            variable_decode = False, | ||||
|            error_handler   = None, | ||||
|            htmlfill        = None, | ||||
|            generic         = False) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,166 +2,5 @@ | ||||
|  | ||||
| Validation and Error Handling | ||||
| ============================= | ||||
| Pecan provides a variety of tools to help you handle common form validation and | ||||
| error handling activities, like: | ||||
|  | ||||
| * Validating the presence of submitted form contents with a schema. | ||||
| * Transforming strings from form submissions into useful Python objects. | ||||
| * Simplifying the process of re-displaying form values and associated error messages inline. | ||||
|  | ||||
| Rather than re-inventing the wheel, Pecan uses `FormEncode <http://formencode.org/>`_ for schemas and form validation. | ||||
|  | ||||
| Writing and Applying Schemas | ||||
| ------------------------------ | ||||
| Here's a simple example of a schema and how to apply it to a controller method using | ||||
| Pecan's ``expose`` decorator:: | ||||
|  | ||||
|     from pecan import expose | ||||
|     from formencode import Schema, validators as v | ||||
|  | ||||
|     class SimpleSchema(Schema):     | ||||
|         username = v.String(not_empty=True) | ||||
|         password = v.String(not_empty=True) | ||||
|          | ||||
|     class LoginController(object): | ||||
|  | ||||
|         @expose(schema=SimpleSchema) | ||||
|         def login(self, **kw): | ||||
|             if authenticate( | ||||
|               kw['username'], | ||||
|               kw['password'] | ||||
|             ): | ||||
|               set_cookie() | ||||
|             return dict() | ||||
|  | ||||
| Validating JSON Content | ||||
| ------------------------------ | ||||
| In addition to simple form arguments, Pecan also makes it easy to validate JSON request bodies. | ||||
| Often, especially in AJAX requests, the request content is encoded as JSON in the request body. | ||||
| Pecan's validation can handle the decoding for you and apply schema validation to the decoded | ||||
| data structure:: | ||||
|  | ||||
|     from pecan import expose | ||||
|     from formencode import Schema, validators as v | ||||
|     from myproject.lib import authenticate | ||||
|  | ||||
|     class JSONSchema(Schema): | ||||
|         """ | ||||
|         This schema would decode a JSON request body | ||||
|         that looked like: | ||||
|          | ||||
|         { | ||||
|           'username' : 'pecan', | ||||
|           'password' : 'dotpy | ||||
|         } | ||||
|          | ||||
|         """ | ||||
|         username = v.String(not_empty=True) | ||||
|         password = v.String(not_empty=True) | ||||
|      | ||||
|     class LoginController(object): | ||||
|  | ||||
|         @expose(json_schema=JSONSchema) | ||||
|         def login(self, **kw): | ||||
|             authenticate( | ||||
|               kw['username'], | ||||
|               kw['password'] | ||||
|             ) | ||||
|             return dict() | ||||
|  | ||||
| Handling Schema Failures | ||||
| ------------------------------ | ||||
| When schema validation fails, the validation errors from FormEncode are applied to Pecan's | ||||
| request object (``pecan.request.pecan``) as a dictionary. | ||||
| The key where the actual errors go is ``validation_errors`` and this can be | ||||
| inspected by your controller methods to react to errors appropiately:: | ||||
|  | ||||
|     from pecan import expose, request | ||||
|     from myproject.schemas import SimpleSchema | ||||
|  | ||||
|     class LoginController(object): | ||||
|  | ||||
|         @expose(schema=SimpleSchema) | ||||
|         def login(self, **kw): | ||||
|             if request.pecan['validation_errors']: | ||||
|                 pass # Don't Panic! | ||||
|             return dict() | ||||
|  | ||||
| Error Handlers and Template Filling | ||||
| ------------------------------ | ||||
| When schema validation fails, Pecan allows you to redirect to another controller internally | ||||
| for error handling via the `error_handler` keyword argument to ``@expose()``. | ||||
| This is especially useful when used in combination with generic | ||||
| controller methods:: | ||||
|  | ||||
|   from pecan import request, expose | ||||
|   from formencode import Schema, validators as v | ||||
|  | ||||
|   class ProfileSchema(Schema):     | ||||
|       name = v.String(not_empty=True) | ||||
|       email = v.String(not_empty=True) | ||||
|  | ||||
|   class ProfileController(object): | ||||
|    | ||||
|       @expose(generic=True) | ||||
|       def index(self): | ||||
|           pass | ||||
|            | ||||
|       @index.when(method="GET", template='profile.html') | ||||
|       def index_get(self): | ||||
|           """ | ||||
|           This method will be called to render the original template. | ||||
|           It will also be used for generating a form pre-filled with values | ||||
|           when schema failures occur. | ||||
|           """ | ||||
|           return dict() | ||||
|            | ||||
|       @index.when(method="POST", schema=ProfileSchema(), error_handler=lambda: request.path) | ||||
|       def index_post(self, **kw): | ||||
|           """ | ||||
|           This method will do something with POST arguments. | ||||
|           If the schema validation fails, an internal redirect will | ||||
|           cause the `profile.html` template to be rendered via the | ||||
|           ``index_get`` method. | ||||
|           """ | ||||
|            | ||||
|           name = kw.get('name') | ||||
|           email = ke.get('email') | ||||
|            | ||||
|           redirect('/profile') | ||||
|            | ||||
| In this example, when form validation errors occur (for example, the email provided is invalid), | ||||
| Pecan will handle pre-filling the form values in ``profile.html`` for you.  Additionally, inline | ||||
| errors will be appended to the template using FormEncode's ``htmlfill``. | ||||
|  | ||||
| Bypassing ``htmlfill`` | ||||
| ------------------------------ | ||||
| Sometimes you want certain fields in your templates to be ignored (i.e., not pre-filled) by ``htmlfill``. | ||||
| A perfect use case for this is password and hidden input fields.  The default Pecan template namespace | ||||
| includes a built-in function, ``static``, which allows you to enforce a static value for form fields, | ||||
| preventing ``htmlfill`` from filling in submitted form variables:: | ||||
|  | ||||
|     <form method="POST"> | ||||
|       <fieldset> | ||||
|         <label>Username:</label> | ||||
|           <input type="text" name="username" /> | ||||
|         <label>Password:</label>         | ||||
|           <input type="password" name="password" value="${static('password', '')}" /> | ||||
|         <input type="hidden" name="ticket" value="${static('ticket', 'RANDOM_PER_REQUEST_VALUE')}" /> | ||||
|         <button>Login</button> | ||||
|       </fieldset> | ||||
|     </form> | ||||
|  | ||||
| Working with ``variabledecode`` | ||||
| ------------------------------ | ||||
| Pecan also lets you take advantage of FormEncode's ``variabledecode`` for transforming flat HTML form | ||||
| submissions into nested structures:: | ||||
|  | ||||
|     from pecan import expose | ||||
|     from myproject import SimpleSchema | ||||
|  | ||||
|     class ProfileController(object): | ||||
|  | ||||
|         @expose(schema=SimpleSchema(), variable_decode=True) | ||||
|         def index(self): | ||||
|             return dict() | ||||
| TODO: Rewrite this (potentially as an extension/cookbook) to illustrate the | ||||
| new technique (without formencode). | ||||
|   | ||||
| @@ -7,8 +7,6 @@ from webob              import Request, Response, exc | ||||
| from threading          import local | ||||
| from itertools          import chain | ||||
| from mimetypes          import guess_type, add_type | ||||
| from formencode         import htmlfill, Invalid, variabledecode | ||||
| from formencode.schema  import merge_dicts | ||||
| from paste.recursive    import ForwardRequestException | ||||
| from urlparse           import urlsplit, urlunsplit | ||||
|  | ||||
| @@ -121,8 +119,7 @@ def redirect(location=None, internal=False, code=None, headers={}, | ||||
| def error_for(field): | ||||
|     ''' | ||||
|     A convenience function for fetching the validation error for a | ||||
|     particular field in a form. Useful within templates when not using | ||||
|     ``htmlfill`` for forms. | ||||
|     particular field in a form. | ||||
|  | ||||
|     :param field: The name of the field to get the error for. | ||||
|     ''' | ||||
| @@ -130,22 +127,6 @@ def error_for(field): | ||||
|     return request.pecan['validation_errors'].get(field, '') | ||||
|  | ||||
|  | ||||
| def static(name, value): | ||||
|     ''' | ||||
|     When using ``htmlfill`` validation support, this function indicates | ||||
|     that ``htmlfill`` should not fill in a value for this field, and | ||||
|     should instead use the value specified. | ||||
|  | ||||
|     :param name: The name of the field. | ||||
|     :param value: The value to specify. | ||||
|     ''' | ||||
|  | ||||
|     if 'pecan.params' not in request.environ: | ||||
|         request.environ['pecan.params'] = dict(request.params) | ||||
|     request.environ['pecan.params'][name] = value | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def render(template, namespace): | ||||
|     ''' | ||||
|     Render the specified template using the Pecan rendering framework | ||||
| @@ -176,14 +157,11 @@ class ValidationException(ForwardRequestException): | ||||
|             location = cfg['error_handler'] | ||||
|             if callable(location): | ||||
|                 location = location() | ||||
|         merge_dicts(request.pecan['validation_errors'], errors) | ||||
|         if 'pecan.params' not in request.environ: | ||||
|             request.environ['pecan.params'] = dict(request.params) | ||||
|         request.environ[ | ||||
|             'pecan.validation_errors' | ||||
|         ] = request.pecan['validation_errors'] | ||||
|         if cfg.get('htmlfill') is not None: | ||||
|             request.environ['pecan.htmlfill'] = cfg['htmlfill'] | ||||
|         request.environ['REQUEST_METHOD'] = 'GET' | ||||
|         request.environ['pecan.validation_redirected'] = True | ||||
|         ForwardRequestException.__init__(self, location) | ||||
| @@ -407,7 +385,6 @@ class Pecan(object): | ||||
|             renderer = self.renderers.get('json', self.template_path) | ||||
|         else: | ||||
|             namespace['error_for'] = error_for | ||||
|             namespace['static'] = static | ||||
|         if ':' in template: | ||||
|             renderer = self.renderers.get( | ||||
|                 template.split(':')[0], | ||||
| @@ -416,45 +393,6 @@ class Pecan(object): | ||||
|             template = template.split(':')[1] | ||||
|         return renderer.render(template, namespace) | ||||
|  | ||||
|     def validate(self, schema, params, json=False, error_handler=None, | ||||
|                  htmlfill=None, variable_decode=None): | ||||
|         ''' | ||||
|         Performs validation against a schema for any passed params, | ||||
|         including support for ``JSON``. | ||||
|  | ||||
|         :param schema: A ``formencode`` ``Schema`` object to validate against. | ||||
|         :param params: The dictionary of parameters to validate. | ||||
|         :param json: A boolean, indicating whether or not the validation should | ||||
|         validate against JSON content. | ||||
|         :param error_handler: The path to a controller which will handle | ||||
|         errors. If not specified, validation errors will raise a | ||||
|         ``ValidationException``. | ||||
|         :param htmlfill: Specifies whether or not to use htmlfill. | ||||
|         :param variable_decode: Indicates whether or not to decode variables | ||||
|         when using htmlfill. | ||||
|         ''' | ||||
|  | ||||
|         try: | ||||
|             to_validate = params | ||||
|             if json: | ||||
|                 to_validate = loads(request.body) | ||||
|             if variable_decode is not None: | ||||
|                 to_validate = variabledecode.variable_decode( | ||||
|                     to_validate, **variable_decode | ||||
|                 ) | ||||
|             params = schema.to_python(to_validate) | ||||
|         except Invalid, e: | ||||
|             kwargs = {} | ||||
|             if variable_decode is not None: | ||||
|                 kwargs['encode_variables'] = True | ||||
|                 kwargs.update(variable_decode) | ||||
|             request.pecan['validation_errors'] = e.unpack_errors(**kwargs) | ||||
|             if error_handler is not None: | ||||
|                 raise ValidationException() | ||||
|         if json: | ||||
|             params = dict(data=params) | ||||
|         return params or {} | ||||
|  | ||||
|     def handle_request(self): | ||||
|         ''' | ||||
|         The main request handler for Pecan applications. | ||||
| @@ -524,20 +462,8 @@ class Pecan(object): | ||||
|         # handle "before" hooks | ||||
|         self.handle_hooks('before', state) | ||||
|  | ||||
|         # fetch and validate any parameters | ||||
|         # fetch any parameters | ||||
|         params = dict(request.params) | ||||
|         if 'schema' in cfg: | ||||
|             params = self.validate( | ||||
|                         cfg['schema'], | ||||
|                         params, | ||||
|                         json=cfg['validate_json'], | ||||
|                         error_handler=cfg.get('error_handler'), | ||||
|                         htmlfill=cfg.get('htmlfill'), | ||||
|                         variable_decode=cfg.get('variable_decode') | ||||
|                     ) | ||||
|         elif 'pecan.validation_errors' in request.environ: | ||||
|             errors = request.environ.pop('pecan.validation_errors') | ||||
|             request.pecan['validation_errors'] = errors | ||||
|  | ||||
|         # fetch the arguments for the controller | ||||
|         args, kwargs = self.get_args( | ||||
| @@ -575,23 +501,8 @@ class Pecan(object): | ||||
|                 request.pecan['content_type'] = 'application/json' | ||||
|             result = self.render(template, result) | ||||
|  | ||||
|         # pass the response through htmlfill (items are popped out of the | ||||
|         # environment even if htmlfill won't run for proper cleanup) | ||||
|         _htmlfill = cfg.get('htmlfill') | ||||
|         if _htmlfill is None and 'pecan.htmlfill' in request.environ: | ||||
|             _htmlfill = request.environ.pop('pecan.htmlfill') | ||||
|         if 'pecan.params' in request.environ: | ||||
|             params = request.environ.pop('pecan.params') | ||||
|         if request.pecan['validation_errors'] and _htmlfill is not None and \ | ||||
|             request.pecan['content_type'] == 'text/html': | ||||
|             errors = request.pecan['validation_errors'] | ||||
|             result = htmlfill.render( | ||||
|                 result, | ||||
|                 defaults=params, | ||||
|                 errors=errors, | ||||
|                 text_as_default=True, | ||||
|                 **_htmlfill | ||||
|             ) | ||||
|  | ||||
|         # If we are in a test request put the namespace where it can be | ||||
|         # accessed directly | ||||
|   | ||||
| @@ -20,11 +20,6 @@ def when_for(controller): | ||||
|  | ||||
| def expose(template=None, | ||||
|            content_type='text/html', | ||||
|            schema=None, | ||||
|            json_schema=None, | ||||
|            variable_decode=False, | ||||
|            error_handler=None, | ||||
|            htmlfill=None, | ||||
|            generic=False): | ||||
|  | ||||
|     ''' | ||||
| @@ -34,14 +29,6 @@ def expose(template=None, | ||||
|     :param template: The path to a template, relative to the base template | ||||
|     directory. | ||||
|     :param content_type: The content-type to use for this template. | ||||
|     :param schema: A ``formencode`` ``Schema`` object to use for validation. | ||||
|     :param json_schema: A ``formencode`` ``Schema`` object to use for | ||||
|     validation of JSON POST/PUT content. | ||||
|     :param variable_decode: A boolean indicating if you want to use | ||||
|     ``htmlfill``'s variable decode capability of transforming flat HTML form | ||||
|     structures into nested ones. | ||||
|     :param htmlfill: Indicates whether or not you want to use ``htmlfill`` for | ||||
|     this controller. | ||||
|     :param generic: A boolean which flags this as a "generic" controller, which | ||||
|     uses generic functions based upon ``simplegeneric`` generic functions. | ||||
|     Allows you to split a single controller into multiple paths based upon HTTP | ||||
| @@ -70,30 +57,8 @@ def expose(template=None, | ||||
|         # store the arguments for this controller method | ||||
|         cfg['argspec'] = getargspec(f) | ||||
|  | ||||
|         # store the schema | ||||
|         cfg['error_handler'] = error_handler | ||||
|         if schema is not None: | ||||
|             cfg['schema'] = schema | ||||
|             cfg['validate_json'] = False | ||||
|         elif json_schema is not None: | ||||
|             cfg['schema'] = json_schema | ||||
|             cfg['validate_json'] = True | ||||
|  | ||||
|         # store the variable decode configuration | ||||
|         if isinstance(variable_decode, dict) or variable_decode == True: | ||||
|             _variable_decode = dict(dict_char='.', list_char='-') | ||||
|             if isinstance(variable_decode, dict): | ||||
|                 _variable_decode.update(variable_decode) | ||||
|             cfg['variable_decode'] = _variable_decode | ||||
|  | ||||
|         # store the htmlfill configuration | ||||
|         if isinstance(htmlfill, dict) or htmlfill == True or \ | ||||
|             schema is not None: | ||||
|             _htmlfill = dict(auto_insert_errors=False) | ||||
|             if isinstance(htmlfill, dict): | ||||
|                 _htmlfill.update(htmlfill) | ||||
|             cfg['htmlfill'] = _htmlfill | ||||
|         return f | ||||
|  | ||||
|     return decorate | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,24 +1,14 @@ | ||||
| from pecan import expose, redirect | ||||
| from formencode import Schema, validators as v | ||||
| from webob.exc import status_map | ||||
|  | ||||
|  | ||||
| class SearchForm(Schema): | ||||
|     q = v.String(not_empty=True) | ||||
|  | ||||
|  | ||||
| class RootController(object): | ||||
|  | ||||
|     @expose(generic=True, template='index.html') | ||||
|     def index(self): | ||||
|         return dict() | ||||
|  | ||||
|     @index.when( | ||||
|         method='POST', | ||||
|         schema=SearchForm(), | ||||
|         error_handler='/index', | ||||
|         htmlfill=dict(auto_insert_errors=True) | ||||
|     ) | ||||
|     @index.when(method='POST') | ||||
|     def index_post(self, q): | ||||
|         redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q) | ||||
|  | ||||
|   | ||||
| @@ -1,2 +0,0 @@ | ||||
| <input type="text" id="username" name="username" /> | ||||
| <input type="password" id="password" name="password" value="${static('password', '')}" /> | ||||
| @@ -7,7 +7,6 @@ from pecan.hooks import ( | ||||
| from pecan.configuration import Config | ||||
| from pecan.decorators import transactional, after_commit, after_rollback | ||||
| from unittest import TestCase | ||||
| from formencode import Schema, validators | ||||
| from webtest import TestApp | ||||
|  | ||||
|  | ||||
| @@ -329,125 +328,6 @@ class TestHooks(TestCase): | ||||
|         assert run_hook[4] == 'after1' | ||||
|         assert run_hook[5] == 'after2' | ||||
|  | ||||
|     def test_hooks_with_validation(self): | ||||
|         run_hook = [] | ||||
|  | ||||
|         class RegistrationSchema(Schema): | ||||
|             first_name = validators.String(not_empty=True) | ||||
|             last_name = validators.String(not_empty=True) | ||||
|             email = validators.Email() | ||||
|             username = validators.PlainText() | ||||
|             password = validators.String() | ||||
|             password_confirm = validators.String() | ||||
|             age = validators.Int() | ||||
|             chained_validators = [ | ||||
|                 validators.FieldsMatch('password', 'password_confirm') | ||||
|             ] | ||||
|  | ||||
|         class SimpleHook(PecanHook): | ||||
|             def on_route(self, state): | ||||
|                 run_hook.append('on_route') | ||||
|  | ||||
|             def before(self, state): | ||||
|                 run_hook.append('before') | ||||
|  | ||||
|             def after(self, state): | ||||
|                 run_hook.append('after') | ||||
|  | ||||
|             def on_error(self, state, e): | ||||
|                 run_hook.append('error') | ||||
|  | ||||
|         class RootController(object): | ||||
|             @expose() | ||||
|             def errors(self, *args, **kwargs): | ||||
|                 run_hook.append('inside') | ||||
|                 return 'errors' | ||||
|  | ||||
|             @expose(schema=RegistrationSchema()) | ||||
|             def index(self, first_name, | ||||
|                             last_name, | ||||
|                             email, | ||||
|                             username, | ||||
|                             password, | ||||
|                             password_confirm, | ||||
|                             age): | ||||
|                 run_hook.append('inside') | ||||
|                 return str(len(request.pecan['validation_errors']) > 0) | ||||
|  | ||||
|             @expose(schema=RegistrationSchema(), error_handler='/errors') | ||||
|             def with_handler(self, first_name, | ||||
|                             last_name, | ||||
|                             email, | ||||
|                             username, | ||||
|                             password, | ||||
|                             password_confirm, | ||||
|                             age): | ||||
|                 run_hook.append('inside') | ||||
|                 return str(len(request.pecan['validation_errors']) > 0) | ||||
|  | ||||
|         # test that the hooks get properly run with no validation errors | ||||
|         app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) | ||||
|         r = app.post('/', dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='123456', | ||||
|             age='31' | ||||
|         )) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'False' | ||||
|         assert len(run_hook) == 4 | ||||
|         assert run_hook[0] == 'on_route' | ||||
|         assert run_hook[1] == 'before' | ||||
|         assert run_hook[2] == 'inside' | ||||
|         assert run_hook[3] == 'after' | ||||
|         run_hook = [] | ||||
|  | ||||
|         # test that the hooks get properly run with validation errors | ||||
|         app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) | ||||
|         r = app.post('/', dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='654321', | ||||
|             password_confirm='123456', | ||||
|             age='31' | ||||
|         )) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'True' | ||||
|         assert len(run_hook) == 4 | ||||
|         assert run_hook[0] == 'on_route' | ||||
|         assert run_hook[1] == 'before' | ||||
|         assert run_hook[2] == 'inside' | ||||
|         assert run_hook[3] == 'after' | ||||
|         run_hook = [] | ||||
|  | ||||
|         # test that the hooks get properly run with validation errors | ||||
|         # and an error handler | ||||
|         app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) | ||||
|         r = app.post('/with_handler', dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='654321', | ||||
|             password_confirm='123456', | ||||
|             age='31' | ||||
|         )) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'errors' | ||||
|         assert len(run_hook) == 7 | ||||
|         assert run_hook[0] == 'on_route' | ||||
|         assert run_hook[1] == 'before' | ||||
|         assert run_hook[2] == 'after' | ||||
|         assert run_hook[3] == 'on_route' | ||||
|         assert run_hook[4] == 'before' | ||||
|         assert run_hook[5] == 'inside' | ||||
|         assert run_hook[6] == 'after' | ||||
|  | ||||
|  | ||||
| class TestTransactionHook(TestCase): | ||||
|     def test_transaction_hook(self): | ||||
|   | ||||
| @@ -7,8 +7,6 @@ try: | ||||
| except: | ||||
|     from json import dumps, loads  # noqa | ||||
|  | ||||
| import formencode | ||||
|  | ||||
|  | ||||
| class TestRestController(TestCase): | ||||
|  | ||||
| @@ -856,58 +854,3 @@ class TestRestController(TestCase): | ||||
|         r = app.get('/foos/bars/bazs/final/named') | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'NAMED' | ||||
|  | ||||
|     def test_rest_with_validation_redirects(self): | ||||
|         """ | ||||
|         Fixing a bug: | ||||
|  | ||||
|         When schema validation fails, pecan can use an internal redirect | ||||
|         (paste.recursive.ForwardRequestException) to send you to another | ||||
|         controller to "handle" the display of error messages.  Additionally, | ||||
|         pecan overwrites the request method as GET. | ||||
|  | ||||
|         In some circumstances, RestController's special `_method` parameter | ||||
|         prevents the redirected request from routing to the appropriate | ||||
|         `error_handler` controller. | ||||
|         """ | ||||
|  | ||||
|         class SampleSchema(formencode.Schema): | ||||
|             name = formencode.validators.String() | ||||
|  | ||||
|         class UserController(RestController): | ||||
|  | ||||
|             @expose() | ||||
|             def get_one(self, id): | ||||
|                 return "FORM VALIDATION FAILED" | ||||
|  | ||||
|             @expose( | ||||
|                 schema=SampleSchema(), | ||||
|                 error_handler=lambda: request.path | ||||
|             ) | ||||
|             def put(self, id): | ||||
|                 raise AssertionError("Schema validation should fail.") | ||||
|  | ||||
|             @expose( | ||||
|                 schema=SampleSchema(), | ||||
|                 error_handler=lambda: request.path | ||||
|             ) | ||||
|             def delete(self, id): | ||||
|                 raise AssertionError("Schema validation should fail.") | ||||
|  | ||||
|         class RootController(object): | ||||
|             users = UserController() | ||||
|  | ||||
|         # create the app | ||||
|         app = TestApp(make_app(RootController())) | ||||
|  | ||||
|         # create the app | ||||
|         app = TestApp(make_app(RootController())) | ||||
|  | ||||
|         # test proper internal redirection | ||||
|         r = app.post('/users/1?_method=put') | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == "FORM VALIDATION FAILED" | ||||
|  | ||||
|         r = app.post('/users/1?_method=delete') | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == "FORM VALIDATION FAILED" | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| import os | ||||
| from pecan import expose, make_app | ||||
| from unittest import TestCase | ||||
| from webtest import TestApp | ||||
|  | ||||
|  | ||||
| class TestStatic(TestCase): | ||||
|  | ||||
|     def test_simple_static(self): | ||||
|         class RootController(object): | ||||
|             @expose() | ||||
|             def index(self): | ||||
|                 return 'Hello, World!' | ||||
|  | ||||
|         # make sure Cascade is working properly | ||||
|         text = os.path.join(os.path.dirname(__file__), 'static/text.txt') | ||||
|         static_root = os.path.join(os.path.dirname(__file__), 'static') | ||||
|  | ||||
|         app = TestApp(make_app(RootController(), static_root=static_root)) | ||||
|         response = app.get('/index.html') | ||||
|         assert response.status_int == 200 | ||||
|         assert response.body == 'Hello, World!' | ||||
|  | ||||
|         # get a static resource | ||||
|         response = app.get('/text.txt') | ||||
|         assert response.status_int == 200 | ||||
|         assert response.body == open(text, 'rb').read() | ||||
| @@ -1,885 +0,0 @@ | ||||
| from formencode import ForEach, Schema, validators | ||||
| from webtest import TestApp | ||||
| from unittest import TestCase | ||||
|  | ||||
| import os.path | ||||
|  | ||||
| from pecan import make_app, expose, request, ValidationException | ||||
| from pecan.templating import _builtin_renderers as builtin_renderers | ||||
|  | ||||
| try: | ||||
|     from simplejson import dumps | ||||
| except ImportError: | ||||
|     from json import dumps  # noqa | ||||
|  | ||||
|  | ||||
| class TestValidation(TestCase): | ||||
|  | ||||
|     template_path = os.path.join(os.path.dirname(__file__), 'templates') | ||||
|  | ||||
|     def test_simple_validation(self): | ||||
|         class RegistrationSchema(Schema): | ||||
|             first_name = validators.String(not_empty=True) | ||||
|             last_name = validators.String(not_empty=True) | ||||
|             email = validators.Email() | ||||
|             username = validators.PlainText() | ||||
|             password = validators.String() | ||||
|             password_confirm = validators.String() | ||||
|             age = validators.Int() | ||||
|             chained_validators = [ | ||||
|                 validators.FieldsMatch('password', 'password_confirm') | ||||
|             ] | ||||
|  | ||||
|         class RootController(object): | ||||
|             @expose(schema=RegistrationSchema()) | ||||
|             def index(self, first_name, | ||||
|                             last_name, | ||||
|                             email, | ||||
|                             username, | ||||
|                             password, | ||||
|                             password_confirm, | ||||
|                             age): | ||||
|                 assert isinstance(last_name, unicode) | ||||
|                 assert isinstance(first_name, unicode) | ||||
|                 assert age == 31 | ||||
|                 assert isinstance(age, int) | ||||
|                 return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=RegistrationSchema()) | ||||
|             def json(self, data): | ||||
|                 assert data['age'] == 31 | ||||
|                 assert isinstance(data['age'], int) | ||||
|                 return 'Success!' | ||||
|  | ||||
|         # test form submissions | ||||
|         app = TestApp(make_app(RootController())) | ||||
|         r = app.post('/', dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='123456', | ||||
|             age='31' | ||||
|         )) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test JSON submissions | ||||
|         r = app.post('/json', dumps(dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='123456', | ||||
|             age='31' | ||||
|         )), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|     def test_validation_with_none_type(self): | ||||
|         """ | ||||
|         formencode schemas can be configured to return NoneType in some | ||||
|         circumstances.  We use the output of formencode's validate() to | ||||
|         determine wildcard arguments, so we should protect ourselves | ||||
|         from this scenario. | ||||
|         """ | ||||
|         class RegistrationSchema(Schema): | ||||
|             if_empty = None | ||||
|             first_name = validators.String(not_empty=True) | ||||
|             last_name = validators.String(not_empty=True) | ||||
|  | ||||
|         class RootController(object): | ||||
|             @expose(schema=RegistrationSchema()) | ||||
|             def index(self, **kw): | ||||
|                 assert 'first_name' not in kw | ||||
|                 assert 'last_name' not in kw | ||||
|                 return 'Success!' | ||||
|  | ||||
|         # test form submissions | ||||
|         app = TestApp(make_app(RootController())) | ||||
|         r = app.post('/', dict()) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|     def test_simple_failure(self): | ||||
|         class RegistrationSchema(Schema): | ||||
|             first_name = validators.String(not_empty=True) | ||||
|             last_name = validators.String(not_empty=True) | ||||
|             email = validators.Email() | ||||
|             username = validators.PlainText() | ||||
|             password = validators.String() | ||||
|             password_confirm = validators.String() | ||||
|             age = validators.Int() | ||||
|             chained_validators = [ | ||||
|                 validators.FieldsMatch('password', 'password_confirm') | ||||
|             ] | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             @expose() | ||||
|             def errors(self, *args, **kwargs): | ||||
|                 assert len(request.pecan['validation_errors']) > 0 | ||||
|                 return 'There was an error!' | ||||
|  | ||||
|             @expose(schema=RegistrationSchema()) | ||||
|             def index(self, first_name, | ||||
|                             last_name, | ||||
|                             email, | ||||
|                             username, | ||||
|                             password, | ||||
|                             password_confirm, | ||||
|                             age): | ||||
|                 assert len(request.pecan['validation_errors']) > 0 | ||||
|                 return 'Success!' | ||||
|  | ||||
|             @expose(schema=RegistrationSchema(), error_handler='/errors') | ||||
|             def with_handler(self, first_name, | ||||
|                             last_name, | ||||
|                             email, | ||||
|                             username, | ||||
|                             password, | ||||
|                             password_confirm, | ||||
|                             age): | ||||
|                 assert len(request.pecan['validation_errors']) > 0 | ||||
|                 return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=RegistrationSchema()) | ||||
|             def json(self, data): | ||||
|                 assert len(request.pecan['validation_errors']) > 0 | ||||
|                 return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=RegistrationSchema(), error_handler='/errors') | ||||
|             def json_with_handler(self, data): | ||||
|                 assert len(request.pecan['validation_errors']) > 0 | ||||
|                 return 'Success!' | ||||
|  | ||||
|         # test without error handler | ||||
|         app = TestApp(make_app(RootController())) | ||||
|         r = app.post('/', dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='654321', | ||||
|             age='31' | ||||
|         )) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test with error handler | ||||
|         r = app.post('/with_handler', dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='654321', | ||||
|             age='31' | ||||
|         )) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'There was an error!' | ||||
|  | ||||
|         # test JSON without error handler | ||||
|         r = app.post('/json', dumps(dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='654321', | ||||
|             age='31' | ||||
|         )), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test JSON with error handler | ||||
|         r = app.post('/json_with_handler', dumps(dict( | ||||
|             first_name='Jonathan', | ||||
|             last_name='LaCour', | ||||
|             email='jonathan@cleverdevil.org', | ||||
|             username='jlacour', | ||||
|             password='123456', | ||||
|             password_confirm='654321', | ||||
|             age='31' | ||||
|         )), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'There was an error!' | ||||
|  | ||||
|     def test_with_variable_decode(self): | ||||
|  | ||||
|         class ColorSchema(Schema): | ||||
|             colors = ForEach(validators.String(not_empty=True)) | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             @expose() | ||||
|             def errors(self, *args, **kwargs): | ||||
|                 errs = ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 return 'Error with %s!' % errs | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     variable_decode=True) | ||||
|             def index(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=True) | ||||
|             def with_handler(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     variable_decode=True) | ||||
|             def json(self, data): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=True) | ||||
|             def json_with_handler(self, data): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     variable_decode=dict()) | ||||
|             def custom(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=dict()) | ||||
|             def custom_with_handler(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     variable_decode=dict()) | ||||
|             def custom_json(self, data): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=dict()) | ||||
|             def custom_json_with_handler(self, data): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     variable_decode=dict(dict_char='-', list_char='.')) | ||||
|             def alternate(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=dict(dict_char='-', list_char='.')) | ||||
|             def alternate_with_handler(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     variable_decode=dict(dict_char='-', list_char='.')) | ||||
|             def alternate_json(self, data): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=dict(dict_char='-', list_char='.')) | ||||
|             def alternate_json_with_handler(self, data): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return ', '.join(request.pecan['validation_errors'].keys()) | ||||
|                 else: | ||||
|                     return 'Success!' | ||||
|  | ||||
|         # test without error handler | ||||
|         app = TestApp(make_app(RootController())) | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test failure without error handler | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'colors-1' | ||||
|  | ||||
|         # test with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test failure with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'colors-0': '', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Error with colors-0!' | ||||
|  | ||||
|         # test JSON without error handler | ||||
|         r = app.post('/json', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test JSON failure without error handler | ||||
|         r = app.post('/json', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'colors-1' | ||||
|  | ||||
|         # test JSON with error handler | ||||
|         r = app.post('/json_with_handler', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test JSON failure with error handler | ||||
|         r = app.post('/json_with_handler', dumps({ | ||||
|             'colors-0': '', | ||||
|             'colors-1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Error with colors-0!' | ||||
|  | ||||
|         # test custom without error handler | ||||
|         r = app.post('/custom', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test custom failure without error handler | ||||
|         r = app.post('/custom', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'colors-1' | ||||
|  | ||||
|         # test custom with error handler | ||||
|         r = app.post('/custom_with_handler', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test custom failure with error handler | ||||
|         r = app.post('/custom_with_handler', { | ||||
|             'colors-0': '', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Error with colors-0!' | ||||
|  | ||||
|         # test custom JSON without error handler | ||||
|         r = app.post('/custom_json', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test custom JSON failure without error handler | ||||
|         r = app.post('/custom_json', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'colors-1' | ||||
|  | ||||
|         # test custom JSON with error handler | ||||
|         r = app.post('/custom_json_with_handler', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test custom JSON failure with error handler | ||||
|         r = app.post('/custom_json_with_handler', dumps({ | ||||
|             'colors-0': '', | ||||
|             'colors-1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Error with colors-0!' | ||||
|  | ||||
|         # test alternate without error handler | ||||
|         r = app.post('/alternate', { | ||||
|             'colors.0': 'blue', | ||||
|             'colors.1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test alternate failure without error handler | ||||
|         r = app.post('/alternate', { | ||||
|             'colors.0': 'blue', | ||||
|             'colors.1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'colors.1' | ||||
|  | ||||
|         # test alternate with error handler | ||||
|         r = app.post('/alternate_with_handler', { | ||||
|             'colors.0': 'blue', | ||||
|             'colors.1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test alternate failure with error handler | ||||
|         r = app.post('/alternate_with_handler', { | ||||
|             'colors.0': '', | ||||
|             'colors.1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Error with colors.0!' | ||||
|  | ||||
|         # test alternate JSON without error handler | ||||
|         r = app.post('/alternate_json', dumps({ | ||||
|             'colors.0': 'blue', | ||||
|             'colors.1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test alternate JSON failure without error handler | ||||
|         r = app.post('/alternate_json', dumps({ | ||||
|             'colors.0': 'blue', | ||||
|             'colors.1': '' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'colors.1' | ||||
|  | ||||
|         # test alternate JSON with error handler | ||||
|         r = app.post('/alternate_json_with_handler', dumps({ | ||||
|             'colors.0': 'blue', | ||||
|             'colors.1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test alternate JSON failure with error handler | ||||
|         r = app.post('/alternate_json_with_handler', dumps({ | ||||
|             'colors.0': '', | ||||
|             'colors.1': 'red' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Error with colors.0!' | ||||
|  | ||||
|     def test_htmlfill(self): | ||||
|  | ||||
|         if 'mako' not in builtin_renderers: | ||||
|             return | ||||
|  | ||||
|         class ColorSchema(Schema): | ||||
|             colors = ForEach(validators.String(not_empty=True)) | ||||
|  | ||||
|         class NameSchema(Schema): | ||||
|             name = validators.String(not_empty=True) | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             @expose(template='mako:form_colors.html', | ||||
|                     schema=ColorSchema(), | ||||
|                     variable_decode=True) | ||||
|             def index(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return dict() | ||||
|                 else: | ||||
|                     return dict(data=kwargs) | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     error_handler='/errors_with_handler', | ||||
|                     variable_decode=True) | ||||
|             def with_handler(self, **kwargs): | ||||
|                 return ', '.join(kwargs['colors']) | ||||
|  | ||||
|             @expose('mako:form_colors.html') | ||||
|             def errors_with_handler(self): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(template='mako:form_name.html', | ||||
|                     schema=NameSchema(), | ||||
|                     htmlfill=dict(auto_insert_errors=True)) | ||||
|             def with_errors(self, **kwargs): | ||||
|                 return kwargs | ||||
|  | ||||
|             @expose(schema=NameSchema(), | ||||
|                     error_handler='/errors_with_handler_and_errors', | ||||
|                     htmlfill=dict(auto_insert_errors=True)) | ||||
|             def with_handler_and_errors(self, **kwargs): | ||||
|                 return kwargs['name'] | ||||
|  | ||||
|             @expose('mako:form_name.html') | ||||
|             def errors_with_handler_and_errors(self): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(template='json', | ||||
|                     schema=NameSchema(), | ||||
|                     htmlfill=dict(auto_insert_errors=True)) | ||||
|             def json(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return dict( | ||||
|                         error_with=request.pecan['validation_errors'].keys() | ||||
|                     ) | ||||
|                 else: | ||||
|                     return kwargs | ||||
|  | ||||
|         def _get_contents(filename): | ||||
|             return open(os.path.join(self.template_path, filename), 'r').read() | ||||
|  | ||||
|         # test without error handler | ||||
|         app = TestApp( | ||||
|             make_app(RootController(), template_path=self.template_path) | ||||
|         ) | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_colors_valid.html') | ||||
|  | ||||
|         # test failure without error handler | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_colors_invalid.html') | ||||
|  | ||||
|         # test with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'blue, red' | ||||
|  | ||||
|         # test failure with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_colors_invalid.html') | ||||
|  | ||||
|         # test with errors | ||||
|         r = app.post('/with_errors', { | ||||
|             'name': 'Yoann' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_name_valid.html') | ||||
|  | ||||
|         # test failure with errors | ||||
|         r = app.post('/with_errors', { | ||||
|             'name': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_name_invalid.html') | ||||
|  | ||||
|         # test with error handler and errors | ||||
|         r = app.post('/with_handler_and_errors', { | ||||
|             'name': 'Yoann' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Yoann' | ||||
|  | ||||
|         # test failure with error handler and errors | ||||
|         r = app.post('/with_handler_and_errors', { | ||||
|             'name': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_name_invalid.html') | ||||
|  | ||||
|         # test JSON | ||||
|         r = app.post('/json', { | ||||
|             'name': 'Yoann' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == dumps(dict(name='Yoann')) | ||||
|  | ||||
|         # test JSON failure | ||||
|         r = app.post('/json', { | ||||
|             'name': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == dumps(dict(error_with=['name'])) | ||||
|  | ||||
|     def test_htmlfill_static(self): | ||||
|  | ||||
|         if 'mako' not in builtin_renderers: | ||||
|             return | ||||
|  | ||||
|         class LoginSchema(Schema): | ||||
|             username = validators.String(not_empty=True) | ||||
|             password = validators.String(not_empty=True) | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             @expose(template='mako:form_login.html', | ||||
|                     schema=LoginSchema()) | ||||
|             def index(self, **kwargs): | ||||
|                 if request.pecan['validation_errors']: | ||||
|                     return dict() | ||||
|                 else: | ||||
|                     return dict(data=kwargs) | ||||
|  | ||||
|             @expose(schema=LoginSchema(), | ||||
|                     error_handler='/errors_with_handler') | ||||
|             def with_handler(self, **kwargs): | ||||
|                 return '%s:%s' % (kwargs['username'], kwargs['password']) | ||||
|  | ||||
|             @expose('mako:form_login.html') | ||||
|             def errors_with_handler(self): | ||||
|                 return dict() | ||||
|  | ||||
|         def _get_contents(filename): | ||||
|             return open(os.path.join(self.template_path, filename), 'r').read() | ||||
|  | ||||
|         # test without error handler | ||||
|         app = TestApp( | ||||
|             make_app(RootController(), template_path=self.template_path) | ||||
|         ) | ||||
|         r = app.post('/', { | ||||
|             'username': 'ryan', | ||||
|             'password': 'password' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_login_valid.html') | ||||
|  | ||||
|         # test failure without error handler | ||||
|         r = app.post('/', { | ||||
|             'username': 'ryan', | ||||
|             'password': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_login_invalid.html') | ||||
|  | ||||
|         # test with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'username': 'ryan', | ||||
|             'password': 'password' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'ryan:password' | ||||
|  | ||||
|         # test failure with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'username': 'ryan', | ||||
|             'password': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_login_invalid.html') | ||||
|  | ||||
|     def test_error_for(self): | ||||
|  | ||||
|         if 'mako' not in builtin_renderers: | ||||
|             return | ||||
|  | ||||
|         class ColorSchema(Schema): | ||||
|             colors = ForEach(validators.String(not_empty=True)) | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             @expose(template='mako:error_for.html') | ||||
|             def errors(self, *args, **kwargs): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(template='mako:error_for.html', | ||||
|                     schema=ColorSchema(), | ||||
|                     variable_decode=True) | ||||
|             def index(self, **kwargs): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=True) | ||||
|             def with_handler(self, **kwargs): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(template='mako:error_for.html', | ||||
|                     json_schema=ColorSchema(), | ||||
|                     variable_decode=True) | ||||
|             def json(self, data): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(json_schema=ColorSchema(), | ||||
|                     error_handler='/errors', | ||||
|                     variable_decode=True) | ||||
|             def json_with_handler(self, data): | ||||
|                 return dict() | ||||
|  | ||||
|         # test without error handler | ||||
|         app = TestApp( | ||||
|             make_app(RootController(), template_path=self.template_path) | ||||
|         ) | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == '' | ||||
|  | ||||
|         # test failure without error handler | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Please enter a value' | ||||
|  | ||||
|         # test failure with error handler | ||||
|         r = app.post('/with_handler', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Please enter a value' | ||||
|  | ||||
|         # test JSON failure without error handler | ||||
|         r = app.post('/json', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Please enter a value' | ||||
|  | ||||
|         # test JSON failure with error handler | ||||
|         r = app.post('/json_with_handler', dumps({ | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }), [('content-type', 'application/json')]) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Please enter a value' | ||||
|  | ||||
|     def test_callable_error_handler(self): | ||||
|  | ||||
|         class ColorSchema(Schema): | ||||
|             colors = ForEach(validators.String(not_empty=True)) | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             @expose() | ||||
|             def errors(self, *args, **kwargs): | ||||
|                 return 'There was an error!' | ||||
|  | ||||
|             @expose(schema=ColorSchema(), | ||||
|                     error_handler=lambda: '/errors', | ||||
|                     variable_decode=True) | ||||
|             def index(self, **kwargs): | ||||
|                 return 'Success!' | ||||
|  | ||||
|         # test with error handler | ||||
|         app = TestApp(make_app(RootController())) | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': 'red' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test with error handler | ||||
|         r = app.post('/', { | ||||
|             'colors-0': 'blue', | ||||
|             'colors-1': '' | ||||
|         }) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'There was an error!' | ||||
|  | ||||
|     def test_validation_exception(self): | ||||
|  | ||||
|         if 'mako' not in builtin_renderers: | ||||
|             return | ||||
|  | ||||
|         class NameSchema(Schema): | ||||
|             name = validators.String(not_empty=True) | ||||
|  | ||||
|         class SubController(object): | ||||
|             @expose() | ||||
|             def _route(self, *args): | ||||
|                 raise ValidationException('/success') | ||||
|  | ||||
|         class RootController(object): | ||||
|  | ||||
|             sub = SubController() | ||||
|  | ||||
|             @expose('mako:form_name.html') | ||||
|             def errors_name(self): | ||||
|                 return dict() | ||||
|  | ||||
|             @expose(schema=NameSchema(), | ||||
|                     error_handler='/errors_name', | ||||
|                     htmlfill=dict(auto_insert_errors=True)) | ||||
|             def name(self, name): | ||||
|                 raise ValidationException( | ||||
|                     errors={'name': 'Names must be unique'} | ||||
|                 ) | ||||
|  | ||||
|             @expose() | ||||
|             def success(self): | ||||
|                 return 'Success!' | ||||
|  | ||||
|         def _get_contents(filename): | ||||
|             return open(os.path.join(self.template_path, filename), 'r').read() | ||||
|  | ||||
|         # test exception with no controller | ||||
|         app = TestApp( | ||||
|             make_app(RootController(), template_path=self.template_path) | ||||
|         ) | ||||
|         r = app.get('/sub') | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == 'Success!' | ||||
|  | ||||
|         # test exception with additional errors | ||||
|         r = app.post('/name', {'name': 'Yoann'}) | ||||
|         assert r.status_int == 200 | ||||
|         assert r.body == _get_contents('form_name_invalid_custom.html') | ||||
		Reference in New Issue
	
	Block a user
	 Ryan Petrello
					Ryan Petrello