summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt17
-rw-r--r--docs/narr/views.rst22
-rw-r--r--repoze/bfg/tests/test_zcml.py18
-rw-r--r--repoze/bfg/zcml.py27
4 files changed, 79 insertions, 5 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 26c180d9d..91c16a362 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,20 @@
+Next release
+============
+
+- A ZCML ``view`` directive (and the associated ``bfg_view``
+ decorator) can now accept a "wrapper" value. If a "wrapper" value
+ is supplied, it is the value of a separate view's *name* attribute.
+ When a view with a ``wrapper`` attribute is rendered, the "inner"
+ view is first rendered normally. Its body is then attached to the
+ request as "wrapped_body", and then a wrapper view name is looked up
+ and rendered (using ``repoze.bfg.render_view_to_response``), passed
+ the request and the context. The wrapper view is assumed to do
+ something sensible with ``request.wrapped_body``, usually inserting
+ its structure into some other rendered template. This feature makes
+ it possible to specify (potentially nested) "owrap" relationships
+ between views using only ZCML or decorators (as opposed always using
+ ZPT METAL and analogues to wrap view renderings in outer wrappers).
+
1.1a2 (2009-09-14)
==================
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index 9487e805a..2efe84fac 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -275,6 +275,24 @@ template
:ref:`views_with_templates` for more information about view
templates.
+wrapper
+
+ The :term:`view name` (*not* an object dotted name) of another view
+ declared elsewhere in ZCML (or via the ``@bfg_view`` decorator)
+ which will receive the response body of this view as the
+ ``request.wrapped_body`` attribute of its own request, and the
+ response returned by this view as the ``request.wrapped_response``
+ attribute of its own request. Using a wrapper makes it possible to
+ "chain" views together to form a composite response. The response
+ of the outermost wrapper view will be returned to the user. The
+ wrapper view will be found as any view is found: see
+ :ref:`view_lookup_ordering`. The "best" wrapper view will be found
+ based on the lookup ordering: "under the hood" this wrapper view is
+ looked up via ``repoze.bfg.view.render_view_to_response(context,
+ request, 'wrapper_viewname')``. The context and request of a wrapper
+ view is the same context and request of the inner view. If this
+ attribute is unspecified, no view wrapping is done.
+
request_method
This value can either be one of the strings 'GET', 'POST', 'PUT',
@@ -473,6 +491,8 @@ If ``for_`` is not supplied, the interface
If ``permission`` is not supplied, no permission is registered for
this view (it's accessible by any caller).
+If ``wrapper`` is not supplied, no wrapper view is used.
+
If ``route_name`` is supplied, the view will be invoked only if the
named route matches. *This is an advanced feature, not often used by
"civilians"*.
@@ -553,7 +573,7 @@ decorator syntactic sugar), if you wish:
.. _views_with_templates:
Views That Have a ``template``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------------
Using a ``view`` with an associated ``template`` attribute differs
from using a ``view`` without an associated ``template`` in a number
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index e54bd108f..398159801 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -1321,6 +1321,24 @@ class TestDeriveView(unittest.TestCase):
self.assertEqual(next, True)
self.assertEqual(predicates, [True, True])
+ def test_view_with_wrapper_viewname(self):
+ from webob import Response
+ from zope.component import getSiteManager
+ from repoze.bfg.interfaces import IView
+ def inner_view(context, request):
+ return Response('OK')
+ def outer_view(context, request):
+ return Response('outer ' + request.wrapped_body)
+ sm = getSiteManager()
+ sm.registerAdapter(outer_view, (None, None), IView, 'owrap')
+ result = self._callFUT(inner_view, wrapper_viewname='owrap')
+ self.failIf(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = DummyRequest()
+ response = result(None, request)
+ self.assertEqual(response.body, 'outer OK')
+
class TestConnectRouteFunction(unittest.TestCase):
def setUp(self):
cleanUp()
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index 3a2a24819..657105e34 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -58,7 +58,7 @@ from repoze.bfg.view import NotFound
from repoze.bfg.view import MultiView
from repoze.bfg.view import map_view
from repoze.bfg.view import decorate_view
-
+from repoze.bfg.view import render_view_to_response
import martian
@@ -84,6 +84,7 @@ def view(
containment=None,
attr=None,
template=None,
+ wrapper=None,
cacheable=True, # not used, here for b/w compat < 0.8
):
@@ -174,7 +175,8 @@ def view(
template = '%s:%s' % (package_name(_context.resolve('.')), template)
def register():
- derived_view = derive_view(view, permission, predicates, attr, template)
+ derived_view = derive_view(view, permission, predicates, attr, template,
+ wrapper)
r_for_ = for_
r_request_type = request_type
if r_for_ is None:
@@ -244,13 +246,25 @@ def forbidden(_context, view):
view_utility(_context, view, IForbiddenView)
def derive_view(original_view, permission=None, predicates=(), attr=None,
- template=None):
+ template=None, wrapper_viewname=None):
mapped_view = map_view(original_view, attr, template)
- secured_view = secure_view(mapped_view, permission)
+ owrapped_view = owrap_view(mapped_view, wrapper_viewname)
+ secured_view = secure_view(owrapped_view, permission)
debug_view = authdebug_view(secured_view, permission)
derived_view = predicate_wrap(debug_view, predicates)
return derived_view
+def owrap_view(view, wrapper_viewname):
+ if not wrapper_viewname:
+ return view
+ def _owrapped_view(context, request):
+ response = view(context, request)
+ request.wrapped_response = response
+ request.wrapped_body = response.body
+ return render_view_to_response(context, request, wrapper_viewname)
+ decorate_view(_owrapped_view, view)
+ return _owrapped_view
+
def predicate_wrap(view, predicates):
if not predicates:
return view
@@ -627,6 +641,11 @@ class IViewDirective(Interface):
description=u'',
required=False)
+ wrapper = TextLine(
+ title = u'The *name* of the view that acts as a wrapper for this view.',
+ description = u'',
+ required=False)
+
request_type = TextLine(
title=u"The request type string or dotted name interface for the view",
description=(u"The view will be called if the interface represented by "