blog

Schema-extending Dexterity content types with five.grok and plone.behavior

| categories: schemaextending, dexterity, adapters, zca, plone

This is a quick blog post about using plone.behavior to change the schema of Plone's Dexterity content types. Hopefully it'll help people familiar the Archetypes' schema-extending paradigm, to achieve the same effect with Dexterity types.

Of course, Dexterity is not Archetypes, and plone.behaviors are not schema-extenders (well, not only), but sometimes you want to do the same thing, whether you're using Archetypes or Dexterity, and that is extend the schema of an existing content type created in a different product (that you don't want to or cannot edit) in your own product.

Martin Aspeli has provided much more thorough and complete documentation on behaviors here. In short, behaviors can be seen as conditional adapters. The condition being that the behavior must be listed in the behaviors property of the type's FTI (Factory Type information).

Schema-extending in Archetypes also happened through adapters, but they were not checked against the type's FTI, and were thus not conditional.

Example:

Suppose we have a Sector type in another package,  and that we want to add another field statistics_level. We don't want to modify this other package, so we'll use plone.behavior to "extend its schema".

In our own package, we create sector.py and add the following code:

from zope import schemafrom plone.directives import dexterity
from plone.directives import form
from plone.autoform.interfaces import IFormFieldProvider
from plone.app.dexterity.behaviors.metadata import MetadataBase
from plone.app.dexterity.behaviors.metadata import DCFieldProperty
class IExtendedSector(form.Schema):
    """ """
    statistics_level = schema.Choice(
        title = _("label_statistics_level", default=u"Statistics Level"),
        description = _(
            "help_statistics_level",
            default=u"Level 1: Basic statistics. Level 2: More detailed statistics."),
            required=True,
            vocabulary = SimpleVocabulary([
                SimpleTerm(1, title=u"1"),
                SimpleTerm(2, title=u"2"),
            ]),
            default=1
        )

The interface IExtendedSector defines the new field statistics_level that we want to add.

alsoProvides(IExtendedSector, IFormFieldProvider)

We then mark IExtendedSector with IFormFieldProvider to signal that it should be processed for form fields.

class ExtendedSector(MetadataBase):
    statistics_level = DCFieldProperty(IExtendedSector['statistics_level'])

Next we need a class that implements the MetadataBase behavior adapter and which will act as the adapter factory.

Now we need to wire this adapter in with ZCML:

<plone:behavior
    title="Extended Sector"
    description="Provide additional statistics_level field"
    provides=".sector.IExtendedSector"
    factory=".sector.ExtendedSector"
    for="plone.dexterity.interfaces.IDexterityContent"
    />

Lastly we need the portal_typesFTI (Factory Type Information) to be updated with our behavior. Go the the ZMI, then portal_types and then click on the Dexterity type you want to extend. You will see that there is a lines field called Behaviors.

Add the interface of your new extender here, i.e collective.myproduct.sector.IExtendedSector

That's basically it!

You probably want to add the behavior interface to a GenericSetup XML file that defines the type you are extending, to avoid having to manually add it to portal_types every time you create a new site (it's also very easy to forget about this step).

Something like this in your type's GS-XML file:

<!-- List of enabled behaviors -->
<property name="behaviors">
   <element value="plone.app.content.interfaces.INameFromTitle" />
   <element value="collective.myproduct.sector.IExtendedSector" />
</property>

Oh, and lastly... you're going to need five.grok for this. Your package needs to be grokked, otherwise the plone.directives.form form hints won't be activated.

In your configure.zcml:

<grok:grok package="." />

You can do a lot more than just the above withDexterityand plone.behavior, take a look at Martin's documentation for more info.