diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-09-14 07:55:36 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-09-14 07:55:36 +0000 |
| commit | 4eb45e9de657bedeb0b03469781c35758500dfa2 (patch) | |
| tree | 0d4a7b93f08248425af04dcc38d447c1e25e66ad | |
| parent | 7dbf3ffc6e3d27dbebe3b32ff6d4c97f6a81de92 (diff) | |
| download | pyramid-4eb45e9de657bedeb0b03469781c35758500dfa2.tar.gz pyramid-4eb45e9de657bedeb0b03469781c35758500dfa2.tar.bz2 pyramid-4eb45e9de657bedeb0b03469781c35758500dfa2.zip | |
- 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).
| -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 " |
