diff options
| -rw-r--r-- | CHANGES.txt | 17 | ||||
| -rw-r--r-- | docs/narr/views.rst | 22 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 18 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 27 |
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 " |
