Adding configuration settings using Zope 3 schemas and formlib
This how-to explains how to add a configlet to Plone's control panel and letting the Zope 3 framework do the work for you.
We're going to put together three components. A local utility where we store the settings, a schema that defines the settings and a view that takes care of the rendering. We glue together the view and the storage by registering a simple adapter.
First, the components
Let's first define the fields that make up our configuration settings. We use zope.schema for this. As part of best practices in Zope development, we support internationalization by wrapping all strings as messages.
Important: If you're using Plone 2.5 you must make sure that your site is registered as a Five local site. That's beyond the scope of this how-to, but you can look over the shoulders of Rocky Burt in the Plone4Artists project. Specifically the p4a.common module.
interfaces.py:from zope.interface import Interface
from zope import schema
from zope.i18nmessageid import MessageFactory
_ = MessageFactory('some_message_domain')
class ISillyConfiguration(Interface):
"""This interface defines the configlet."""
favorite_color = schema.TextLine(title=_(u"Enter your favorite color"),
required=True)
Next we'll set up the configlet view class:
browser/config.py:from zope.formlib import form
from zope.i18nmessageid import MessageFactory
from Products.Five.formlib import formbase
from interfaces import ISillyConfiguration
_ = MessageFactory('some_message_domain')
class SillyConfigurationForm(formbase.EditFormBase):
form_fields = form.Fields(ISillyConfiguration)
label = _(u"A silly settings form")
We need to register this view on the Plone site. Here's the required configuration:
browser/configuration.zcml:<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="Products.CMFPlone.Portal.PloneSite"
name="silly-configuration"
class=".config.SillyConfigurationForm"
permission="cmf.ManagePortal"
/>
</configure>
We don't want to store the settings on the Plone site itself. Let's add a local utility and store it there:
config.py:from zope.interface import implements
from zope.schema.fieldproperty import FieldProperty
from interfaces import ISillyConfiguration
from OFS.SimpleItem import SimpleItem
class SillyConfiguration(SimpleItem):
implements(ISillyConfiguration)
contact_email = FieldProperty(ISillyConfiguration['contact_email'])
We'll create and register this local utility when we install our product. Make sure the following method is called as part of the normal product installation routine.
sitesetup.py:from Products.SillyProduct.interfaces import ISillyConfiguration
from Products.SillyProduct.config import SillyConfiguration
def setup_site(portal):
sm = portal.getSiteManager()
if not sm.queryUtility(interfaces.ISillyConfiguration, name='silly_config'):
sm.registerUtility(SillyConfiguration(),
interfaces.ISillyConfiguration,
'silly_config')
Note that if you're on Zope 2.9 you need to reverse the first two arguments for the registerUtility call.
Add glue
All that's left now is to add an adapter that tells the configuration form that we it should use our local utility to store the schema attributes.
Instead of registering a class to adapt our components we just use a normal function. The way it works is that formlib will look for an adapter to tell it how to glue together the fields with the context and it uses the resulting object to write the fields to. Since we just want to store the settings in the local utility we look it up and return it.
configure.zcml:<adapter for="Products.CMFPlone.Portal.PloneSite" provides=".interfaces.ISillyConfiguration" factory=".config.form_adapter" />config.py:
from zope.component import getUtility
from interfaces import ISillyConfiguration
def form_adapter(context):
return getUtility(ISillyConfiguration, name='silly_config', context=context)
What's left
You'll probably want to add your new configlet to the control panel by adding it to your controlpanel.xml (see CMFPlone/profiles/default/controlpanel.xml). You can define the icon by adding it to actionicons.xml.
error in call of `registerUtility`
instead of `sm.registerUtility(interfaces.ISillyConfiguration, SillyConfiguration(), 'silly_config')` it needs to be `sm.registerUtility(SillyConfiguration(), ISillyConfiguration,'silly_config')`