Chris McDonough
2015-03-15 3f7e1707d4aa759cca33f0391508f7bf92227e60
Merge branch 'dstufft-custom-append-slash-redirect'
6 files modified
91 ■■■■■ changed files
CHANGES.txt 4 ●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt 2 ●●●●● patch | view | raw | blame | history
docs/narr/urldispatch.rst 4 ●●● patch | view | raw | blame | history
pyramid/config/views.py 25 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_views.py 26 ●●●●● patch | view | raw | blame | history
pyramid/view.py 30 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -119,6 +119,10 @@
  explicitly different from ``request.response``. This does not change the
  API of a renderer. See https://github.com/Pylons/pyramid/pull/1563
- ``Configurator().add_notfound_view()`` will now accept anything that
  implements the ``IResponse`` interface and will use that as the response
  class instead of the default ``HTTPFound``.
Bug Fixes
---------
CONTRIBUTORS.txt
@@ -244,3 +244,5 @@
- Geoffrey T. Dairiki, 2015/02/06
- David Glick, 2015/02/12
- Donald Stufft, 2015/03/15
docs/narr/urldispatch.rst
@@ -842,7 +842,9 @@
application, this view will be invoked 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 pattern.  In this case it does an HTTP redirect to the
slash-appended ``PATH_INFO``.
slash-appended ``PATH_INFO``. In addition you may pass anything that implements
:class:`pyramid.interfaces.IResponse` which will then be used in place of the
default class (:class:`pyramid.httpexceptions.HTTPFound`).
Let's use an example.  If the following routes are configured in your
application:
pyramid/config/views.py
@@ -1703,6 +1703,24 @@
        Pyramid will return the result of the view callable provided as
        ``view``, as normal.
        If the argument provided as ``append_slash`` is not a boolean but
        instead implements :class:`~pyramid.interfaces.IResponse`, the
        append_slash logic will behave as if ``append_slash=True`` was passed,
        but the provided class will be used as the response class instead of
        the default :class:`~pyramid.httpexceptions.HTTPFound` response class
        when a redirect is performed.  For example:
          .. code-block:: python
            from pyramid.httpexceptions import HTTPMovedPermanently
            config.add_notfound_view(append_slash=HTTPMovedPermanently)
        The above means that a redirect to a slash-appended route will be
        attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound`
        being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
        be used` for the redirect response if a slash-appended route is found.
        .. versionchanged:: 1.6
        .. versionadded:: 1.3
        """
        for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
@@ -1737,7 +1755,12 @@
        settings.update(predicates)
        if append_slash:
            view = self._derive_view(view, attr=attr, renderer=renderer)
            view = AppendSlashNotFoundViewFactory(view)
            if IResponse.implementedBy(append_slash):
                view = AppendSlashNotFoundViewFactory(
                    view, redirect_class=append_slash,
                )
            else:
                view = AppendSlashNotFoundViewFactory(view)
            settings['view'] = view
        else:
            settings['attr'] = attr
pyramid/tests/test_config/test_views.py
@@ -1941,7 +1941,7 @@
        from pyramid.renderers import null_renderer
        from zope.interface import implementedBy
        from pyramid.interfaces import IRequest
        from pyramid.httpexceptions import HTTPNotFound
        from pyramid.httpexceptions import HTTPFound, HTTPNotFound
        config = self._makeOne(autocommit=True)
        config.add_route('foo', '/foo/')
        def view(request): return Response('OK')
@@ -1954,6 +1954,30 @@
                                     ctx_iface=implementedBy(HTTPNotFound),
                                     request_iface=IRequest)
        result = view(None, request)
        self.assertTrue(isinstance(result, HTTPFound))
        self.assertEqual(result.location, '/scriptname/foo/?a=1&b=2')
    def test_add_notfound_view_append_slash_custom_response(self):
        from pyramid.response import Response
        from pyramid.renderers import null_renderer
        from zope.interface import implementedBy
        from pyramid.interfaces import IRequest
        from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound
        config = self._makeOne(autocommit=True)
        config.add_route('foo', '/foo/')
        def view(request): return Response('OK')
        config.add_notfound_view(
            view, renderer=null_renderer,append_slash=HTTPMovedPermanently
        )
        request = self._makeRequest(config)
        request.environ['PATH_INFO'] = '/foo'
        request.query_string = 'a=1&b=2'
        request.path = '/scriptname/foo'
        view = self._getViewCallable(config,
                                     ctx_iface=implementedBy(HTTPNotFound),
                                     request_iface=IRequest)
        result = view(None, request)
        self.assertTrue(isinstance(result, HTTPMovedPermanently))
        self.assertEqual(result.location, '/scriptname/foo/?a=1&b=2')
    def test_add_notfound_view_with_view_defaults(self):
pyramid/view.py
@@ -252,10 +252,11 @@
    .. deprecated:: 1.3
    """
    def __init__(self, notfound_view=None):
    def __init__(self, notfound_view=None, redirect_class=HTTPFound):
        if notfound_view is None:
            notfound_view = default_exceptionresponse_view
        self.notfound_view = notfound_view
        self.redirect_class = redirect_class
    def __call__(self, context, request):
        path = decode_path_info(request.environ['PATH_INFO'] or '/')
@@ -268,7 +269,7 @@
                    qs = request.query_string
                    if qs:
                        qs = '?' + qs
                    return HTTPFound(location=request.path+'/'+qs)
                    return self.redirect_class(location=request.path+'/'+qs)
        return self.notfound_view(context, request)
append_slash_notfound_view = AppendSlashNotFoundViewFactory()
@@ -331,6 +332,31 @@
    redirect to the URL implied by the route; if it does not, Pyramid will
    return the result of the view callable provided as ``view``, as normal.
    If the argument provided as ``append_slash`` is not a boolean but
    instead implements :class:`~pyramid.interfaces.IResponse`, the
    append_slash logic will behave as if ``append_slash=True`` was passed,
    but the provided class will be used as the response class instead of
    the default :class:`~pyramid.httpexceptions.HTTPFound` response class
    when a redirect is performed.  For example:
      .. code-block:: python
        from pyramid.httpexceptions import (
            HTTPMovedPermanently,
            HTTPNotFound
            )
        @notfound_view_config(append_slash=HTTPMovedPermanently)
        def aview(request):
            return HTTPNotFound('not found')
    The above means that a redirect to a slash-appended route will be
    attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound`
    being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
    be used` for the redirect response if a slash-appended route is found.
    .. versionchanged:: 1.6
    See :ref:`changing_the_notfound_view` for detailed usage information.
    """