================================ The ``viewletManager`` Directive ================================ Setup traversal stuff >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) The ``viewletManager`` directive allows you to quickly register a new viewlet manager without worrying about the details of the ``adapter`` directive. Before we can use the directives, we have to register their handlers by executing the package's meta configuration: >>> context = zcml.load_string(''' ... ... ... ... ''') Now we can register a viewlet manager: >>> context = zcml.load_string(''' ... ... ... ... ''') Let's make sure the directive has really issued a sensible adapter registration; to do that, we create some dummy content, request and view objects: >>> from Products.Five.viewlet.tests import Content >>> content = Content() >>> obj_id = self.folder._setObject('content1', Content()) >>> content = self.folder[obj_id] >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from Products.Five.browser import BrowserView as View >>> view = View(content, request) Now let's lookup the manager. This particular registration is pretty boring: >>> import zope.component >>> from zope.viewlet import interfaces >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ... interfaces.IViewletManager, name='defaultmanager') >>> manager object ...> >>> interfaces.IViewletManager.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' However, this registration is not very useful, since we did specify a specific viewlet manager interface, a specific content interface, specific view or specific layer. This means that all viewlets registered will be found. The first step to effectively using the viewlet manager directive is to define a special viewlet manager interface: >>> from Products.Five.viewlet.tests import ILeftColumn Now we can register register a manager providing this interface: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' Next let's see what happens, if we specify a template for the viewlet manager: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt') >>> open(leftColumnTemplate, 'w').write(''' ...
...
...
... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' %leftColumnTemplate) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.update() >>> print manager.render().strip()
Additionally you can specify a class that will serve as a base to the default viewlet manager or be a viewlet manager in its own right. In our case we will provide a custom implementation of the ``sort()`` method, which will sort by a weight attribute in the viewlet: >>> context = zcml.load_string(''' ... ... ... ... ''' %leftColumnTemplate) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> manager.__class__.__bases__ (, ) >>> ILeftColumn.providedBy(manager) True >>> manager.update() >>> print manager.render().strip()
Finally, if a non-existent template is specified, an error is raised: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt') ========================= The ``viewlet`` Directive ========================= Now that we have a viewlet manager, we have to register some viewlets for it. The ``viewlet`` directive is similar to the ``viewletManager`` directive, except that the viewlet is also registered for a particular manager interface, as seen below: >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt') >>> open(weatherTemplate, 'w').write(''' ...
sunny
... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) If we look into the adapter registry, we will find the viewlet: >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather') >>> viewlet.render().strip() u'
sunny
' >>> viewlet.extra_string_attributes u'can be specified' The manager now also gives us the output of the one and only viewlet: >>> manager.update() >>> print manager.render().strip()
sunny
Let's now ensure that we can also specify a viewlet class: >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather2') >>> viewlet().strip() u'
sunny
' Okay, so the template-driven cases work. But just specifying a class should also work: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, name='sport') >>> viewlet() u'Red Sox vs. White Sox' It should also be possible to specify an alternative attribute of the class to be rendered upon calling the viewlet: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock') >>> viewlet.render() u'SRC $5.19' A final feature the ``viewlet`` directive supports is the additional specification of any amount keyword arguments: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock2') >>> viewlet.weight u'8' Error Scenarios --------------- Neither the class or template have been specified: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: Must specify a class or template The specified attribute is not ``__call__``, but also a template has been specified: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: Attribute and template cannot be used together. Now, we are not specifying a template, but a class that does not have the specified attribute: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute ================================ Viewlet Directive Security ================================ Before we can begin, we need to set up a few things. We need a manager account: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) Finally, we need to setup a traversable folder. Otherwise, Five won't get do its view lookup magic: >>> from OFS.Folder import manage_addFolder >>> manage_addFolder(self.folder, 'ftf') Now we can register another simple viewlet manager: >>> from Products.Five.viewlet.tests import INewColumn >>> context = zcml.load_string(''' ... ... ... ... ''') And a view to call our new content provider: >>> testTemplate = os.path.join(temp_dir, 'test.pt') >>> open(testTemplate, 'w').write(''' ... ... ...

Weather

...
... ... ... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' % testTemplate) We now register some viewlets with different permissions: >>> weatherTemplate = os.path.join(temp_dir, 'weather2.pt') >>> open(weatherTemplate, 'w').write(''' ...
sunny
... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) If we make the request as a manager, we should see both viewlets: >>> print http(r""" ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1 ... Authorization: Basic manager:r00t ... """, handle_errors=False) HTTP/1.1 200 OK ...

Weather

sunny
sunny
... But when we make an anonymous request, we will only see the public viewlet: >>> print http(r""" ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1 ... """, handle_errors=False) HTTP/1.1 200 OK ...

Weather

sunny
... A Dynamic Viewlet ----------------- A viewlet template can of course contain some dynamic code, let's see how that works: >>> dynWeatherTemplate = os.path.join(temp_dir, 'dynamic_weather.pt') >>> open(dynWeatherTemplate, 'w').write(u''' ...
''' ... ) >>> context = zcml.load_string(''' ... ... ... ... ''' % dynWeatherTemplate) Now we request the view to ensure that we can see the dynamic template rendered as expected: >>> print http(r""" ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1 ... """, handle_errors=False) HTTP/1.1 200 OK ...

Weather

Los Angeles, CA: 78 F
sunny
... Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) Clear registries: >>> from zope.testing.cleanup import cleanUp >>> cleanUp()