How to declare additional workflows for a behavior#

This guide shows you how to make a behavior contribute one or more workflows to the chain of every content type that enables it.

Prerequisites#

  • An add-on package with its own configure.zcml.

  • A workflow to contribute, installed by a GenericSetup profile. To write one that composes safely, see How to write a workflow that composes.

1. Define the marker interface#

Your behavior's marker interface must extend IAdditionalWorkflows.

from collective.multiworkflow.interfaces import IAdditionalWorkflows


class IFoundationMember(IAdditionalWorkflows):
    """Marker for content whose membership status is tracked."""

That base marker is the single registration point for the chain adapter. A marker that does not extend it never reaches the adapter, and the declaration in step 3 refuses to configure.

2. Register the behavior#

Register the marker as a behavior in the usual way.

<configure xmlns:plone="http://namespaces.plone.org/plone">

  <plone:behavior
      name="my.package.foundation_member"
      title="Foundation member"
      description="Track a membership lifecycle alongside publication."
      provides=".interfaces.IFoundationMember"
      />

</configure>

No factory is needed. With none, plone.behavior applies the interface to content as a marker, which is all the mechanism requires.

3. Declare the contribution#

<plone:additionalworkflows
    marker=".interfaces.IFoundationMember"
    workflows="foundation_member_workflow"
    />

To contribute several workflows, separate their ids with whitespace. They are appended in the order given, after the workflows the content type is already configured with.

See ZCML directive for the directive's full description.

4. Enable the behavior on a type#

Enable it as you would any behavior, in the type's FTI.

<property name="behaviors">
  <element value="my.package.foundation_member" />
</property>

Every object of that type now resolves a chain with your workflow appended.

Register from Python instead#

Use contributes where ZCML cannot express the declaration—building a contribution from configuration read at start-up, for instance.

from collective.multiworkflow.declaration import contributes
from collective.multiworkflow.interfaces import IAdditionalWorkflowsFor
from zope.component import getGlobalSiteManager

factory = contributes(IFoundationMember, "foundation_member_workflow")
getGlobalSiteManager().registerSubscriptionAdapter(
    factory, (IFoundationMember,), IAdditionalWorkflowsFor
)

Register it as a subscription adapter, as shown. A plain adapter has one winner per interface, so content providing two participating behaviors would gain the workflows of only one of them.

Verify#

Create an object of the type and read its chain.

from plone import api

wftool = api.portal.get_tool("portal_workflow")
chain = wftool.getChainFor(obj)

assert chain == (
    "simple_publication_workflow",
    "foundation_member_workflow",
)

If your workflow is missing from the chain, check the instance log. A contributed id that no workflow answers to is logged and skipped, so that a bad declaration cannot break chain lookup and make the object unusable.

See also

About behavior-driven assignment for why the mechanism is built this way.