diff options
Diffstat (limited to 'repoze')
| -rw-r--r-- | repoze/bfg/configuration.py | 17 | ||||
| -rw-r--r-- | repoze/bfg/exceptions.py | 68 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 22 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 57 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_exceptions.py | 45 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 119 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 193 |
7 files changed, 309 insertions, 212 deletions
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index f694d7737..0ab4abcb5 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -38,6 +38,8 @@ from repoze.bfg.interfaces import ITranslationDirectories from repoze.bfg.interfaces import ITraverser from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewClassifier +from repoze.bfg.interfaces import IExceptionResponse +from repoze.bfg.interfaces import IException from repoze.bfg import chameleon_text from repoze.bfg import chameleon_zpt @@ -69,8 +71,7 @@ from repoze.bfg.traversal import DefaultRootFactory from repoze.bfg.traversal import find_interface from repoze.bfg.urldispatch import RoutesMapper from repoze.bfg.view import render_view_to_response -from repoze.bfg.view import default_notfound_view -from repoze.bfg.view import default_forbidden_view +from repoze.bfg.view import default_exceptionresponse_view MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() @@ -408,8 +409,8 @@ class Configurator(object): authorization_policy) for name, renderer in renderers: self.add_renderer(name, renderer) - self.add_view(default_notfound_view, context=NotFound) - self.add_view(default_forbidden_view, context=Forbidden) + self.add_view(default_exceptionresponse_view, + context=IExceptionResponse) if locale_negotiator: registry.registerUtility(locale_negotiator, ILocaleNegotiator) if request_factory: @@ -2312,8 +2313,12 @@ def _attr_wrap(view, accept, order, phash): return attr_view def isexception(o): - return isinstance(o, Exception) or ( - inspect.isclass(o) and issubclass(o, Exception) + if IInterface.providedBy(o): + if IException.isEqualOrExtendedBy(o): + return True + return ( + isinstance(o, Exception) or + (inspect.isclass(o) and (issubclass(o, Exception))) ) # note that ``options`` is a b/w compat alias for ``settings`` and diff --git a/repoze/bfg/exceptions.py b/repoze/bfg/exceptions.py index 00cb76883..9b885d9dc 100644 --- a/repoze/bfg/exceptions.py +++ b/repoze/bfg/exceptions.py @@ -1,7 +1,43 @@ from zope.configuration.exceptions import ConfigurationError as ZCE +from zope.interface import implements -class Forbidden(Exception): - """\ +from repoze.bfg.decorator import reify +from repoze.bfg.interfaces import IExceptionResponse +import cgi + +class ExceptionResponse(Exception): + """ Abstract class to support behaving as a WSGI response object """ + implements(IExceptionResponse) + status = None + + def __init__(self, message=''): + Exception.__init__(self, message) # B / C + self.message = message + + @reify # defer execution until asked explicitly + def app_iter(self): + return [ + """ + <html> + <title>%s</title> + <body> + <h1>%s</h1> + <code>%s</code> + </body> + </html> + """ % (self.status, self.status, cgi.escape(self.message)) + ] + + @reify # defer execution until asked explicitly + def headerlist(self): + return [ + ('Content-Length', str(len(self.app_iter[0]))), + ('Content-Type', 'text/html') + ] + + +class Forbidden(ExceptionResponse): + """ Raise this exception within :term:`view` code to immediately return the :term:`forbidden view` to the invoking user. Usually this is a basic ``401`` page, but the forbidden view can be @@ -11,10 +47,12 @@ class Forbidden(Exception): which should be a string. The value of this string will be placed into the WSGI environment by the router under the ``repoze.bfg.message`` key, for availability to the - :term:`Forbidden View`.""" + :term:`Forbidden View`. + """ + status = '401 Unauthorized' -class NotFound(Exception): - """\ +class NotFound(ExceptionResponse): + """ Raise this exception within :term:`view` code to immediately return the :term:`Not Found view` to the invoking user. Usually this is a basic ``404`` page, but the Not Found view can be @@ -24,7 +62,18 @@ class NotFound(Exception): which should be a string. The value of this string will be placed into the WSGI environment by the router under the ``repoze.bfg.message`` key, for availability to the :term:`Not Found - View`.""" + View`. + """ + status = '404 Not Found' + +class PredicateMismatch(NotFound): + """ + Internal exception (not an API) raised by multiviews when no + view matches. This exception subclasses the ``NotFound`` + exception only one reason: if it reaches the main exception + handler, it should be treated like a ``NotFound`` by any exception + view registrations. + """ class URLDecodeError(UnicodeDecodeError): """ @@ -41,10 +90,3 @@ class ConfigurationError(ZCE): """ Raised when inappropriate input values are supplied to an API method of a :term:`Configurator`""" -class PredicateMismatch(NotFound): - """ Internal exception (not an API) raised by multiviews when no - view matches. This exception subclasses the ``NotFound`` - exception only one reason: if it reaches the main exception - handler, it should be treated like a ``NotFound`` by any exception - view registrations.""" - diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index def957dad..086c93f3a 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -24,6 +24,22 @@ class IWSGIApplicationCreatedEvent(Interface): is called.""" app = Attribute(u"Published application") +class IResponse(Interface): # not an API + status = Attribute('WSGI status code of response') + headerlist = Attribute('List of response headers') + app_iter = Attribute('Iterable representing the response body') + +class IException(Interface): # not an API + """ An interface representing a generic exception """ + +class IExceptionResponse(IException, IResponse): + """ An interface representing a WSGI response which is also an + exception object. Register an exception view using this interface + as a ``context`` to apply the registered view for all exception + types raised by :mod:`repoze.bfg` internally + (:class:`repoze.bfg.exceptions.NotFound` and + :class:`repoze.bfg.exceptions.Forbidden`).""" + # internal interfaces class IRequest(Interface): @@ -35,11 +51,6 @@ class IRouteRequest(Interface): """ *internal only* interface used as in a utility lookup to find route-specific interfaces. Not an API.""" -class IResponse(Interface): - status = Attribute('WSGI status code of response') - headerlist = Attribute('List of response headers') - app_iter = Attribute('Iterable representing the response body') - class IAuthenticationPolicy(Interface): """ An object representing a BFG authentication policy. """ def authenticated_userid(request): @@ -271,3 +282,4 @@ class ILocaleNegotiator(Interface): class ITranslationDirectories(Interface): """ A list object representing all known translation directories for an application""" + diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 2f001ba8d..1eae66094 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -194,11 +194,9 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(reg.notify(1), None) self.assertEqual(reg.events, (1,)) - def test_setup_registry_registers_default_exception_views(self): - from repoze.bfg.exceptions import NotFound - from repoze.bfg.exceptions import Forbidden - from repoze.bfg.view import default_notfound_view - from repoze.bfg.view import default_forbidden_view + def test_setup_registry_registers_default_exceptionresponse_view(self): + from repoze.bfg.interfaces import IExceptionResponse + from repoze.bfg.view import default_exceptionresponse_view class DummyRegistry(object): def registerUtility(self, *arg, **kw): pass @@ -207,10 +205,25 @@ class ConfiguratorTests(unittest.TestCase): views = [] config.add_view = lambda *arg, **kw: views.append((arg, kw)) config.setup_registry() - self.assertEqual(views[0], ((default_notfound_view,), - {'context':NotFound})) - self.assertEqual(views[1], ((default_forbidden_view,), - {'context':Forbidden})) + self.assertEqual(views[0], ((default_exceptionresponse_view,), + {'context':IExceptionResponse})) + + def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self): + from zope.interface import implementedBy + from repoze.bfg.interfaces import IRequest + from repoze.bfg.exceptions import NotFound + from repoze.bfg.registry import Registry + reg = Registry() + config = self._makeOne(reg) + config.setup_registry() # registers IExceptionResponse default view + def myview(context, request): + return 'OK' + config.add_view(myview, context=NotFound) + request = self._makeRequest(config) + view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound), + request_iface=IRequest) + result = view(None, request) + self.assertEqual(result, 'OK') def test_setup_registry_custom_settings(self): from repoze.bfg.registry import Registry @@ -3702,6 +3715,32 @@ class TestDottedNameResolver(unittest.TestCase): self.assertEqual(e.args[0], "The dotted name 'cant.be.found' cannot be imported") +class Test_isexception(unittest.TestCase): + def _callFUT(self, ob): + from repoze.bfg.configuration import isexception + return isexception(ob) + + def test_is_exception_instance(self): + class E(Exception): + pass + e = E() + self.assertEqual(self._callFUT(e), True) + + def test_is_exception_class(self): + class E(Exception): + pass + self.assertEqual(self._callFUT(E), True) + + def test_is_IException(self): + from repoze.bfg.interfaces import IException + self.assertEqual(self._callFUT(IException), True) + + def test_is_IException_subinterface(self): + from repoze.bfg.interfaces import IException + class ISubException(IException): + pass + self.assertEqual(self._callFUT(ISubException), True) + class DummyRequest: subpath = () def __init__(self): diff --git a/repoze/bfg/tests/test_exceptions.py b/repoze/bfg/tests/test_exceptions.py new file mode 100644 index 000000000..4091eb941 --- /dev/null +++ b/repoze/bfg/tests/test_exceptions.py @@ -0,0 +1,45 @@ +import unittest + +class TestExceptionResponse(unittest.TestCase): + def _makeOne(self, message): + from repoze.bfg.exceptions import ExceptionResponse + return ExceptionResponse(message) + + def test_app_iter(self): + exc = self._makeOne('') + self.failUnless('<code></code>' in exc.app_iter[0]) + + def test_headerlist(self): + exc = self._makeOne('') + headerlist = exc.headerlist + headerlist.sort() + app_iter = exc.app_iter + clen = str(len(app_iter[0])) + self.assertEqual(headerlist[0], ('Content-Length', clen)) + self.assertEqual(headerlist[1], ('Content-Type', 'text/html')) + + def test_withmessage(self): + exc = self._makeOne('abc&123') + self.failUnless('<code>abc&123</code>' in exc.app_iter[0]) + +class TestNotFound(unittest.TestCase): + def _makeOne(self, message): + from repoze.bfg.exceptions import NotFound + return NotFound(message) + + def test_it(self): + from repoze.bfg.exceptions import ExceptionResponse + e = self._makeOne('notfound') + self.failUnless(isinstance(e, ExceptionResponse)) + self.assertEqual(e.status, '404 Not Found') + +class TestForbidden(unittest.TestCase): + def _makeOne(self, message): + from repoze.bfg.exceptions import Forbidden + return Forbidden(message) + + def test_it(self): + from repoze.bfg.exceptions import ExceptionResponse + e = self._makeOne('unauthorized') + self.failUnless(isinstance(e, ExceptionResponse)) + self.assertEqual(e.status, '401 Unauthorized') diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index 27f468c74..18a46a205 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -342,59 +342,7 @@ class TestBFGViewDecorator(unittest.TestCase): self.assertEqual(settings[0]['renderer'], 'repoze.bfg.tests:fixtures/minimal.pt') -class TestDefaultView(BaseTest): - def test_no_registry_on_request(self): - request = None - context = Exception() - response = self._callFUT(context, request) - self.assertEqual(response.status, self.status) - self.failUnless('<code></code>' in response.body) - - def test_nomessage(self): - request = self._makeRequest() - context = Exception() - response = self._callFUT(context, request) - self.assertEqual(response.status, self.status) - self.failUnless('<code></code>' in response.body) - - def test_withmessage(self): - request = self._makeRequest() - context = Exception('abc&123') - response = self._callFUT(context, request) - self.assertEqual(response.status, self.status) - self.failUnless('<code>abc&123</code>' in response.body) - - def test_context_not_exception(self): - request = self._makeRequest() - request.exception = Exception('woo') - context = None - response = self._callFUT(context, request) - self.assertEqual(response.status, self.status) - self.failUnless('<code>woo</code>' in response.body) - - def test_msg_exception_raised(self): - request = self._makeRequest() - context = None - response = self._callFUT(context, request) - self.assertEqual(response.status, self.status) - self.failUnless('<code></code>' in response.body) - -class TestDefaultForbiddenView(TestDefaultView, unittest.TestCase): - status = '401 Unauthorized' - - def _callFUT(self, context, request): - from repoze.bfg.view import default_forbidden_view - return default_forbidden_view(context, request) - - -class TestDefaultNotFoundView(TestDefaultView, unittest.TestCase): - status = '404 Not Found' - - def _callFUT(self, context, request): - from repoze.bfg.view import default_notfound_view - return default_notfound_view(context, request) - -class AppendSlashNotFoundView(BaseTest, unittest.TestCase): +class Test_append_slash_notfound_view(BaseTest, unittest.TestCase): def _callFUT(self, context, request): from repoze.bfg.view import append_slash_notfound_view return append_slash_notfound_view(context, request) @@ -417,49 +365,81 @@ class AppendSlashNotFoundView(BaseTest, unittest.TestCase): def test_context_is_not_exception(self): request = self._makeRequest(PATH_INFO='/abc') - request.exception = Exception('halloo') + request.exception = ExceptionResponse() context = DummyContext() response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') - self.failUnless('halloo' in response.body) + self.assertEqual(response.app_iter, ['Not Found']) def test_no_mapper(self): request = self._makeRequest(PATH_INFO='/abc') - context = Exception() + context = ExceptionResponse() response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') - def test_custom_notfound_view(self): - request = self._makeRequest(PATH_INFO='/abc') - def notfound(exc, request): - return 'abc' - request.custom_notfound_view = notfound - context = Exception() - response = self._callFUT(context, request) - self.assertEqual(response, 'abc') - def test_no_path(self): request = self._makeRequest() - context = Exception() + context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') def test_mapper_path_already_slash_ending(self): request = self._makeRequest(PATH_INFO='/abc/') - context = Exception() + context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') def test_matches(self): request = self._makeRequest(PATH_INFO='/abc') - context = Exception() + context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '302 Found') self.assertEqual(response.location, '/abc/') +class TestAppendSlashNotFoundViewFactory(BaseTest, unittest.TestCase): + def _makeOne(self, notfound_view): + from repoze.bfg.view import AppendSlashNotFoundViewFactory + return AppendSlashNotFoundViewFactory(notfound_view) + + def test_custom_notfound_view(self): + request = self._makeRequest(PATH_INFO='/abc') + context = ExceptionResponse() + def custom_notfound(context, request): + return 'OK' + view = self._makeOne(custom_notfound) + response = view(context, request) + self.assertEqual(response, 'OK') + +class Test_default_exceptionresponse_view(unittest.TestCase): + def _callFUT(self, context, request): + from repoze.bfg.view import default_exceptionresponse_view + return default_exceptionresponse_view(context, request) + + def test_is_exception(self): + context = Exception() + result = self._callFUT(context, None) + self.failUnless(result is context) + + def test_is_not_exception_no_request_exception(self): + context = object() + request = DummyRequest() + result = self._callFUT(context, request) + self.failUnless(result is context) + + def test_is_not_exception_request_exception(self): + context = object() + request = DummyRequest() + request.exception = 'abc' + result = self._callFUT(context, request) + self.assertEqual(result, 'abc') + +class ExceptionResponse(Exception): + status = '404 Not Found' + app_iter = ['Not Found'] + headerlist = [] class DummyContext: pass @@ -469,6 +449,9 @@ def make_view(response): return response return view +class DummyRequest: + pass + class DummyResponse: status = '200 OK' headerlist = () diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 30076b775..1d1839530 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -1,4 +1,3 @@ -import cgi import mimetypes import os @@ -12,7 +11,6 @@ import os if hasattr(mimetypes, 'init'): mimetypes.init() -from webob import Response from webob.exc import HTTPFound import venusian @@ -20,7 +18,6 @@ import venusian from zope.deprecation import deprecated from zope.interface import providedBy -from repoze.bfg.interfaces import IResponseFactory from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewClassifier @@ -447,129 +444,103 @@ class bfg_view(object): return wrapped -def default_view(context, request, status): +def default_exceptionresponse_view(context, request): if not isinstance(context, Exception): - # backwards compat for a default_view registered via + # backwards compat for an exception response view registered via # config.set_notfound_view or config.set_forbidden_view # instead of as a proper exception view - context = getattr(request, 'exception', None) - try: - msg = cgi.escape('%s' % context.args[0]) - except Exception: - msg = '' - html = """ - <html> - <title>%s</title> - <body> - <h1>%s</h1> - <code>%s</code> - </body> - </html> - """ % (status, status, msg) - headers = [('Content-Length', str(len(html))), - ('Content-Type', 'text/html')] - try: - registry = request.registry - except AttributeError: - registry = get_current_registry() - response_factory = registry.queryUtility(IResponseFactory, - default=Response) - return response_factory(status = status, - headerlist = headers, - app_iter = [html]) - -def default_forbidden_view(context, request): - return default_view(context, request, '401 Unauthorized') - -def default_notfound_view(context, request): - return default_view(context, request, '404 Not Found') - -def append_slash_notfound_view(context, request): - """For behavior like Django's ``APPEND_SLASH=True``, use this view - as the :term:`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 the ``PATH_INFO`` WSGI - environment variable 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. - - If you use :term:`ZCML`, add the following to your application's - ``configure.zcml`` to use this view as the Not Found view:: + context = getattr(request, 'exception', context) + return context + +class AppendSlashNotFoundViewFactory(object): + """ There can only be one :term:`Not Found view` in any + :mod:`repoze.bfg application. Even if you use + :func:`repoze.bfg.view.append_slash_notfound_view` as the Not + Found view, :mod:`repoze.bfg` still must generate a ``404 Not + Found`` response when it cannot redirect to a slash-appended URL; + this not found response will be visible to site users. + + If you don't care what this 404 response looks like, and you only + need redirections to slash-appended route URLs, you may use the + :func:`repoze.bfg.view.append_slash_notfound_view` object as the + Not Found view. However, if you wish to use a *custom* notfound + view callable when a URL cannot be redirected to a slash-appended + URL, you may wish to use an instance of this class as the Not + Found view, supplying a :term:`view callable` to be used as the + custom notfound view as the first argument to its constructor. + For instance: - <view - context="repoze.bfg.exceptions.NotFound" - view="repoze.bfg.view.append_slash_notfound_view"/> + .. code-block:: python - Or use the - :meth:`repoze.bfg.configuration.Configurator.add_view` - method if you don't use ZCML:: + from repoze.bfg.exceptions import NotFound + from repoze.bfg.view import AppendSlashNotFoundViewFactory - from repoze.bfg.exceptions import NotFound - from repoze.bfg.view import append_slash_notfound_view - config.add_view(append_slash_notfound_view, context=NotFound) + def notfound_view(context, request): + return HTTPNotFound('It aint there, stop trying!') - See also :ref:`changing_the_notfound_view`. + custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) + config.add_view(custom_append_slash, context=NotFound) - .. note:: This function is new as of :mod:`repoze.bfg` version 1.1. + The ``notfound_view`` supplied must adhere to the two-argument + view callable calling convention of ``(context, request)`` + (``context`` will be the exception object). - There can only be one Not Found view in any :mod:`repoze.bfg - application. If you use ``append_slash_notfound_view`` as the Not - Found view, it still must generate a NotFound response when it - cannot redirect to a slash-appended URL; this not found response - will be visible to site users. + .. note:: This class is new as of :mod:`repoze.bfg` version 1.3. - If you wish to use a custom notfound view callable when - ``append_slash_notfound_view`` does not redirect to a - slash-appended URL, use a wrapper function as the - :exc:`repoze.bfg.exceptions.NotFound` view; have this wrapper - attach a :term:`view callable` which returns a response to the - request object named ``custom_notfound_view`` before calling - ``append_slash_notfound_view``. For example: + """ + def __init__(self, notfound_view=None): + if notfound_view is None: + notfound_view = default_exceptionresponse_view + self.notfound_view = notfound_view + + def __call__(self, context, request): + if not isinstance(context, Exception): + # backwards compat for an append_notslash_view registered via + # config.set_notfound_view instead of as a proper exception view + context = getattr(request, 'exception', None) + path = request.environ.get('PATH_INFO', '/') + registry = request.registry + mapper = registry.queryUtility(IRoutesMapper) + if mapper is not None and not path.endswith('/'): + slashpath = path + '/' + for route in mapper.get_routes(): + if route.match(slashpath) is not None: + return HTTPFound(location=slashpath) + return self.notfound_view(context, request) + +append_slash_notfound_view = AppendSlashNotFoundViewFactory() +append_slash_notfound_view.__doc__ = """\ +For behavior like Django's ``APPEND_SLASH=True``, use this view as the +:term:`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 the ``PATH_INFO`` WSGI environment +variable 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. + +If you use :term:`ZCML`, add the following to your application's +``configure.zcml`` to use this view as the Not Found view:: - .. code-block:: python + <view + context="repoze.bfg.exceptions.NotFound" + view="repoze.bfg.view.append_slash_notfound_view"/> - from webob.exc import HTTPNotFound - from repoze.bfg.exceptions import NotFound - from repoze.bfg.view import append_slash_notfound_view +Or use the +:meth:`repoze.bfg.configuration.Configurator.add_view` +method if you don't use ZCML:: - def notfound_view(exc, request): - def fallback_notfound_view(exc, request): - return HTTPNotFound('It aint there, stop trying!') - request.fallback_notfound_view = fallback_notfound_view - return append_slash_notfound_view(exc, request) + from repoze.bfg.exceptions import NotFound + from repoze.bfg.view import append_slash_notfound_view + config.add_view(append_slash_notfound_view, context=NotFound) - config.add_view(notfound_view, context=NotFound) +See also :ref:`changing_the_notfound_view`. - ``custom_notfound_view`` must adhere to the two-argument view - callable calling convention of ``(context, request)`` (``context`` - will be the exception object). +.. note:: This function is new as of :mod:`repoze.bfg` version 1.1. +""" - If ``custom_notfound_view`` is not found on the request object, a - default notfound response will be generated when the - ``append_slash_notfound_view`` doesn't redirect to a - slash-appended URL. - .. note:: The checking for ``request.custom_notfound_view`` by - ``append_slash_notfound_view`` is new as of :mod:`repoze.bfg` - version 1.3. - """ - if not isinstance(context, Exception): - # backwards compat for an append_notslash_view registered via - # config.set_notfound_view instead of as a proper exception view - context = getattr(request, 'exception', None) - path = request.environ.get('PATH_INFO', '/') - registry = request.registry - mapper = registry.queryUtility(IRoutesMapper) - if mapper is not None and not path.endswith('/'): - slashpath = path + '/' - for route in mapper.get_routes(): - if route.match(slashpath) is not None: - return HTTPFound(location=slashpath) - notfound_view = getattr(request, 'custom_notfound_view', - default_notfound_view) - return notfound_view(context, request) |
