Attention

This document was written for an old version of Plone, Plone 3, and was last updated 862 days ago.

To learn how to upgrade to the current version of Plone, read the upgrade manual.

Adding a viewlet

by David Convent last modified Jan 11, 2010 09:34 PM
How to add a viewlet and still have several skins living in peace.

Hiding and reordering viewlets within a viewlet manager is something we can do, we now have to cover how to add a new one.

Example

First, lets have a look at one of the examples that are shipped with DIYPloneStyle (version 3.0 is out, still beta but useable) in order to have a better understanding of the machinery: The credits_viewlet product that is located in DIYPloneStyle/example/ adds a new viewlet to the portal footer region.

In the credits_viewlet code, in its browser/ folder, we have a set of files:

- browser/
    - __init__.py
    - configure.zcml
    - interfaces.py
    - logo.pt
    - viewlets.py
__init__.py
This is an empty file, that is here simply to make browser a python package.
configure.zcml
The file where all Zope 3 configuration is defined for browser.
interfaces.py
We'll need this file a bit later, I leave it where it is for now.
logo.pt
The template that will replace the original HTML code in the main template.
viewlets.py
The file that stores viewlet Python classes.

In the same DIYPloneStyle/example/credits_viewlet subfolder, we find the profiles/ repository that stores the Generic Setup configuration files: skins.xml and viewlets.xml.

Practice

Let's get back to our own MyTheme product and reproduce the example we just had a look at.

Viewlet registration

In order to create our new viewlet, we'll have to write a viewlet class in MyTheme/browser/viewlets.py (that renders the MyTheme/browser/credits.pt template), and register it for the viewlet manager we want it to show in (from MyTheme/browser/configure.zcml). Then we'll have to order it at the place we want it to show within the viewlet manager (in MyTheme/profiles/default/viewlets.xml).

Note
If you generate your theme product with DIYPloneStyle, you have to use the --add-viewlet-example option while calling the generator script in order to have the viewlet base code included in your project. The plone3_theme from ZopeSkel includes viewlet base code by default.

We have to edit the following files…

MyTheme/browser/__init__.py:

This is an empty but needed file that makes browser/ a python package.

In MyTheme/browser/viewlets.py:

from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets.common import ViewletBase

DESIGNER = 'John Doe'

class CreditsViewlet(ViewletBase):
    render = ViewPageTemplateFile('credits.pt')

    def update(self):
        # set here the values that you need to grab from the template.
        # stupid example:
        self.designer = DESIGNER

In MyTheme/browser/credits.pt:

<div id="design-credits"
     i18n:domain="custom_i18n_domain">

    <span i18n:translate="design_by">Design by:
        <span tal:omit-tag="python:True"
              tal:content="view/designer"
              i18n:name="designer">Terry Gilliam</span>.</span>

</div>

In MyTheme/browser/configure.zcml:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    i18n_domain="custom_i18n_domain">

    <browser:viewlet
        name="example.credits"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        class=".viewlets.CreditsViewlet"
        permission="zope2.View"
        />

</configure>

Don't forget to include the browser package in MyTheme/configure.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   xmlns:browser="http://namespaces.zope.org/browser">

   <include file="profiles.zcml" />

   <include package=".browser" />

</configure>

In MyTheme/profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
  <order manager="plone.portalfooter" skinname="Credits Viewlet Theme"
         based-on="Plone Default">
    <viewlet name="example.credits" insert-before="plone.colophon" />
  </order>
</object>

We can register our new skin from MyTheme/profiles/default/skins.xml:

<?xml version="1.0"?>
<object name="portal_skins">

    <skin-path name="Credits Viewlet Theme" based-on="Plone Default" />

</object>

Now we can restart Zope and install our product in Plone.

No problem so far, after restarting Zope, installing our product in Plone, and refreshing the portal home page, we see that the footer region has been modified and now displays, in addition to its default content, a message telling who owns credits for the site design.

Skin layer

Now let's go, as portal manager, to Site Setup > Themes in the Plone interface, and select Plone Default as default theme.

After refreshing the portal home page, we see that the credits viewlet is still rendered by the bottom of the page, which is not what we want.

We must find a way to make the credits viewlet render only for the skin it was designed for. We could hide it for all other themes from the viewlets.xml Generic Setup file with the <hidden /> node, but that won't work if you want to design a distributable product. You never know what other theme(s) could be installed on any portal that will have yours installed on.

We can register a viewlet only for one theme (one skin selection) thanks to the plone.theme package. Thanks to plone.theme, we can set a Zope 3 skin layer that corresponds to a skin selection in portal_skins (a theme).

In order to set that skin layer up, add or edit the following files in the browser/ folder in our MyTheme product:

  • MyTheme/browser/interfaces.py:

    from plone.theme.interfaces import IDefaultPloneLayer
    
    class IThemeSpecific(IDefaultPloneLayer):
        """Marker interface that defines a Zope 3 skin layer bound to a Skin
           Selection in portal_skins.
        """
    

It is not needed to rename that interface class if you have more than one theme product. There will be no clash with other products holding the same interface in their own browser/ directories.

  • MyTheme/browser/configure.zcml:

    <configure
       xmlns="http://namespaces.zope.org/zope"
       xmlns:browser="http://namespaces.zope.org/browser">
    
       <interface
          interface=".interfaces.IThemeSpecific"
          type="zope.publisher.interfaces.browser.IBrowserSkinType"
          name="My Theme"
          />
    
       <browser:viewlet
          name="example.credits"
          manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
          class=".viewlets.CreditsViewlet"
          permission="zope2.View"
          layer=".interfaces.IThemeSpecific"
          />
    
    </configure>
    

Note the interface declaration for the Zope 3 skin layer, and the layer parameter for the viewlet itself.

The value for the name parameter in the interface declaration must be the name of the theme added by MyTheme (also known as skin name), as seen in the Site setup > Themes Plone control panel.

The value for the layer attribute in the viewlet declaration must be the interface that is used to define the skin layer.

After restarting Zope (or refreshing our product), and reloading the portal home page, we can see that the Plone Default theme doesn't show our viewlet up anymore.

Cool :-)


Contribute

Something wrong or out of date? Anybody can edit or create a new article in the knowledge base. Simply create an account on this site, log in, and click the Edit button to contribute.