diff --git a/pecan/__init__.py b/pecan/__init__.py index 9538339..bfef5b0 100644 --- a/pecan/__init__.py +++ b/pecan/__init__.py @@ -1,7 +1,9 @@ from paste.urlparser import StaticURLParser from paste.cascade import Cascade +from weberror.errormiddleware import ErrorMiddleware +from paste.recursive import RecursiveMiddleware -from pecan import Pecan, request, response, override_template, redirect +from pecan import Pecan, request, response, override_template, redirect, error_for from decorators import expose __all__ = [ @@ -9,8 +11,10 @@ __all__ = [ ] -def make_app(root, static_root=None, **kw): +def make_app(root, static_root=None, debug=False, errorcfg={}, **kw): app = Pecan(root, **kw) + app = RecursiveMiddleware(app) + app = ErrorMiddleware(app, debug=debug, **errorcfg) if static_root: app = Cascade([StaticURLParser(static_root), app]) return app \ No newline at end of file diff --git a/pecan/pecan.py b/pecan/pecan.py index ec4fa0d..e2a4755 100644 --- a/pecan/pecan.py +++ b/pecan/pecan.py @@ -5,6 +5,7 @@ from webob import Request, Response, exc from threading import local from itertools import chain from formencode import Invalid +from paste.recursive import ForwardRequestException try: from json import loads @@ -19,6 +20,8 @@ def proxy(key): class ObjectProxy(object): def __getattr__(self, attr): obj = getattr(state, key) + if attr == 'validation_error': + return getattr(obj, attr, None) return getattr(obj, attr) def __setattr__(self, attr, value): obj = getattr(state, key) @@ -37,6 +40,10 @@ def override_template(template): def redirect(location): raise exc.HTTPFound(location=location) +def error_for(field): + if request.validation_error is None: return '' + return request.validation_error.error_dict.get(field, '') + class Pecan(object): def __init__(self, root, @@ -122,6 +129,7 @@ class Pecan(object): controller.pecan['argspec'] ) if 'schema' in controller.pecan: + request.validation_error = None try: params = self.validate( controller.pecan['schema'], @@ -131,8 +139,7 @@ class Pecan(object): except Invalid, e: request.validation_error = e if controller.pecan['error_handler'] is not None: - state.validation_error = e - redirect(controller.pecan['error_handler']) + raise ForwardRequestException(controller.pecan['error_handler']) if controller.pecan['validate_json']: params = dict(data=params) # get the result from the controller @@ -147,7 +154,10 @@ class Pecan(object): renderer = self.renderers.get(self.default_renderer, self.template_path) if template == 'json': renderer = self.renderers.get('json', self.template_path) - elif ':' in template: + else: + result['error_for'] = error_for + + if ':' in template: renderer = self.renderers.get(template.split(':')[0], self.template_path) template = template.split(':')[1] result = renderer.render(template, result) @@ -168,12 +178,7 @@ class Pecan(object): state.request = Request(environ) state.response = Response() state.hooks = [] - - # handle validation errors from redirects - if hasattr(state, 'validation_error'): - state.request.validation_error = state.validation_error - del state.validation_error - + # handle the request try: self.handle_request() diff --git a/setup.py b/setup.py index e1cf968..0e23117 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ class PyTest(Command): # determine requirements # requirements = [ - "WebOb >= 0.9.8", + "WebOb >= 1.0.0", + "WebCore >= 1.0.0", "simplegeneric >= 0.7", "Genshi >= 0.6", "Kajiki >= 0.2.2", diff --git a/templates/project/+egg+/controllers/root.py b/templates/project/+egg+/controllers/root.py index 5c404e1..f5c5ae9 100644 --- a/templates/project/+egg+/controllers/root.py +++ b/templates/project/+egg+/controllers/root.py @@ -1,6 +1,17 @@ -from pecan import expose +from pecan import expose, request +from formencode import Schema, validators as v + + +class SampleForm(Schema): + name = v.String(not_empty=True) + age = v.Int(not_empty=True) + class RootController(object): @expose('kajiki:index.html') - def index(self, name='World'): - return dict(name=name) \ No newline at end of file + def index(self, name='', age=''): + return dict(errors=request.validation_error, name=name, age=age) + + @expose('kajiki:success.html', schema=SampleForm(), error_handler='index') + def handle_form(self, name, age): + return dict(name=name, age=age) diff --git a/templates/project/+egg+/templates/index.html b/templates/project/+egg+/templates/index.html index d2a8837..2411552 100644 --- a/templates/project/+egg+/templates/index.html +++ b/templates/project/+egg+/templates/index.html @@ -1,8 +1,44 @@ - +
-This is the default Pecan project, which includes:
+ +
+ Note that most functionality is enabled/disabled in your start.py,
+ which can be edited to suit your needs, and to add additional WSGI middleware.
+
+ To get an idea of how to develop applications with Pecan, + here is a simple form: +
+ + \ No newline at end of file diff --git a/templates/project/+egg+/templates/layout.html b/templates/project/+egg+/templates/layout.html new file mode 100644 index 0000000..7cf7c06 --- /dev/null +++ b/templates/project/+egg+/templates/layout.html @@ -0,0 +1,13 @@ + + +Your form submission was successful! Thanks, ${name}!
+ + \ No newline at end of file diff --git a/templates/project/public/css/style.css b/templates/project/public/css/style.css new file mode 100644 index 0000000..b68a653 --- /dev/null +++ b/templates/project/public/css/style.css @@ -0,0 +1,39 @@ +body { + background: #311F00; + color: white; + font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif; + font-size: 1.25em; + padding: 1em 2em; +} + +a { + color: #FAFF78; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +form { + margin: 0 1em; + padding: 1em; + border: 5px transparent; +} + +form.invalid { + border: 5px solid FAFF78; +} + +input.invalid { + background: #FAFF78; +} + +header { + text-align: center; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif; + text-transform: uppercase; +} \ No newline at end of file diff --git a/templates/project/public/javascript/shared.js b/templates/project/public/javascript/shared.js new file mode 100644 index 0000000..e69de29 diff --git a/templates/project/start.py_tmpl b/templates/project/start.py_tmpl index fb4ef5e..e1389af 100644 --- a/templates/project/start.py_tmpl +++ b/templates/project/start.py_tmpl @@ -5,7 +5,8 @@ if __name__ == '__main__': app = make_app( RootController(), static_root='public', - template_path='${egg}/templates' + template_path='${egg}/templates', + debug=True ) print 'Serving on http://0.0.0.0:8080' diff --git a/tests/test_validation.py b/tests/test_validation.py index 9c9d941..3516d9c 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,4 +1,4 @@ -from pecan import Pecan, expose, request, response +from pecan import make_app, expose, request, response from webtest import TestApp from formencode import validators, Schema @@ -45,7 +45,7 @@ class TestValidation(object): return 'Success!' # test form submissions - app = TestApp(Pecan(RootController())) + app = TestApp(make_app(RootController())) r = app.post('/', dict( first_name='Jonathan', last_name='LaCour', @@ -85,6 +85,11 @@ class TestValidation(object): ] class RootController(object): + @expose() + def errors(self, *args, **kwargs): + assert request.validation_error is not None + return 'There was an error!' + @expose(schema=RegistrationSchema()) def index(self, first_name, last_name, @@ -116,15 +121,10 @@ class TestValidation(object): def json_with_handler(self, data): assert request.validation_error is not None return 'Success!' - - @expose() - def errors(self, *args, **kwargs): - assert request.validation_error is not None - return 'There was an error!' # test without error handler - app = TestApp(Pecan(RootController())) + app = TestApp(make_app(RootController())) r = app.post('/', dict( first_name='Jonathan', last_name='LaCour', @@ -138,7 +138,7 @@ class TestValidation(object): assert r.body == 'Success!' # test with error handler - app = TestApp(Pecan(RootController())) + app = TestApp(make_app(RootController())) r = app.post('/with_handler', dict( first_name='Jonathan', last_name='LaCour', @@ -148,8 +148,6 @@ class TestValidation(object): password_confirm='654321', age='31' )) - assert r.status_int == 302 - r = r.follow() assert r.status_int == 200 assert r.body == 'There was an error!' @@ -176,7 +174,5 @@ class TestValidation(object): password_confirm='654321', age='31' )), [('content-type', 'application/json')]) - assert r.status_int == 302 - r = r.follow() assert r.status_int == 200 assert r.body == 'There was an error!' \ No newline at end of file