Allow append_slash to use a subrequest rather than a redirect, closes #2607
| | |
| | | Features |
| | | -------- |
| | | |
| | | - Allow the ``append_slash`` argument of ``config.add_notfound_view`` to be |
| | | the special value ``pyramid.view.UseSubrequest``, which will cause |
| | | Pyramid to do a subrequest rather than a redirect when a slash-appended |
| | | route is found associated with a view, rather than a redirect. |
| | | |
| | | - Add a ``_depth`` and ``_category`` arguments to all of the venusian |
| | | decorators. The ``_category`` argument can be used to affect which actions |
| | | are registered when performing a ``config.scan(..., category=...)`` with a |
| | |
| | | .. autoclass:: exception_view_config |
| | | :members: |
| | | |
| | | .. attribute:: UseSubrequest |
| | | |
| | | Object passed to :meth:`pyramid.config.Configurator.add_notfound_view` as |
| | | the value to ``append_slash`` if you wish to cause a :term:`subrequest` |
| | | rather than a redirect. |
| | |
| | | |
| | | context manager |
| | | A context manager is an object that defines the runtime context to be established when executing a :ref:`with <python:with>` statement in Python. The context manager handles the entry into, and the exit from, the desired runtime context for the execution of the block of code. Context managers are normally invoked using the ``with`` statement, but can also be used by directly invoking their methods. Pyramid adds context managers for :class:`pyramid.config.Configurator`, :meth:`pyramid.interfaces.IRouter.request_context`, :func:`pyramid.paster.bootstrap`, :func:`pyramid.scripting.prepare`, and :func:`pyramid.testing.testConfig`. See also the Python documentation for :ref:`With Statement Context Managers <python:context-managers>` and :pep:`343`. |
| | | |
| | | subrequest |
| | | A Pyramid concept that implies that as the result of an HTTP request |
| | | another "internal" request can be issued to find a view without |
| | | requiring cooperation from the client in the form of e.g. a redirect. See |
| | | :ref:`subrequest_chapter`. |
| | | |
| | |
| | | |
| | | .. versionadded:: 1.4 |
| | | |
| | | :app:`Pyramid` allows you to invoke a subrequest at any point during the |
| | | processing of a request. Invoking a subrequest allows you to obtain a |
| | | :app:`Pyramid` allows you to invoke a :term:`subrequest` at any point during |
| | | the processing of a request. Invoking a subrequest allows you to obtain a |
| | | :term:`response` object from a view callable within your :app:`Pyramid` |
| | | application while you're executing a different view callable within the same |
| | | application. |
| | |
| | | config.add_route('hasslash', 'has_slash/') |
| | | config.scan() |
| | | |
| | | .. warning:: |
| | | You **should not** rely on the default mechanism to redirect ``POST`` requests. |
| | | The redirect of the slash-appending :term:`Not Found View` will turn a ``POST`` |
| | | request into a ``GET``, losing any ``POST`` data in the original request. But |
| | | if the argument supplied as ``append_slash`` is the special object |
| | | :attr:`~pyramid.views.UseSubrequest`, a :term:`subrequest` will be issued |
| | | instead of a redirect. This makes it possible to successfully invoke a |
| | | slash-appended URL without losing the HTTP verb, POST data, or any other |
| | | information contained in the original request. Instead of returning a redirect |
| | | response when a slash-appended route is detected during the not-found |
| | | processing, Pyramid will call the view associated with the slash-appended route |
| | | "under the hood" and will return whatever response is returned by that view. |
| | | This has the potential downside that both URLs (the slash-appended and the |
| | | non-slash-appended URLs) in an application will be "canonical" to clients; they |
| | | will behave exactly the same, and the client will never be notified that the |
| | | slash-appended URL is "better than" the non-slash-appended URL by virtue of a |
| | | redirect. It, however, has the upside that a POST request with a body can be |
| | | handled successfully with an append-slash during notfound processing. |
| | | |
| | | You **should not** rely on this mechanism to redirect ``POST`` requests. |
| | | The redirect of the slash-appending :term:`Not Found View` will turn a |
| | | ``POST`` request into a ``GET``, losing any ``POST`` data in the original |
| | | request. |
| | | .. versionchanged:: 1.10 |
| | | Added the functionality to use a subrequest rather than a redirect by |
| | | using :class:`~pyramid.views.UseSubrequest` as an argument to |
| | | ``append_slash``. |
| | | |
| | | See :ref:`view_module` and :ref:`changing_the_notfound_view` for a more |
| | | general description of how to configure a view and/or a :term:`Not Found View`. |
| | |
| | | |
| | | from pyramid.url import parse_url_overrides |
| | | |
| | | from pyramid.view import AppendSlashNotFoundViewFactory |
| | | from pyramid.view import ( |
| | | AppendSlashNotFoundViewFactory, |
| | | UseSubrequest, |
| | | ) |
| | | |
| | | import pyramid.util |
| | | from pyramid.util import ( |
| | |
| | | settings.update(view_options) |
| | | if append_slash: |
| | | view = self._derive_view(view, attr=attr, renderer=renderer) |
| | | if IResponse.implementedBy(append_slash): |
| | | if (append_slash is UseSubrequest or |
| | | IResponse.implementedBy(append_slash)): |
| | | view = AppendSlashNotFoundViewFactory( |
| | | view, redirect_class=append_slash, |
| | | ) |
| | |
| | | from pyramid.view import notfound_view_config, view_config |
| | | from pyramid.view import notfound_view_config, view_config, UseSubrequest |
| | | from pyramid.response import Response |
| | | |
| | | @notfound_view_config(route_name='foo', append_slash=True) |
| | |
| | | def foo2(request): |
| | | return Response('OK foo2') |
| | | |
| | | @notfound_view_config(route_name='wiz', append_slash=UseSubrequest) |
| | | def wiz_notfound(request): # pragma: no cover |
| | | return Response('wiz_notfound') |
| | | |
| | | @view_config(route_name='wiz2') |
| | | def wiz2(request): |
| | | return Response('OK wiz2') |
| | | |
| | | def includeme(config): |
| | | config.add_route('foo', '/foo') |
| | | config.add_route('foo2', '/foo/') |
| | | config.add_route('bar', '/bar/') |
| | | config.add_route('baz', '/baz') |
| | | config.add_route('wiz', '/wiz') |
| | | config.add_route('wiz2', '/wiz/') |
| | | config.scan('pyramid.tests.pkgs.notfoundview') |
| | | |
| | |
| | | self.assertRaises(ConfigurationError, |
| | | config.add_notfound_view, for_='foo') |
| | | |
| | | def test_add_notfound_view_append_slash(self): |
| | | def test_add_notfound_view_append_slash_use_subrequest(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 HTTPFound, HTTPNotFound |
| | | from pyramid.httpexceptions import HTTPNotFound |
| | | from pyramid.view import UseSubrequest |
| | | 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=True) |
| | | config.add_notfound_view( |
| | | view, |
| | | renderer=null_renderer, |
| | | append_slash=UseSubrequest, |
| | | ) |
| | | request = self._makeRequest(config) |
| | | request.environ['PATH_INFO'] = '/foo' |
| | | request.query_string = 'a=1&b=2' |
| | | request.path = '/scriptname/foo' |
| | | def copy(): |
| | | request.copied = True |
| | | return request |
| | | request.copy = copy |
| | | resp = Response() |
| | | def invoke_subrequest(req, **kw): |
| | | self.assertEqual(req.path_info, '/scriptname/foo/') |
| | | self.assertEqual(req.query_string, 'a=1&b=2') |
| | | self.assertEqual(kw, {'use_tweens':True}) |
| | | return resp |
| | | request.invoke_subrequest = invoke_subrequest |
| | | view = self._getViewCallable(config, |
| | | exc_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') |
| | | self.assertTrue(request.copied) |
| | | self.assertEqual(result, resp) |
| | | |
| | | def test_add_notfound_view_append_slash_custom_response(self): |
| | | def test_add_notfound_view_append_slash_using_redirect(self): |
| | | from pyramid.response import Response |
| | | from pyramid.renderers import null_renderer |
| | | from zope.interface import implementedBy |
| | |
| | | self.assertTrue(b'OK foo2' in res.body) |
| | | res = self.testapp.get('/baz', status=200) |
| | | self.assertTrue(b'baz_notfound' in res.body) |
| | | res = self.testapp.get('/wiz', status=200) # uses subrequest |
| | | self.assertTrue(b'OK wiz2' in res.body) |
| | | |
| | | class TestForbiddenView(IntegrationBase, unittest.TestCase): |
| | | package = 'pyramid.tests.pkgs.forbiddenview' |
| | |
| | | |
| | | _marker = object() |
| | | |
| | | class _UseSubrequest(object): |
| | | """ Object passed to :meth:`pyramid.config.Configurator.add_notfound_view` |
| | | as the value to ``append_slash`` if you wish to cause a subrequest |
| | | rather than a redirect """ |
| | | |
| | | UseSubrequest = _UseSubrequest() # singleton |
| | | |
| | | def render_view_to_response(context, request, name='', secure=True): |
| | | """ Call the :term:`view callable` configured with a :term:`view |
| | | configuration` that matches the :term:`view name` ``name`` |
| | |
| | | view callable calling convention of ``(context, request)`` |
| | | (``context`` will be the exception object). |
| | | |
| | | .. deprecated:: 1.3 |
| | | |
| | | """ |
| | | def __init__(self, notfound_view=None, redirect_class=HTTPFound): |
| | | if notfound_view is None: |
| | |
| | | slashpath = path + '/' |
| | | for route in mapper.get_routes(): |
| | | if route.match(slashpath) is not None: |
| | | qs = request.query_string |
| | | if qs: |
| | | qs = '?' + qs |
| | | return self.redirect_class(location=request.path + '/' + qs) |
| | | if self.redirect_class is UseSubrequest: |
| | | subreq = request.copy() |
| | | subreq.path_info = request.path + '/' |
| | | return request.invoke_subrequest( |
| | | subreq, |
| | | use_tweens=True |
| | | ) |
| | | else: |
| | | qs = request.query_string |
| | | if qs: |
| | | qs = '?' + qs |
| | | return self.redirect_class( |
| | | location=request.path + '/' + qs |
| | | ) |
| | | |
| | | return self.notfound_view(context, request) |
| | | |
| | | append_slash_notfound_view = AppendSlashNotFoundViewFactory() |
| | |
| | | being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will |
| | | be used` for the redirect response if a slash-appended route is found. |
| | | |
| | | If the argument supplied as ``append_slash`` is the special object |
| | | :attr:`~pyramid.views.UseSubrequest`, a :term:`subrequest` will be issued |
| | | instead of a redirect. This makes it possible to successfully invoke a |
| | | slash-appended URL without losing the HTTP verb, POST data, or any other |
| | | information contained in the original request. Instead of returning a |
| | | redirect response when a slash-appended route is detected during the |
| | | not-found processing, Pyramid will call the view associated with the |
| | | slash-appended route "under the hood" and will return whatever response is |
| | | returned by that view. This has the potential downside that both URLs (the |
| | | slash-appended and the non-slash-appended URLs) in an application will be |
| | | "canonical" to clients; they will behave exactly the same, and the client |
| | | will never be notified that the slash-appended URL is "better than" the |
| | | non-slash-appended URL by virtue of a redirect. It, however, has the |
| | | upside that a POST request with a body can be handled successfully |
| | | with an append-slash during notfound processing. |
| | | |
| | | See :ref:`changing_the_notfound_view` for detailed usage information. |
| | | |
| | | .. versionchanged:: 1.9.1 |
| | | Added the ``_depth`` and ``_category`` arguments. |
| | | |
| | | .. versionchanged:: 1.10 |
| | | Added the functionality to use a subrequest rather than a redirect by |
| | | using :class:`~pyramid.views.UseSubrequest` as an argument to |
| | | ``append_slash``. |
| | | |
| | | """ |
| | | |
| | | venusian = venusian |