diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst index 5eeadcf..d8c2aa9 100644 --- a/docs/source/quick_start.rst +++ b/docs/source/quick_start.rst @@ -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) diff --git a/docs/source/routing.rst b/docs/source/routing.rst index 9fc0dd5..f936254 100644 --- a/docs/source/routing.rst +++ b/docs/source/routing.rst @@ -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) diff --git a/docs/source/validation_n_errors.rst b/docs/source/validation_n_errors.rst index 4543371..facc4d6 100644 --- a/docs/source/validation_n_errors.rst +++ b/docs/source/validation_n_errors.rst @@ -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 `_ 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:: - -
-
- - - - - - -
-
- -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). diff --git a/pecan/core.py b/pecan/core.py index 35cae46..261c83d 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -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 diff --git a/pecan/decorators.py b/pecan/decorators.py index 2ae86eb..aaf144c 100644 --- a/pecan/decorators.py +++ b/pecan/decorators.py @@ -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 diff --git a/pecan/templates/project/+package+/controllers/root.py b/pecan/templates/project/+package+/controllers/root.py index 1be22c0..bc1e72b 100644 --- a/pecan/templates/project/+package+/controllers/root.py +++ b/pecan/templates/project/+package+/controllers/root.py @@ -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) diff --git a/pecan/tests/templates/form_login.html b/pecan/tests/templates/form_login.html deleted file mode 100644 index b159f3e..0000000 --- a/pecan/tests/templates/form_login.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py index 94b52c5..a7d7025 100644 --- a/pecan/tests/test_hooks.py +++ b/pecan/tests/test_hooks.py @@ -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): diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py index 0a007c5..86e56ea 100644 --- a/pecan/tests/test_rest.py +++ b/pecan/tests/test_rest.py @@ -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" diff --git a/pecan/tests/test_static.py b/pecan/tests/test_static.py deleted file mode 100644 index b423707..0000000 --- a/pecan/tests/test_static.py +++ /dev/null @@ -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() diff --git a/pecan/tests/test_validation.py b/pecan/tests/test_validation.py deleted file mode 100644 index df138a9..0000000 --- a/pecan/tests/test_validation.py +++ /dev/null @@ -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') diff --git a/setup.py b/setup.py index e79e2f0..bcdd92b 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,6 @@ requirements = [ "Mako >= 0.4.0", "Paste >= 1.7.5.1", "PasteScript >= 1.7.3", - "formencode >= 1.2.2", "WebTest >= 1.2.2" ]