summaryrefslogtreecommitdiff
path: root/docs/narr
diff options
context:
space:
mode:
authorMichael Merickel <mmerickel@users.noreply.github.com>2016-04-10 12:49:16 -0500
committerMichael Merickel <mmerickel@users.noreply.github.com>2016-04-10 12:49:16 -0500
commitb6187b09fb50ff7599815f2ffc2b061793d1f52b (patch)
treefcf36691ccfde6e36e5a4af8bfb3d365e0458668 /docs/narr
parentef38dee074c0d7646c0d085fe7d4fd9473157954 (diff)
parent18ea0545091aa173d3fdf25425ede77a5b9243dd (diff)
downloadpyramid-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.rst1
-rw-r--r--docs/narr/hooks.rst183
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.