diff options
| author | Michael Merickel <michael@merickel.org> | 2016-03-14 22:08:04 -0500 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2016-03-14 22:08:04 -0500 |
| commit | fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41 (patch) | |
| tree | 9797833c4ffdbc82e2ac8630329a472c23762033 | |
| parent | 7fc181d89e9553b152618066490d1fb659d45e13 (diff) | |
| download | pyramid-fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41.tar.gz pyramid-fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41.tar.bz2 pyramid-fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41.zip | |
first cut at documenting view derivers
| -rw-r--r-- | docs/narr/extconfig.rst | 1 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 140 | ||||
| -rw-r--r-- | pyramid/config/views.py | 14 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_derivations.py | 13 |
4 files changed, 153 insertions, 15 deletions
diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst index fee8d0d3a..af7d0a349 100644 --- a/docs/narr/extconfig.rst +++ b/docs/narr/extconfig.rst @@ -259,6 +259,7 @@ Pre-defined Phases - :meth:`pyramid.config.Configurator.add_route_predicate` - :meth:`pyramid.config.Configurator.add_subscriber_predicate` - :meth:`pyramid.config.Configurator.add_view_predicate` +- :meth:`pyramid.config.Configurator.add_view_deriver` - :meth:`pyramid.config.Configurator.set_authorization_policy` - :meth:`pyramid.config.Configurator.set_default_permission` - :meth:`pyramid.config.Configurator.set_view_mapper` diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 7ff119b53..13daed1fc 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1547,3 +1547,143 @@ in every subscriber registration. It is not the responsibility of the predicate author to make every predicate make sense for every event type; it is the responsibility of the predicate consumer to use predicates that make sense for a particular event type registration. + +.. index:: + single: view derivers + +.. _view_derivers: + +View Derivers +------------- + +Every URL processed by :app:`Pyramid` is matched against a custom view +pipeline. See :ref:`router_chapter` for how this works. The view pipeline +itself is built from the user-supplied :term:`view callable` which is then +composed with :term:`view derivers <view deriver>`. A view deriver is a +composable element of the view pipeline which is used to wrap a view with +added functionality. View derivers are very similar to the ``decorator`` +argument to :meth:`pyramid.config.Configurator.add_view` except that they have +the option to execute for every view in the application. + +Built-in View Derivers +~~~~~~~~~~~~~~~~~~~~~~ + +There are several builtin view derivers that :app:`Pyramid` will automatically +apply to any view. They are defined in order from closest to furthest from +the user-defined :term:`view callable`: + +``mapped_view`` + + Applies the :term:`view mapper` defined by the ``mapper`` option or the + application's default view mapper to the :term:`view callable`. This + is always the closest deriver to the user-defined view and standardizes the + view pipeline interface to accept ``(context, request)`` from all previous + view derivers. + +``decorated_view`` + + Wraps the view with the decorators from the ``decorator`` option. + +``http_cached_view`` + + Applies cache control headers to the response defined by the ``http_cache`` + option. This element is a noop if the ``pyramid.prevent_http_cache`` setting + is enabled or the ``http_cache`` option is ``None``. + +``owrapped_view`` + + Invokes the wrapped view defined by the ``wrapper`` option. + +``secured_view`` + + Enforce the ``permission`` defined on the view. This element is a noop if + no permission is defined. Note there will always be a permission defined + if a default permission was assigned via + :meth:`pyramid.config.Configurator.set_default_permission`. + +``authdebug_view`` + + Used to output useful debugging information when + ``pyramid.debug_authorization`` is enabled. This element is a noop otherwise. + +Custom View Derivers +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.7 + +It is possible to define custom view derivers which will affect all views in +an application. There are many uses for this but most will likely be centered +around monitoring and security. In order to register a custom +:term:`view deriver` you should create a callable that conforms to the +:class:`pyramid.interfaces.IViewDeriver` interface. For example, below +is a callable that can provide timing information for the view pipeline: + +.. code-block:: python + :linenos: + + import time + + def timing_view(view, info): + def wrapper_view(context, request): + start = time.time() + response = view(context, request) + end = time.time() + response.headers['X-View-Performance'] = '%.3f' % (end - start,) + return wrapper_view + + config.add_view_deriver('timing_view', timing_view) + +View derivers are unique in that they have access to most of the options +passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what +to do and they have a chance to affect every view in the application. + +Let's look at one more example which will protect views by requiring a CSRF +token unless ``disable_csrf=True`` is passed to the view: + +.. code-block:: python + :linenos: + + from pyramid.session import check_csrf_token + + def require_csrf_view(view, info): + wrapper_view = view + if not info.options.get('disable_csrf', False): + def wrapper_view(context, request): + if request.method == 'POST': + check_csrf_token(request) + return view(context, request) + return wrapper_view + + require_csrf_view.options = ('disable_csrf',) + + config.add_view_deriver('require_csrf_view', require_csrf_view) + + def myview(request): + return 'protected' + + def my_unprotected_view(request): + return 'unprotected' + + config.add_view(myview, name='safe', renderer='string') + config.add_view(my_unprotected_, name='unsafe', disable_csrf=True, renderer='string') + +Navigating to ``/safe`` with a POST request will then fail when the call to +:func:`pyramid.session.check_csrf_token` raises a +:class:`pyramid.exceptions.BadCSRFToken` exception. However, ``/unsafe`` will +not error. + +Ordering View Derivers +~~~~~~~~~~~~~~~~~~~~~~ + +By default, every new view deriver is added between the ``decorated_view`` +and ``mapped_view`` built-in derivers. It is possible to customize this +ordering using the ``over`` and ``under`` options. Each option can use the +names of other view derivers in order to specify an ordering. There should +rarely be a reason to worry about the ordering of the derivers. + +It is not possible to add a deriver OVER the ``mapped_view`` as the +:term:`view mapper` is intimately tied to the signature of the user-defined +:term:`view callable`. If you simply need to know what the original view +callable was, it can be found as ``info.original_view`` on the provided +:class:`pyramid.interfaces.IViewDeriverInfo` object passed to every view +deriver. diff --git a/pyramid/config/views.py b/pyramid/config/views.py index c3e5d360e..b762a711c 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -654,19 +654,19 @@ class ViewsConfiguratorMixin(object): view_options: Pass a key/value pair here to use a third-party predicate or set a - value for a view derivative option registered via - :meth:`pyramid.config.Configurator.add_view_predicate` or - :meth:`pyramid.config.Configurator.add_view_option`. More than - one key/value pair can be used at the same time. See + value for a view deriver. See + :meth:`pyramid.config.Configurator.add_view_predicate` and + :meth:`pyramid.config.Configurator.add_view_deriver`. See :ref:`view_and_route_predicates` for more information about - third-party predicates. + third-party predicates and :ref:`view_derivers` for information + about view derivers. .. versionadded: 1.4a1 .. versionchanged: 1.7 - Support setting arbitrary view options. Previously, only - predicate values could be supplied. + Support setting view deriver options. Previously, only custom + view predicate values could be supplied. """ if custom_predicates: diff --git a/pyramid/tests/test_config/test_derivations.py b/pyramid/tests/test_config/test_derivations.py index 12fcc300a..7202c5bb6 100644 --- a/pyramid/tests/test_config/test_derivations.py +++ b/pyramid/tests/test_config/test_derivations.py @@ -1,7 +1,12 @@ import unittest +from zope.interface import implementer from pyramid import testing from pyramid.exceptions import ConfigurationError +from pyramid.interfaces import ( + IResponse, + IRequest, + ) class TestDeriveView(unittest.TestCase): @@ -1318,13 +1323,6 @@ class TestDeriverIntegration(unittest.TestCase): ConfigurationError, lambda: self.config.add_view(lambda r: {}, deriv1='test1')) - -from zope.interface import implementer -from pyramid.interfaces import ( - IResponse, - IRequest, - ) - @implementer(IResponse) class DummyResponse(object): content_type = None @@ -1374,4 +1372,3 @@ def assert_similar_datetime(one, two): two_attr = getattr(two, attr) if not one_attr == two_attr: # pragma: no cover raise AssertionError('%r != %r in %s' % (one_attr, two_attr, attr)) - |
