Navigation

  • index
  • modules |
  • next |
  • previous |
  • zope.viewlet 6.0 documentation »
  • Viewlet-related ZCML Directives

Viewlet-related ZCML Directives¶

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('''
... <configure i18n_domain="zope">
...   <include package="zope.viewlet" file="meta.zcml" />
... </configure>
... ''')

Now we can register a viewlet manager:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewletManager
...       name="defaultmanager"
...       permission="zope.Public"
...       />
... </configure>
... ''', 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
>>> @zope.interface.implementer(zope.interface.Interface)
... class Content(object):
...     pass
>>> 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
<zope.viewlet.manager.<ViewletManager providing IViewletManager> object ...>
>>> interfaces.IViewletManager.providedBy(manager)
True
>>> manager.template is None
True
>>> manager.update()
>>> manager.render()
''

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('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewletManager
...       name="leftcolumn"
...       permission="zope.Public"
...       provides="zope.viewlet.directives.ILeftColumn"
...       />
... </configure>
... ''', context=context)
>>> manager = zope.component.getMultiAdapter(
...     (content, request, view), ILeftColumn, name='leftcolumn')
>>> manager
<zope.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
>>> ILeftColumn.providedBy(manager)
True
>>> manager.template is None
True
>>> manager.update()
>>> manager.render()
''

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')
>>> with open(leftColumnTemplate, 'w') as file:
...     _ = file.write('''
... <div class="column">
...    <div class="entry"
...         tal:repeat="viewlet options/viewlets"
...         tal:content="structure viewlet" />
... </div>
... ''')
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewletManager
...       name="leftcolumn"
...       permission="zope.Public"
...       provides="zope.viewlet.directives.ILeftColumn"
...       template="%s"
...       />
... </configure>
... ''' %leftColumnTemplate, context=context)
>>> manager = zope.component.getMultiAdapter(
...     (content, request, view), ILeftColumn, name='leftcolumn')
>>> manager
<zope.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
>>> ILeftColumn.providedBy(manager)
True
>>> manager.template
<BoundPageTemplateFile of ...<ViewletManager providing ILeftColumn>  ...>>
>>> manager.update()
>>> print(manager.render().strip())
<div class="column">
</div>

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, key=lambda x: getattr(x[1], 'weight', 0))
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewletManager
...       name="leftcolumn"
...       permission="zope.Public"
...       provides="zope.viewlet.directives.ILeftColumn"
...       template="%s"
...       class="zope.viewlet.directives.WeightBasedSorting"
...       />
... </configure>
... ''' %leftColumnTemplate, context=context)
>>> manager = zope.component.getMultiAdapter(
...     (content, request, view), ILeftColumn, name='leftcolumn')
>>> manager
<zope.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
>>> manager.__class__.__bases__
(<class 'zope.viewlet.directives.WeightBasedSorting'>,
 <class 'zope.viewlet.manager.ViewletManagerBase'>)
>>> ILeftColumn.providedBy(manager)
True
>>> manager.template
<BoundPageTemplateFile of ...<ViewletManager providing ILeftColumn>  ...>>
>>> manager.update()
>>> print(manager.render().strip())
<div class="column">
</div>

Finally, if a non-existent template is specified, an error is raised:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewletManager
...       name="leftcolumn"
...       permission="zope.Public"
...       template="foo.pt"
...       />
... </configure>
... ''', context=context)
Traceback (most recent call last):
...
ConfigurationError: ('No such file', '...foo.pt')
    File "<string>", line 3.2-7.8

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')
>>> with open(weatherTemplate, 'w') as file:
...     _ = file.write('''
... <div>sunny</div>
... ''')
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="weather"
...       manager="zope.viewlet.directives.ILeftColumn"
...       template="%s"
...       permission="zope.Public"
...       extra_string_attributes="can be specified"
...       />
... </configure>
... ''' % 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()
'<div>sunny</div>'
>>> viewlet.extra_string_attributes
'can be specified'

The manager now also gives us the output of the one and only viewlet:

>>> manager.update()
>>> print(manager.render().strip())
<div class="column">
  <div class="entry">
    <div>sunny</div>
  </div>
</div>

Let’s now ensure that we can also specify a viewlet class:

>>> class Weather(object):
...     weight = 0
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="weather2"
...       for="*"
...       manager="zope.viewlet.directives.ILeftColumn"
...       template="%s"
...       class="zope.viewlet.directives.Weather"
...       permission="zope.Public"
...       />
... </configure>
... ''' % weatherTemplate, context=context)
>>> viewlet = zope.component.getMultiAdapter(
...     (content, request, view, manager), interfaces.IViewlet,
...     name='weather2')
>>> viewlet().strip()
'<div>sunny</div>'

Okay, so the template-driven cases work. But just specifying a class should also work:

>>> class Sport(object):
...     weight = 0
...     def __call__(self):
...         return 'Red Sox vs. White Sox'
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="sport"
...       for="*"
...       manager="zope.viewlet.directives.ILeftColumn"
...       class="zope.viewlet.directives.Sport"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
>>> viewlet = zope.component.getMultiAdapter(
...     (content, request, view, manager), interfaces.IViewlet, name='sport')
>>> viewlet()
'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 'SRC $5.19'
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="stock"
...       for="*"
...       manager="zope.viewlet.directives.ILeftColumn"
...       class="zope.viewlet.directives.Stock"
...       attribute="getStockTicker"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
>>> viewlet = zope.component.getMultiAdapter(
...     (content, request, view, manager), interfaces.IViewlet,
...     name='stock')
>>> viewlet.render()
'SRC $5.19'

If the class mentions that it implements any interfaces using the old-fashioned style, the resulting viewlet will implement IBrowserPublisher:

>>> from zope.publisher.interfaces.browser import IBrowserPublisher
>>> from zope.interface import classImplements
>>> Stock.__implements__ = ()
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="stock"
...       for="*"
...       manager="zope.viewlet.directives.ILeftColumn"
...       class="zope.viewlet.directives.Stock"
...       attribute="getStockTicker"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
>>> viewlet = zope.component.getMultiAdapter(
...     (content, request, view, manager), interfaces.IViewlet,
...     name='stock')
>>> IBrowserPublisher.providedBy(viewlet)
True

A final feature the viewlet directive is that it supports the specification of any number of keyword arguments:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="stock2"
...       permission="zope.Public"
...       class="zope.viewlet.directives.Stock"
...       weight="8"
...       />
... </configure>
... ''', context=context)
>>> viewlet = zope.component.getMultiAdapter(
...     (content, request, view, manager), interfaces.IViewlet,
...     name='stock2')
>>> viewlet.weight
'8'

Error Scenarios¶

Neither the class or template have been specified:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="testviewlet"
...       manager="zope.viewlet.directives.ILeftColumn"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
Traceback (most recent call last):
...
ConfigurationError: Must specify a class or template
    File "<string>", line 3.2-7.8

The specified attribute is not __call__, but also a template has been specified:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="testviewlet"
...       manager="zope.viewlet.directives.ILeftColumn"
...       template="test_viewlet.pt"
...       attribute="faux"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
Traceback (most recent call last):
...
ConfigurationError: Attribute and template cannot be used together.
    File "<string>", line 3.2-9.8

Now, we are not specifying a template, but a class that does not have the specified attribute:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="testviewlet"
...       manager="zope.viewlet.directives.ILeftColumn"
...       class="zope.viewlet.directives.Sport"
...       attribute="faux"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
Traceback (most recent call last):
...
ConfigurationError: The provided class doesn't have the specified attribute
    File "<string>", line 3.2-9.8

Now for a template that doesn’t exist:

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
...   <viewlet
...       name="weather2"
...       for="*"
...       manager="zope.viewlet.directives.ILeftColumn"
...       template="this template is not here"
...       class="zope.viewlet.directives.Weather"
...       permission="zope.Public"
...       />
... </configure>
... ''', context=context)
Traceback (most recent call last):
...
ConfigurationError: ('No such file', '...this template is not here')
    File "<string>", line 3.2-10.8

Cleanup¶

>>> import shutil
>>> shutil.rmtree(temp_dir)

Table of Contents

  • Viewlet-related ZCML Directives
    • The viewletManager Directive
    • The viewlet Directive
      • Error Scenarios
      • Cleanup

Previous topic

A technique for communication between viewlets

Next topic

Interfaces

This Page

  • Show Source

Quick search

Navigation

  • index
  • modules |
  • next |
  • previous |
  • zope.viewlet 6.0 documentation »
  • Viewlet-related ZCML Directives
© Copyright 2015-2025, Zope Foundation and Contributors. Created using Sphinx 8.2.3.