summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2016-03-14 22:08:04 -0500
committerMichael Merickel <michael@merickel.org>2016-03-14 22:08:04 -0500
commitfdd1f8352fc341bc60e0b7d32dadd2b4109a2b41 (patch)
tree9797833c4ffdbc82e2ac8630329a472c23762033
parent7fc181d89e9553b152618066490d1fb659d45e13 (diff)
downloadpyramid-fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41.tar.gz
pyramid-fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41.tar.bz2
pyramid-fdd1f8352fc341bc60e0b7d32dadd2b4109a2b41.zip
first cut at documenting view derivers
-rw-r--r--docs/narr/extconfig.rst1
-rw-r--r--docs/narr/hooks.rst140
-rw-r--r--pyramid/config/views.py14
-rw-r--r--pyramid/tests/test_config/test_derivations.py13
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))
-