The difference between BrowserSkins and BrowserLayers
In this blogpost I'd like to shed some light on two concepts that sound similar and can be quite ambiguous and confusing when first encountered.
When we register certain Zope Components (via ZCML or Grok), we can specify a layer attribute for which we would provide an Interface. This will restrict these components to be available only when that Interface is provided by the Request object.
In ZCML it looks like this:
And with Five.Grok:
class Foo(BrowserView): """ """ grok.template("my-template") grok.name("foo") grok.layer(interfaces.IMyLayer)
How would the Request provide an interface? Well, with the Zope Component Architecture, you can make any object provide any interface, by using the alsoProvides method. In this way we can use interfaces as markers, to mark or identify certain objects.
In Plone this is very handy, because it allows us to only enable or disable certain functionality, by registering it against a specific Interface and then marking the Request or not. This "marking" of the request is done when your Plone add-on product is installed or uninstalled in the configuration panel.
Both approaches do the same thing, they mark the Request with an Interface, but they way they do it differs, and that can be bewildering to the uninitiated.
plone.theme provides a way mark the request with an interface when it's associated with a skin installed in the portal_skins tool. Plone 4 comes with two skins, the Classic skin and the newer Sunburst. In other frameworks or environments, a skin could be called a theme.
Lets pretend we were creating a skin with a new egg product called collective.kickasstheme. If you've ever generated a Plone theme package with PasteScript and ZopeSkel, then you've now doubt seen the following.
In collective.kickasstheme.browser.interfaces.py we have this interface declared:
from plone.theme.interfaces import IDefaultPloneLayer class IThemeSpecific(IDefaultPloneLayer): """Marker interface that defines a Zope 3 browser layer. If you need to register a viewlet only for the "Custom Theme" theme, this interface must be its layer (in kickasstheme/viewlets/configure.zcml). """
Registered with this ZCML:
<interface interface=".interfaces.IThemeSpecific" type="zope.publisher.interfaces.browser.IBrowserSkinType" name="Kickass Theme" />
And then with browser resources registered against it, for example:
<browser:resourceDirectory name="collective.kickasstheme.images" directory="images" layer=".interfaces.IThemeSpecific" /> <browser:viewlet name="collective.someviewlet" manager="plone.app.layout.viewlets.interfaces.IPortalFooter" class=".viewlets.MyViewlet" layer=".interfaces.IThemeSpecific" permission="zope2.View" />
The skin is installed with GenericSetup, so we need to inform it about it in collective/kickasstheme/profiles/default/skins.xml
<object name="portal_skins" allow_any="False" cookie_persistence="False" default_skin="Kickass Theme"> <-- Here comes a list of registered folders that are registered in portal_skins --> </object>
plone.theme registers an event subscriber (also known as an event listener) for the IBeforeTraverseEvent. In this subscriber, it queries for the BrowserSkin that is installed with the current Skin in portal_skins and then uses directlyProvides, to mark the Request object with this BrowserSkin. If our skin/theme product is installed, then the IThemeSpecific interface declared in our egg collective.kickasstheme will be the BrowserLayer and will mark the Request.
You can see the code for yourself in plone/theme/layer.py and plone/theme/configure.zcml.
This means that all components (such as BrowserViews, ResourceDirectories, viewlets, etc.) registered against our BrowserSkin will now appear. As soon as we uninstall our theme egg, they will again disappear, because the BrowserSkin is again removed.
BrowserSkins have two major drawbacks.
- You can only have one of them active at any time. If you need more than one BrowserSkin (which is just an Interface) on the request (i.e to render portlets registered against another one) you will have to subclass that other BrowserSkin.
- You also need to create a theme product (with a skin registered in portal_skins) every time you want to create your own BrowserSkin.
So, what do we do if we for example want to create a new viewlet or portlet in a non-theme product, but don't want it to render unless the product is installed?
plone.browserlayer was developed to solve this problem.
So, now we can create any egg, it doesn't have to be a theme egg, and the declare inside it our BrowserLayer.
We start off by creating the marker Interface that will be given to the layer attribute:
from zope.interface import Interface class IMyProductLayer(Interface): """Browser layer for this particular product """
Then add a browserlayer.xml file to the GenericSetup profile directory, with the following:
<?xml version="1.0"?> <layers> <layer name="my.product" interface="my.product.interfaces.IMyProductLayer" /> </layers>
When we now install our egg in Plone's control panel, our BrowserLayer will also added to plone.browserlayer's persistent registry of "installed" layers and applied to the Request object during runtime.
All components and resources registered against this BrowserLayer will be activated and visible. Once we uninstall our egg, they will disappear again.
The benefits of using BrowserLayers are basically the inverse of the drawbacks of using BrowserSkins.
- Any egg can have a dependency on plone.browserlayers and create BrowserLayers, not just theme eggs.
- You can have multiple BrowserLayers currently on the Request.
Both BrowserSkins and BrowserLayers allow you to mark and unmark the Request object and thereby switch components on or off. They also allow you to override existing components, by simply registering your custom components against a specific layer. Previously, you had to override them in overrides.zcml and this could only be done once.
BrowserSkins had some drawbacks and BrowserLayers where developed to address these. That said, both are still around and both are still in use.
Lastly, please use them! It's bad practice to register components for Plone sites that are not tied to a layer. If you do. they will be active in the Plone site, regardless of the egg being installed or not, potentially causing havoc when serving multiple Plone sites.