diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-09-30 04:15:39 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-09-30 04:15:39 +0000 |
| commit | 3a61a378e03ae3a5b44ce326aac56b159a15bfa1 (patch) | |
| tree | 1a3e1c968694056fccf400ccd13530fb344d6d60 | |
| parent | d75fe70228c89e3606e51a4d5775faf549252a90 (diff) | |
| download | pyramid-3a61a378e03ae3a5b44ce326aac56b159a15bfa1.tar.gz pyramid-3a61a378e03ae3a5b44ce326aac56b159a15bfa1.tar.bz2 pyramid-3a61a378e03ae3a5b44ce326aac56b159a15bfa1.zip | |
- For behavior like Django's ``APPEND_SLASH=True``, use the
``repoze.bfg.view.append_slash_notfound_view`` view as the Not Found
view in your application. When this view is the Not Found view
(indicating that no view was found), and any routes have been
defined in the configuration of your application, if the value of
``PATH_INFO`` does not already end in a slash, and if the value of
``PATH_INFO`` *plus* a slash matches any route's path, do an HTTP
redirect to the slash-appended PATH_INFO. Note that this will
*lose* ``POST`` data information (turning it into a GET), so you
shouldn't rely on this to redirect POST requests.
| -rw-r--r-- | CHANGES.txt | 11 | ||||
| -rw-r--r-- | docs/api/view.rst | 3 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 55 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 35 |
4 files changed, 104 insertions, 0 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 981483e25..8a82bc086 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,17 @@ Documentation Features -------- +- For behavior like Django's ``APPEND_SLASH=True``, use the + ``repoze.bfg.view.append_slash_notfound_view`` view as the Not Found + view in your application. When this view is the Not Found view + (indicating that no view was found), and any routes have been + defined in the configuration of your application, if the value of + ``PATH_INFO`` does not already end in a slash, and if the value of + ``PATH_INFO`` *plus* a slash matches any route's path, do an HTTP + redirect to the slash-appended PATH_INFO. Note that this will + *lose* ``POST`` data information (turning it into a GET), so you + shouldn't rely on this to redirect POST requests. + - Speed up ``repoze.bfg.location.lineage`` slightly. - Speed up ``repoze.bfg.encode.urlencode`` (nee' diff --git a/docs/api/view.rst b/docs/api/view.rst index 40c69d24b..e345a0015 100644 --- a/docs/api/view.rst +++ b/docs/api/view.rst @@ -19,3 +19,6 @@ .. autoclass:: static :members: + .. autofunction:: append_slash_notfound_view + + diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index 32c9d391b..4705a16c4 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -441,6 +441,61 @@ class TestDefaultNotFoundView(unittest.TestCase): self.assertEqual(response.status, '404 Not Found') self.failUnless('<code>abc&123</code>' in response.body) +class AppendSlashNotFoundView(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, context, request): + from repoze.bfg.view import append_slash_notfound_view + return append_slash_notfound_view(context, request) + + def _registerMapper(self, match=True): + from repoze.bfg.interfaces import IRoutesMapper + class DummyRoute(object): + def __init__(self, val): + self.val = val + def match(self, path): + return self.val + class DummyMapper(object): + def __init__(self): + self.routelist = [ DummyRoute(match) ] + mapper = DummyMapper() + import zope.component + gsm = zope.component.getGlobalSiteManager() + gsm.registerUtility(mapper, IRoutesMapper) + return mapper + + def test_no_mapper(self): + request = DummyRequest({'PATH_INFO':'/abc'}) + context = DummyContext() + response = self._callFUT(context, request) + self.assertEqual(response.status, '404 Not Found') + + def test_no_path(self): + self._registerMapper(True) + request = DummyRequest({}) + context = DummyContext() + response = self._callFUT(context, request) + self.assertEqual(response.status, '404 Not Found') + + def test_mapper_path_already_slash_ending(self): + self._registerMapper(True) + request = DummyRequest({'PATH_INFO':'/abc/'}) + context = DummyContext() + response = self._callFUT(context, request) + self.assertEqual(response.status, '404 Not Found') + + def test_matches(self): + self._registerMapper(True) + request = DummyRequest({'PATH_INFO':'/abc'}) + context = DummyContext() + response = self._callFUT(context, request) + self.assertEqual(response.status, '302 Found') + self.assertEqual(response.location, '/abc/') + class TestMultiView(unittest.TestCase): def _getTargetClass(self): from repoze.bfg.view import MultiView diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 91ccad57d..a4abf4947 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -14,6 +14,7 @@ if hasattr(mimetypes, 'init'): mimetypes.init() from webob import Response +from webob.exc import HTTPFound from paste.urlparser import StaticURLParser @@ -29,6 +30,7 @@ from repoze.bfg.interfaces import ILogger from repoze.bfg.interfaces import IMultiView from repoze.bfg.interfaces import IRendererFactory from repoze.bfg.interfaces import IResponseFactory +from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import IView from repoze.bfg.exceptions import NotFound @@ -718,3 +720,36 @@ def authdebug_view(view, permission): decorate_view(wrapped_view, view) return wrapped_view + +def append_slash_notfound_view(context, request): + """For behavior like Django's ``APPEND_SLASH=True``, use this view + as the Not Found view in your application. + + When this view is the Not Found view (indicating that no view was + found), and any routes have been defined in the configuration of + your application, if the value of ``PATH_INFO`` does not already + end in a slash, and if the value of ``PATH_INFO`` *plus* a slash + matches any route's path, do an HTTP redirect to the + slash-appended PATH_INFO. Note that this will *lose* ``POST`` + data information (turning it into a GET), so you shouldn't rely on + this to redirect POST requests. + + Add the following to your application's ``configure.zcml`` to use + this view as the Not Found view:: + + <notfound + view="repoze.bfg.view.append_slash_notfound_view"/> + + See also :ref:`changing_the_notfound_view`. + + .. note:: This function is new as of :mod:`repoze.bfg` version 1.1. + + """ + path = request.environ.get('PATH_INFO', '/') + mapper = queryUtility(IRoutesMapper) + if mapper is not None and not path.endswith('/'): + slashpath = path + '/' + for route in mapper.routelist: + if route.match(slashpath) is not None: + return HTTPFound(location=slashpath) + return default_view(context, request, '404 Not Found') |
