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 >>> 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 <zope.viewlet.manager.<ViewletManager providing IViewletManager> 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(''' ... <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() 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(''' ... <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, ... lambda x, y: cmp(x[1].weight, y[1].weight))>>> 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): ... ZopeXMLConfigurationError: File "<string>", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt')
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(''' ... <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() u'<div>sunny</div>' >>> 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() <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() u'<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 u'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() 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(''' ... <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() u'SRC $5.19'
A final feature the viewlet directive supports is the additional specification of any amount 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 u'8'
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): ... ZopeXMLConfigurationError: File "<string>", 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(''' ... <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): ... ZopeXMLConfigurationError: File "<string>", 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(''' ... <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): ... ZopeXMLConfigurationError: File "<string>", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute
>>> import shutil >>> shutil.rmtree(temp_dir)