================================
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()
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
...
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
...
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()