diff options
| author | Michael Merickel <mmerickel@users.noreply.github.com> | 2016-04-10 12:49:16 -0500 |
|---|---|---|
| committer | Michael Merickel <mmerickel@users.noreply.github.com> | 2016-04-10 12:49:16 -0500 |
| commit | b6187b09fb50ff7599815f2ffc2b061793d1f52b (patch) | |
| tree | fcf36691ccfde6e36e5a4af8bfb3d365e0458668 /docs/narr | |
| parent | ef38dee074c0d7646c0d085fe7d4fd9473157954 (diff) | |
| parent | 18ea0545091aa173d3fdf25425ede77a5b9243dd (diff) | |
| download | pyramid-b6187b09fb50ff7599815f2ffc2b061793d1f52b.tar.gz pyramid-b6187b09fb50ff7599815f2ffc2b061793d1f52b.tar.bz2 pyramid-b6187b09fb50ff7599815f2ffc2b061793d1f52b.zip | |
Merge pull request #2021 from Pylons/feature/configurable-view-deriver
configurable view deriver
Diffstat (limited to 'docs/narr')
| -rw-r--r-- | docs/narr/extconfig.rst | 1 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 183 |
2 files changed, 184 insertions, 0 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..2c3782387 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1547,3 +1547,186 @@ 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 +------------- + +.. versionadded:: 1.7 + +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. + +It is helpful to think of a :term:`view deriver` as middleware for views. +Unlike tweens or WSGI middleware which are scoped to the application itself, +a view deriver is invoked once per view in the application, and can use +configuration options from the view to customize its behavior. + +Built-in View Derivers +~~~~~~~~~~~~~~~~~~~~~~ + +There are several built-in view derivers that :app:`Pyramid` will automatically +apply to any view. Below they are defined in order from furthest to closest to +the user-defined :term:`view callable`: + +``secured_view`` + + Enforce the ``permission`` defined on the view. This element is a no-op 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`. + + This element will also output useful debugging information when + ``pyramid.debug_authorization`` is enabled. + +``owrapped_view`` + + Invokes the wrapped view defined by the ``wrapper`` option. + +``http_cached_view`` + + Applies cache control headers to the response defined by the ``http_cache`` + option. This element is a no-op if the ``pyramid.prevent_http_cache`` setting + is enabled or the ``http_cache`` option is ``None``. + +``decorated_view`` + + Wraps the view with the decorators from the ``decorator`` option. + +``rendered_view`` + + Adapts the result of the :term:`view callable` into a :term:`response` + object. Below this point the result may be any Python object. + +``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. + +.. warning:: + + Any view derivers defined ``under`` the ``rendered_view`` are not + guaranteed to receive a valid response object. Rather they will receive the + result from the :term:`view mapper` which is likely the original response + returned from the view. This is possibly a dictionary for a renderer but it + may be any Python object that may be adapted into a response. + +Custom View Derivers +~~~~~~~~~~~~~~~~~~~~ + +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, and then register it with +your application using :meth:`pyramid.config.Configurator.add_view_deriver`. +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) + +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.response import Response + 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) + + def protected_view(request): + return Response('protected') + + def unprotected_view(request): + return Response('unprotected') + + config.add_view(protected_view, name='safe') + config.add_view(unprotected_view, name='unsafe', disable_csrf=True) + +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 +``rendered_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 except when the deriver +depends on other operations in the view pipeline. + +Both ``over`` and ``under`` may also be iterables of constraints. For either +option, if one or more constraints was defined, at least one must be satisfied, +else a :class:`pyramid.exceptions.ConfigurationError` will be raised. This may +be used to define fallback constraints if another deriver is missing. + +Two sentinel values exist, :attr:`pyramid.viewderivers.INGRESS` and +:attr:`pyramid.viewderivers.VIEW`, which may be used when specifying +constraints at the edges of the view pipeline. For example, to add a deriver +at the start of the pipeline you may use ``under=INGRESS``. + +It is not possible to add a view deriver under 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. + +.. warning:: + + The default constraints for any view deriver are ``over='rendered_view'`` + and ``under='decorated_view'``. When escaping these constraints you must + take care to avoid cyclic dependencies between derivers. For example, if + you want to add a new view deriver before ``secured_view`` then + simply specifying ``over='secured_view'`` is not enough, because the + default is also under ``decorated view`` there will be an unsatisfiable + cycle. You must specify a valid ``under`` constraint as well, such as + ``under=INGRESS`` to fall between INGRESS and ``secured_view`` at the + beginning of the view pipeline. |
