summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-09-30 04:15:39 +0000
committerChris McDonough <chrism@agendaless.com>2009-09-30 04:15:39 +0000
commit3a61a378e03ae3a5b44ce326aac56b159a15bfa1 (patch)
tree1a3e1c968694056fccf400ccd13530fb344d6d60
parentd75fe70228c89e3606e51a4d5775faf549252a90 (diff)
downloadpyramid-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.txt11
-rw-r--r--docs/api/view.rst3
-rw-r--r--repoze/bfg/tests/test_view.py55
-rw-r--r--repoze/bfg/view.py35
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&amp;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')