Personal tools
You are here: Home Documentation Tutorials Customization for developers
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Customization for developers

Note: Return to tutorial view.

This tutorial provides an overview of how to customise different aspects of Plone 3

Introduction

What are we doing here?

Plone has a strong culture of customisation. That is, virtually every aspect of Plone can be customised or overridden, without touching the original source code. This tutorial will outline the main ways in which visual components in Plone 3 can be customised by developers.

It is possible for site administrators to customise various templates through-the-web, using either the portal_skins tool (for traditional CMF skin layer resources) or the portal_view_customizations tool (for Zope 3 views, viewlets and portlets). We will explain the difference in more detail on subsequent pages. Through-the-web customisation is fine for quick fixes or experimentation, but this is no way to build or deploy a serious project. This tutorial focuses on filesystem-based customisations, as a developer would do in a third-party add-on product.

This tutorial demonstrates the main types of customisations using a package called example.customization. You can download it from the Cheese Shop, installing it in a local Python environment or a buildout. The buildout tutorial will show you how to install an egg-based product. Don't forget to install a ZCML slug if you want to test the product out.

If you simply want to look at the latest version of the source code, you can browse for it in the Collective Subversion repository.

Skin layer customisation

The old-fashioned way

Traditionally, customisation in Zope has been done through acquisition. For example, a developer might have placed a page template in the root of the site and acquire it to render an object in a subfolder /foo/bar. If the developer wanted a different rendering for objects in /foo/bar/baz only, he may place a template with the same name (id) in that folder, which would take precedence.

Since Plone (and the CMF) is all about letting site users create their own content hierarchies, placing templates and code inside the site structure would become messy. Therefore, the CMF developers invented the portal_skins tool.If you view this tool in the ZMI, you will find one or more skins on its Properties tab, notably Plone Default. A skin (which we tend to call themes to avoid confusion), is just an ordered list of skin layers. The skin layers listed on the Properties tab corresponds to folders inside the portal_skins tool. When Zope is looking for a resource (logo.jpg, say) it will search the current folder, and then parent folders up until the site root. If the resource can't be found, CMF will look for it in the skin layers for the current theme, starting at the top of the list and going down until it finds one.

Notice how custom is the first entry in the list. This is how through-the-web customisation works. Copy an item to this folder, and the new copy takes precedence over existing entries. Also notice how all the other folders read-only. That is because they are not folders in the ZODB at all - they are Filesystem Directory Views - folders that reflect the files inside a particular folder in the filesystem.

Thus, to create a product that overrides some resource in a skin layer that ships with Plone, we must:

  • Register a new filesystem directory view
  • Insert this in the list of skin layers for the current skin, normally just below the custom folder
  • Copy the relevant resource into the new skin layer directory, keeping the same name
  • Customise this copy

Note: Some resources will have an associated .metadata file. If so, you must copy the .metadata file along with the resource when customising the resource.

Traditionally, all Plone resources - images, style sheets, Javascript files, page templates and Python scripts such as form handlers, were kept in skin layers. However, as you may imagine this became a bit cumbersome as Plone grew, since the skin layer mechanism presupposes that every resource has a globally unique name (id). Still, most of the core views, images and style sheets that are part of Plone are kept in skin layers that have names prefixed with plone_, such as plone_images or plone_templates. On the filesystem, you will find these inside CMFPlone/skins. You can search the subfolders of this folder to find various resources.

In general, if a resource is mentioned in the Plone UI (e.g. from a url, an action in portal_actions or an alias in portal_types) and it is not prefixed with either @@ (as in @@view) or ++resource++ (as ++resource++stylesheet.css), then it is likely to be found in a skin layer.

Registering and installing a new filesystem-based skin layer

In the example.customization product, we have a skins/ subdirectory, with a single skin layer directory, example_customiation. For us to be able to register this a filesystem directory view, we must tell CMF about the top-level skins directory. We do so in the package's __init__.py file:

from Products.CMFCore.DirectoryView import registerDirectory

GLOBALS = globals()
registerDirectory('skins', GLOBALS)

def initialize(context):
    """Intializer called when used as a Zope 2 product."""

For this to work, we also need to ensure that this package is a Zope 2 product. If it is in the magical Products.* namespace (e.g. a traditional product placed in the Products directory if a Zope instance), this happens automatically. If we are using an egg-based product in a different namespace, we add this to the package's configure.zcml:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
    xmlns:five="http://namespaces.zope.org/five"
    i18n_domain="example.customization">

   <five:registerPackage package="." initialize=".initialize" />

   ...

</configure>

We then need to create the directory view and install it for the current skin when the product is installed in a Plone site. We do this using GenericSetup. First, we must register a new extension profile so that the product is installable. This is done in configure.zcml, as follows:

   <genericsetup:registerProfile
      name="default"
      title="Example customizations"
      directory="profiles/default"
      description='Install various customizations from the example.customization package'
      provides="Products.GenericSetup.interfaces.EXTENSION"
      />

Then we use the skins.xml import handler to configure the portal_skins tool:

<?xml version="1.0"?>
<!-- This file holds the setup configuration for the portal_skins tool -->

<object name="portal_skins">

 <object name="example_customization"
    meta_type="Filesystem Directory View"
    directory="example.customization:skins/example_customization"/>

 <skin-path name="*">
  <layer name="example_customization"
     insert-after="custom"/>
 </skin-path>

</object>

If we wanted to, we could register as many directory views as we wanted here.

To test it all, we have placed an exciting new logo in skins/example_customizations/logo.jpg, overriding logo.jpg from CMFPlone/skins/plone_images. When the product is installed, you will see this logo instead of the default Plone one. You may need to do a hard-refresh in your browser.

Browser layers

Customisation Zope 3-style

When Zope 3 was designed, many of the lessons from CMF were incorporated and the technology refined. The idea of a single, global namespace with customisation possible by id only was supplanted by the notion of named resources being registered for a context type (so that the view called @@view when invoked on a Page looks different to the view with the same name invoked on a Folder, say), and possibly registered for a browser layer. A Zope 3 browser layer is similar in purpose to a CMF skin layer, but is implemented differently.

A browser layer is just a marker interface which is applied to the request upon traversal. A Zope 3 browser resource is a multi-adapter on the context and the request, and when the request is marked with a particular interface, the Component Architecture may find a more specific adapter in a view registered for that particular layer. If that made no sense, don't worry. All you need to know is this:

  • We define a marker interface (which is just a class with no body, deriving from zope.interface.Interface) representing the browser layer.
  • We ensure that the interface is applied to the request automatically, enabling the browser layer in our site.
  • We register (using ZCML) browser resources, views, viewlets and portlets for this new layer, allowing them to override the defaults (which are implicitly registered for the default browser layer).

In Plone, there are two main ways of enabling a custom browser layer interface. The first is to use the mechanisms of plone.theme. This package, which ships with Plone by default, allows us to link a browser layer with a particular theme (skin) in portal_skins (not to be confused with a skin layer). When the theme is enabled in portal_skins, the layer is in effect. This is useful for products that install a whole new theme in Plone. It's less useful for general (non-theme) products, since only one plone.theme-installed layer is active at any given time. We really want layer installation to be additive, so that we can install any number of products, each providing its own layer.

To do that, we need to use the plone.browserlayer package. This package is not part of Plone 3.0 (but its functionality will be part of Plone 3.1). Therefore, we must install it along with our package.

Since example.customization is an egg-based product, we can get plone.browserlayer simply by requiring it in setup.py. This is done by modifying the install_requires definition in that file:

install_requires=[
          'setuptools',
          'plone.browserlayer',
      ],

With this, buildout (or easy_install) will download and install plone.browserlayer from PyPI.

We also need to install plone.browserlayer as a product. In the absence of GenericSetup dependency support (scheduled for Plone 3.1), we do so using an Extensions/Install.py file inside our product, with the following mostly-boilerplate code:

import transaction
from Products.CMFCore.utils import getToolByName

PRODUCT_DEPENDENCIES = ('plone.browserlayer',)
                        
EXTENSION_PROFILES = ('example.customization:default',)

def install(self, reinstall=False):
    portal_quickinstaller = getToolByName(self, 'portal_quickinstaller')
    portal_setup = getToolByName(self, 'portal_setup')

    for product in PRODUCT_DEPENDENCIES:
        if reinstall and portal_quickinstaller.isProductInstalled(product):
            portal_quickinstaller.reinstallProducts([product])
            transaction.savepoint()
        elif not portal_quickinstaller.isProductInstalled(product):
            portal_quickinstaller.installProduct(product)
            transaction.savepoint()
    
    for extension_id in EXTENSION_PROFILES:
        portal_setup.runAllImportStepsFromProfile('profile-%s' % extension_id, purge_old=False)
        product_name = extension_id.split(':')[0]
        portal_quickinstaller.notifyInstalled(product_name)
        transaction.savepoint()

With this, plone.browserlayer ("Local browser layer support") will be installed when we install example.customiation.

Zope 3 browser resources

How to customise images and stylesheets registered as Zope 3 browser layers

Zope 3 allows browser resources, notably images and style sheets, to be registered under a special namespace. For example, if you register an image resource with the name wibble.gif, the browser resource would be addressable as http://yoursite.com/++resource++wibble.gif. This serves to get the resource out of the flat, global namespace. Browser resources don't do much by themselves. They are typically installed in a registry such as portal_css, portal_javascripts, or portal_kss, or used in actions or other links.

Like all Zope 3 browser components, browser resources are registered with a ZCML directive in the browser namespace that takes, among other things, a layer attribute. The layer should resolve to an interface.

As an example, take the plone.app.iterate package. In its browser/configure.zcml file, you will find the following definition:

    <browser:resource
        name="checkout.png"
        image="checkout.png"
        />

This defines a resource, ++resource++checkout.png, which is used in the check-out action when Iterate is installed. If we wanted to turn this rather pretty image into an ugly pink, we could customise it for the IExampleCustomization layer. With a custom image called ugly_checkout.png in our own browser/ directory, we would add the following in browser/configure.zcml:

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

    ...

    <browser:resource
        name="checkout.png"
        image="ugly_checkout.png"
        layer=".interfaces.IExampleCustomization"
        />

    ...

</configure>

Note that without the layer attribute, we would get a configuration conflict with the original ++resource++checkout.png definition.

Zope 3 browser views

Customising browser views and pages

Browser views are analogous to page templates (.pt files) in skin layers. However, they are registered in ZCML for a particular type of context. Here is an example, from plone.app.content.browser:

    <browser:page
        for="*"
        class=".reviewlist.FullReviewListView"
        name="full_review_list"
        template="full_review_list.pt"
        permission="cmf.ReviewPortalContent" />

This tells us that the view is called @@full_review_list (the @@ disambiguator is not strictly necessary, but it makes it clear that this is a view and not, say, a content item). It is available to users that currently have the Review portal content permission, and it is available for any context (for="*"). The implementation is held in a class called FullReviewListView in reviewlist.py in the current directory, which is implicitly rendered by a template called full_review_list.pt.

One way to customise this is to provide an override for a more specific (or different) context. The "*" context is the most general (under the hood, this means zope.interface.Interface). If we had our own implementation for a standard Page (as identified by the Products.ATContentTypes.interface.document.IATDocument interface), we could do so with the following declaration in our own package's ZCML:

    <browser:page
        for="Products.ATContentTypes.interface.document.IATDocument"
        class="plone.app.content.browser.reviewlist.FullReviewListView"
        name="full_review_list"
        template="document_full_review_list.pt"
        permission="cmf.ReviewPortalContent" />

Here, we have chosen to use the default layer (that is, we haven't specified a layer), but a new context type (the for attribute, which points to the interface of the context type in question). We could have provided a new class (or none at all), but here we use the default view class from plone.app.content. Note that we converted it from a relative module path to an absolute one, since we are now in a different package! We do change the template, which again is relative to the directory where the ZCML file is found. We could have chosen to use a class only, or a template only, if that made more sense.

Alternatively, we could customise by layer (and thus ensure that the customisation only takes effect when the product is installed). Here is another example, this time replacing the default view with a new one, using the old class with a new template.

    <browser:page
        for="*"
        class="plone.app.content.browser.reviewlist.FullReviewListView"
        name="full_review_list"
        template="standard_full_review_list.pt"
        layer=".interfaces.IExampleCustomization"
        permission="cmf.ReviewPortalContent" />

Note that these two declarations can co-exist. In this case, the default will use standard_full_review_list.pt, but the view on a Page will use document_full_review_list.pt, since that is more specific. Of course, we could do both - use a context override and a layer. The result would be that the context override only took effect when the layer is installed.

Finding views

Since views are spread out across packages, it can sometimes be difficult to identify where they come from. Often, doing a file search in your egg cache is a quick-and-dirty solution. However, you can also do either of the following:

  • Go to portal_view_customizations in the ZMI, where you will find views, viewlets and portlet renderers, grouped together by their context type. Hover your mouse over the view title, and you should see a package name and a template name in a tool tip.
  • Append /@@zptviews.html to the end of a particular URL, e.g. http://localhost:8080/plone/@@zptviews.html. This shows views with templates (but not viewlets or portlet renderers), along with their template, context type interface and source ZCML file.

Viewlets

Customising Zope 3 viewlets

Viewlets are snippets of a page, written a bit like a view but composed into the page via a viewlet manager. The viewlet manager is responsible for finding, ordering, filtering and rendering its viewlets. Most of Plone's standard viewlets are found in the plone.app.layout package. Here is an excerpt from its viewlets/configure.zcml file:

    <browser:viewlet
        name="plone.colophon"
        for="*"
        manager=".interfaces.IPortalFooter"
        template="colophon.pt"
        permission="zope.Public"
        />

The name is unique within the portlet manager, which is identified by an interface (and defined earlier in the same file, using a <browser:viewletManager /> directive). Here, we are using a template-only viewlet, defined for any context, and not protected by any particular permission. It is possible to define a class instead of or in addition to the the template.

As you may have guessed, it's possible to customise the viewlet either by registering a new viewlet with the same name, in the same manager, but for a more specific context. Alternatively, we can register a new viewlet with the same name, in the same manager, using a custom layer. That could look like this:

    <browser:viewlet
        name="plone.colophon"
        for="*"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        template="funny_colophon.pt"
        permission="zope.Public"
        layer=".interfaces.IExampleCustomization"
        />

Notice how we have turned the relative module path to the IPortalFooter viewlet manager interface into an absolute one and specified a new template (found relative to the directory where our new ZCML file is located).

Finally, it's possible to use the view attribute to specify an interface or class of a page view. With this type of customisation, you can have a viewlet that looks different on different pages. The most common use of this in Plone is to register viewlets that are only shown on the main "view" of a page, by using the special IViewView marker interface, which is applied to the current view during traversal if the user is looking at the "view" tab of the current context. Here is an example from plone.app.layout.viewlets:

    <browser:viewlet
        name="plone.comments"
        for="Products.CMFCore.interfaces.IContentish"
        manager=".interfaces.IBelowContent"
        view="..globals.interfaces.IViewView"
        class=".comments.CommentsViewlet"
        permission="zope2.View"
        />

As with views, you can of course use the for, layer and view customisation dimensions in combination.

Finding viewlets

The portal_view_customizations tool will show you viewlet registrations (and the viewlet managers they are registered for). As with views, you can hover over the viewlet name to see where it is registered in a tool tip. To discover the name of a particular viewlet, you can use the @@manage-viewlets view, e.g. as http://localhost:8080/plone/@@manage-viewlets.

For more information about viewlets, see the tutorial on customising the viewlets in main_template.

Portlet renderers

Customising Plone 3 portlet renderers

Portlets in Plone 3 are not too dissimilar to viewlets, but they consist of a persistent component, the portlet assignment, containing the configuration of the portlet, which is rendered using a portlet renderer. For visual customisations, you want to get at the portlet renderer.

Since portlet renderers can incorporate complex logic and relies on some specific registration techniques, Plone comes with a new ZCML directive - <plone:portletRenderer /> - which makes customising portlet renderers a little easier. Unlike the customisation we have seen for standard browser resources, a template-only custom portlet renderer will actually use the renderer view class (as its view variable) that was used to register the original portlet renderer.

In plone.app.portlets, under portlets/configure.zcml, you will find this definition for the Recent portlet:

    <plone:portlet
        name="portlets.Recent"
        interface=".recent.IRecentPortlet"
        assignment=".recent.Assignment"
        renderer=".recent.Renderer"
        addview=".recent.AddForm"
        editview=".recent.EditForm"
        />

Let's say we wanted to customise this with a new template. The default template (as referenced by the Renderer  class in recent.py) is recent.pt in the same directory. Copying that to our own package, we can do the following:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    i18n_domain="example.customization">

    <!-- We need to include the package of the portlets we are customising -->
    <include package="plone.app.portlets" />

    ...

    <plone:portletRenderer
        portlet="plone.app.portlets.portlets.recent.IRecentPortlet"
        layer=".interfaces.IExampleCustomization"
        template="mostly_recent.pt"
        />

</configure>

Note that we are explicitly including the plone.app.portlets package in ZCML processing, since we are now using its portlet Renderer class implicitly. We then define a new portlet renderer with a custom template, for our new layer. It is also possible to use the for attribute to customise for a particular type of context, or the view attribute to customise for a particular view, as with viewlets.

Instead of using a custom template, we could use a whole new renderer class. See the implementations in plone.app.portlets to understand how this would work. Note that unlike views and viewlets, portlet renderers support either template or class, but not both.


For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by