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``)::
|
(``test_project.controllers.root.RootController``)::
|
||||||
|
|
||||||
from pecan import expose
|
from pecan import expose
|
||||||
from formencode import Schema, validators as v
|
|
||||||
from webob.exc import status_map
|
from webob.exc import status_map
|
||||||
|
|
||||||
|
|
||||||
class SampleForm(Schema):
|
|
||||||
name = v.String(not_empty=True)
|
|
||||||
age = v.Int(not_empty=True)
|
|
||||||
|
|
||||||
|
|
||||||
class RootController(object):
|
class RootController(object):
|
||||||
|
|
||||||
@expose(
|
@expose(
|
||||||
@@ -175,13 +169,7 @@ This is how it looks in the project template
|
|||||||
def index(self):
|
def index(self):
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
@index.when(
|
@index.when(method='POST')
|
||||||
method = 'POST',
|
|
||||||
template = 'success.html',
|
|
||||||
schema = SampleForm(),
|
|
||||||
error_handler = '/index',
|
|
||||||
htmlfill = dict(auto_insert_errors = True, prefix_error = False)
|
|
||||||
)
|
|
||||||
def index_post(self, name, age):
|
def index_post(self, name, age):
|
||||||
return dict(name=name)
|
return dict(name=name)
|
||||||
|
|
||||||
|
|||||||
@@ -178,11 +178,6 @@ parameters, some of which can impact routing.
|
|||||||
|
|
||||||
expose(template = None,
|
expose(template = None,
|
||||||
content_type = 'text/html',
|
content_type = 'text/html',
|
||||||
schema = None,
|
|
||||||
json_schema = None,
|
|
||||||
variable_decode = False,
|
|
||||||
error_handler = None,
|
|
||||||
htmlfill = None,
|
|
||||||
generic = False)
|
generic = False)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,166 +2,5 @@
|
|||||||
|
|
||||||
Validation and Error Handling
|
Validation and Error Handling
|
||||||
=============================
|
=============================
|
||||||
Pecan provides a variety of tools to help you handle common form validation and
|
TODO: Rewrite this (potentially as an extension/cookbook) to illustrate the
|
||||||
error handling activities, like:
|
new technique (without formencode).
|
||||||
|
|
||||||
* 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()
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ from webob import Request, Response, exc
|
|||||||
from threading import local
|
from threading import local
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from mimetypes import guess_type, add_type
|
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 paste.recursive import ForwardRequestException
|
||||||
from urlparse import urlsplit, urlunsplit
|
from urlparse import urlsplit, urlunsplit
|
||||||
|
|
||||||
@@ -121,8 +119,7 @@ def redirect(location=None, internal=False, code=None, headers={},
|
|||||||
def error_for(field):
|
def error_for(field):
|
||||||
'''
|
'''
|
||||||
A convenience function for fetching the validation error for a
|
A convenience function for fetching the validation error for a
|
||||||
particular field in a form. Useful within templates when not using
|
particular field in a form.
|
||||||
``htmlfill`` for forms.
|
|
||||||
|
|
||||||
:param field: The name of the field to get the error for.
|
: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, '')
|
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):
|
def render(template, namespace):
|
||||||
'''
|
'''
|
||||||
Render the specified template using the Pecan rendering framework
|
Render the specified template using the Pecan rendering framework
|
||||||
@@ -176,14 +157,11 @@ class ValidationException(ForwardRequestException):
|
|||||||
location = cfg['error_handler']
|
location = cfg['error_handler']
|
||||||
if callable(location):
|
if callable(location):
|
||||||
location = location()
|
location = location()
|
||||||
merge_dicts(request.pecan['validation_errors'], errors)
|
|
||||||
if 'pecan.params' not in request.environ:
|
if 'pecan.params' not in request.environ:
|
||||||
request.environ['pecan.params'] = dict(request.params)
|
request.environ['pecan.params'] = dict(request.params)
|
||||||
request.environ[
|
request.environ[
|
||||||
'pecan.validation_errors'
|
'pecan.validation_errors'
|
||||||
] = request.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['REQUEST_METHOD'] = 'GET'
|
||||||
request.environ['pecan.validation_redirected'] = True
|
request.environ['pecan.validation_redirected'] = True
|
||||||
ForwardRequestException.__init__(self, location)
|
ForwardRequestException.__init__(self, location)
|
||||||
@@ -407,7 +385,6 @@ class Pecan(object):
|
|||||||
renderer = self.renderers.get('json', self.template_path)
|
renderer = self.renderers.get('json', self.template_path)
|
||||||
else:
|
else:
|
||||||
namespace['error_for'] = error_for
|
namespace['error_for'] = error_for
|
||||||
namespace['static'] = static
|
|
||||||
if ':' in template:
|
if ':' in template:
|
||||||
renderer = self.renderers.get(
|
renderer = self.renderers.get(
|
||||||
template.split(':')[0],
|
template.split(':')[0],
|
||||||
@@ -416,45 +393,6 @@ class Pecan(object):
|
|||||||
template = template.split(':')[1]
|
template = template.split(':')[1]
|
||||||
return renderer.render(template, namespace)
|
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):
|
def handle_request(self):
|
||||||
'''
|
'''
|
||||||
The main request handler for Pecan applications.
|
The main request handler for Pecan applications.
|
||||||
@@ -524,20 +462,8 @@ class Pecan(object):
|
|||||||
# handle "before" hooks
|
# handle "before" hooks
|
||||||
self.handle_hooks('before', state)
|
self.handle_hooks('before', state)
|
||||||
|
|
||||||
# fetch and validate any parameters
|
# fetch any parameters
|
||||||
params = dict(request.params)
|
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
|
# fetch the arguments for the controller
|
||||||
args, kwargs = self.get_args(
|
args, kwargs = self.get_args(
|
||||||
@@ -575,23 +501,8 @@ class Pecan(object):
|
|||||||
request.pecan['content_type'] = 'application/json'
|
request.pecan['content_type'] = 'application/json'
|
||||||
result = self.render(template, result)
|
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:
|
if 'pecan.params' in request.environ:
|
||||||
params = request.environ.pop('pecan.params')
|
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
|
# If we are in a test request put the namespace where it can be
|
||||||
# accessed directly
|
# accessed directly
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ def when_for(controller):
|
|||||||
|
|
||||||
def expose(template=None,
|
def expose(template=None,
|
||||||
content_type='text/html',
|
content_type='text/html',
|
||||||
schema=None,
|
|
||||||
json_schema=None,
|
|
||||||
variable_decode=False,
|
|
||||||
error_handler=None,
|
|
||||||
htmlfill=None,
|
|
||||||
generic=False):
|
generic=False):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -34,14 +29,6 @@ def expose(template=None,
|
|||||||
:param template: The path to a template, relative to the base template
|
:param template: The path to a template, relative to the base template
|
||||||
directory.
|
directory.
|
||||||
:param content_type: The content-type to use for this template.
|
: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
|
:param generic: A boolean which flags this as a "generic" controller, which
|
||||||
uses generic functions based upon ``simplegeneric`` generic functions.
|
uses generic functions based upon ``simplegeneric`` generic functions.
|
||||||
Allows you to split a single controller into multiple paths based upon HTTP
|
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
|
# store the arguments for this controller method
|
||||||
cfg['argspec'] = getargspec(f)
|
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 f
|
||||||
|
|
||||||
return decorate
|
return decorate
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
from pecan import expose, redirect
|
from pecan import expose, redirect
|
||||||
from formencode import Schema, validators as v
|
|
||||||
from webob.exc import status_map
|
from webob.exc import status_map
|
||||||
|
|
||||||
|
|
||||||
class SearchForm(Schema):
|
|
||||||
q = v.String(not_empty=True)
|
|
||||||
|
|
||||||
|
|
||||||
class RootController(object):
|
class RootController(object):
|
||||||
|
|
||||||
@expose(generic=True, template='index.html')
|
@expose(generic=True, template='index.html')
|
||||||
def index(self):
|
def index(self):
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
@index.when(
|
@index.when(method='POST')
|
||||||
method='POST',
|
|
||||||
schema=SearchForm(),
|
|
||||||
error_handler='/index',
|
|
||||||
htmlfill=dict(auto_insert_errors=True)
|
|
||||||
)
|
|
||||||
def index_post(self, q):
|
def index_post(self, q):
|
||||||
redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % 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.configuration import Config
|
||||||
from pecan.decorators import transactional, after_commit, after_rollback
|
from pecan.decorators import transactional, after_commit, after_rollback
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from formencode import Schema, validators
|
|
||||||
from webtest import TestApp
|
from webtest import TestApp
|
||||||
|
|
||||||
|
|
||||||
@@ -329,125 +328,6 @@ class TestHooks(TestCase):
|
|||||||
assert run_hook[4] == 'after1'
|
assert run_hook[4] == 'after1'
|
||||||
assert run_hook[5] == 'after2'
|
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):
|
class TestTransactionHook(TestCase):
|
||||||
def test_transaction_hook(self):
|
def test_transaction_hook(self):
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ try:
|
|||||||
except:
|
except:
|
||||||
from json import dumps, loads # noqa
|
from json import dumps, loads # noqa
|
||||||
|
|
||||||
import formencode
|
|
||||||
|
|
||||||
|
|
||||||
class TestRestController(TestCase):
|
class TestRestController(TestCase):
|
||||||
|
|
||||||
@@ -856,58 +854,3 @@ class TestRestController(TestCase):
|
|||||||
r = app.get('/foos/bars/bazs/final/named')
|
r = app.get('/foos/bars/bazs/final/named')
|
||||||
assert r.status_int == 200
|
assert r.status_int == 200
|
||||||
assert r.body == 'NAMED'
|
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