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.

 

The new GenericSetup way

Posted by Silvio Tomatis at Feb 04, 2009 03:07 AM
Since Plone 3.1 plone.browserlayer is included in the main distribution.
If you use 3.1 or later, to register a browser layer in your extension profile you should only do two things:
 * define an interface for your product (for example Products.myproduct.interfaces.IThemeSpecific)
 * put a file in your extension profile named browserlayer.xml with this content:
   <layers>
    <layer
        name="myproduct"
        interface="Products.myproduct.interfaces.IThemeSpecific"
        />
   </layers>
After that, you can refer to your interface in the layer attribute of <browser:page> elements.