From bfb45e033a2c5cfe6c0319626e0cb515c9d62982 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Sun, 21 Aug 2011 01:56:31 -0400 Subject: [PATCH] Fixing a schema validation bug related to specialized RestController routing. https://github.com/pecan/pecan/issues/13 --- pecan/core.py | 1 + pecan/rest.py | 12 ++++++++-- tests/test_rest.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pecan/core.py b/pecan/core.py index 573c6dd..1271a21 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -153,6 +153,7 @@ class ValidationException(ForwardRequestException): 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) diff --git a/pecan/rest.py b/pecan/rest.py index fc03b6c..aac32bc 100644 --- a/pecan/rest.py +++ b/pecan/rest.py @@ -19,12 +19,20 @@ class RestController(object): def _route(self, args): # convention uses "_method" to handle browser-unsupported methods - method = request.params.get('_method', request.method).lower() + if request.environ.get('pecan.validation_redirected', False) == True: + # + # If the request has been internally redirected due to a validation + # exception, we want the request method to be enforced as GET, not + # the `_method` param which may have been passed for REST support. + # + method = request.method.lower() + else: + method = request.params.get('_method', request.method).lower() # make sure DELETE/PUT requests don't use GET if request.method == 'GET' and method in ('delete', 'put'): abort(405) - + # check for nested controllers result = self._find_sub_controllers(args) if result: diff --git a/tests/test_rest.py b/tests/test_rest.py index b9b7e0c..9bcb4b5 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -6,6 +6,8 @@ try: except: from json import dumps, loads +import formencode + class TestRestController(object): @@ -829,3 +831,58 @@ class TestRestController(object): 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"