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:
Ryan Petrello
2012-03-12 13:44:03 -07:00
parent 941aa05444
commit 02a80731c4
12 changed files with 7 additions and 1411 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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).

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -1,2 +0,0 @@
<input type="text" id="username" name="username" />
<input type="password" id="password" name="password" value="${static('password', '')}" />

View File

@@ -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):

View File

@@ -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"

View File

@@ -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()

View File

@@ -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')

View File

@@ -14,7 +14,6 @@ requirements = [
"Mako >= 0.4.0",
"Paste >= 1.7.5.1",
"PasteScript >= 1.7.3",
"formencode >= 1.2.2",
"WebTest >= 1.2.2"
]