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