================================
The ``viewletManager`` Directive
================================
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:
>>> from zope.configuration import xmlconfig
>>> context = xmlconfig.string('''
...
...
...
... ''')
Now we can register a viewlet manager:
>>> context = xmlconfig.string('''
...
...
...
... ''', context=context)
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:
>>> import zope.interface
>>> class Content(object):
... zope.interface.implements(zope.interface.Interface)
>>> content = Content()
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> from zope.publisher.browser import BrowserView
>>> view = BrowserView(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:
>>> class ILeftColumn(interfaces.IViewletManager):
... """Left column of my page."""
Now we can register register a manager providing this interface:
>>> context = xmlconfig.string('''
...
...
...
... ''', context=context)
>>> 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 = xmlconfig.string('''
...
...
...
... ''' %leftColumnTemplate, context=context)
>>> manager = zope.component.getMultiAdapter(
... (content, request, view), ILeftColumn, name='leftcolumn')
>>> manager
object ...>
>>> ILeftColumn.providedBy(manager)
True
>>> manager.template
...>>
>>> 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:
>>> class WeightBasedSorting(object):
... def sort(self, viewlets):
... return sorted(viewlets,
... lambda x, y: cmp(x[1].weight, y[1].weight))
>>> context = xmlconfig.string('''
...
...
...
... ''' %leftColumnTemplate, context=context)
>>> manager = zope.component.getMultiAdapter(
... (content, request, view), ILeftColumn, name='leftcolumn')
>>> manager
object ...>
>>> manager.__class__.__bases__
(,
)
>>> ILeftColumn.providedBy(manager)
True
>>> manager.template
...>>
>>> manager.update()
>>> print manager.render().strip()
Finally, if a non-existent template is specified, an error is raised:
>>> context = xmlconfig.string('''
...
...
...
... ''', context=context)
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 = xmlconfig.string('''
...
...
...
... ''' % weatherTemplate, context=context)
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:
>>> class Weather(object):
... weight = 0
>>> context = xmlconfig.string('''
...
...
...
... ''' % weatherTemplate, context=context)
>>> 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:
>>> class Sport(object):
... weight = 0
... def __call__(self):
... return u'Red Sox vs. White Sox'
>>> context = xmlconfig.string('''
...
...
...
... ''', context=context)
>>> 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:
>>> class Stock(object):
... weight = 0
... def getStockTicker(self):
... return u'SRC $5.19'
>>> context = xmlconfig.string('''
...
...
...
... ''', context=context)
>>> viewlet = zope.component.getMultiAdapter(
... (content, request, view, manager), interfaces.IViewlet,
... name='stock')
>>> viewlet.render()
u'SRC $5.19'
A final feature the ``viewlet`` directive is that it supports the
specification of any number of keyword arguments:
>>> context = xmlconfig.string('''
...
...
...
... ''', context=context)
>>> 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 = xmlconfig.string('''
...
...
...
... ''', context=context)
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 = xmlconfig.string('''
...
...
...
... ''', context=context)
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 = xmlconfig.string('''
...
...
...
... ''', context=context)
Traceback (most recent call last):
...
ZopeXMLConfigurationError: File "", line 3.2-9.8
ConfigurationError: The provided class doesn't have the specified attribute
Cleanup
-------
>>> import shutil
>>> shutil.rmtree(temp_dir)