Plone 4 Development: Creating a Custom Workflow


Professional Plone 4 Development

Professional Plone 4 Development

Build robust, content-centric web applications with Plone 4.

        Read more about this book      

(For more resources on Plone, see here.)

Keeping control with workflow

As we have alluded to before, managing permissions directly anywhere other than the site root is usually a bad idea. Every content object in a Plone site is subject to security, and will in most cases inherit permission settings from its parent. If we start making special settings in particular folders, we will quickly lose control.

However, if settings are always acquired, how can we restrict access to particular folders or prevent authors from editing published content whilst still giving them rights to work on items in a draft state? The answer to both of these problems is workflow.

Workflows are managed by the portal_workflow tool. This controls a mapping of content types to workflows definitions, and sets a default workflow for types not explicitly mapped.

The workflow tool allows a workflow chain of multiple workflows to be assigned to a content type. Each workflow is given its own state variable. Multiple workflows can manage permissions concurrently. Plone's user interface does not explicitly support more than one workflow, but can be used in combination with custom user interface elements to address complex security and workflow requirements.

The workflow definitions themselves are objects found inside the portal_workflow tool, under the Contents tab. Each definition consists of states, such as private or published, and transitions between them.

Transitions can be protected by permissions or restricted to particular roles.

Although it is fairly common to protect workflow transitions by role, this is not actually a very good use of the security system. It would be much more sensible to use an appropriate permission. The exception is when custom roles are used solely for the purpose of defining roles in a workflow.

Some transitions are automatic, which means that they will be invoked as soon as an object enters a state that has this transition as a possible exit (that is, provided the relevant guard conditions are met). More commonly, transitions are invoked following some user action, normally through the State drop-down menu in Plone's user interface. It is possible to execute code immediately before or after a transition is executed.

States may be used simply for information purposes. For example, it is useful to be able to mark a content object as "published" and be able to search for all published content.

More commonly, states are also used to control content item security. When an object enters a particular state, either its initial state, when it is first created, or a state that is the result of a workflow transition, the workflow tool can set a number of permissions according to a predefined permissions map associated with the target state.

The permissions that are managed by a particular workflow are listed under the Permissions tab on the workflow definition:

Plone 4 Development: Creating a Custom Workflow

These permissions are used in a permission map for each state:

Plone 4 Development: Creating a Custom Workflow

If you change workflow security settings, your changes will not take effect immediately, since permissions are only modified upon workflow transitions. To synchronize permissions with the current workflow definitions, use the Update security settings button at the bottom of the Workflows tab of the portal_workflow tool. Note that this can take a long time on large sites, because it needs to find all content items using the old settings and update their permissions. If you use the Types control panel in Plone's Site Setup to change workflows, this reindexing happens automatically.

Workflows can also be used to manage role-to-group assignments in the same way they can be used to manage role-to-permission assignments. This feature is rarely used in Plone, however.

All workflows manage a number of workflow variables, whose values can change with transitions and be queried through the workflow tool. These are rarely changed, however, and Plone relies on a number of the default ones. These include the previous transition (action), the user ID of the person who performed that transition (actor), any associated comments (comments), the date/time of the last transition (time), and the full transition history (review_history).

Finally, workflows can define work lists, which are used by Plone's Review list portlet to show pending tasks for the current user. A work list in effect performs a catalog search using the workflow's state variable. In Plone, the state variable is always called review_state.

The workflow system is very powerful, and can be used to solve many kinds of problems where objects of the same type need to be in different states. Learning to use it effectively can pay off greatly in the long run.

Interacting with workflow in code

Interacting with workflow from our own code is usually straightforward. To get the workflow state of a particular object, we can do:

from Products.CMFCore.utils import getToolByName

wftool = getToolByName(context, 'portal_workflow')
review_state = wftool.getInfoFor(context, 'review_state')

However, if we are doing a search using the portal_catalog tool, the results it returns has the review state as metadata already:

from Products.CMFCore.utils import getToolByName

catalog = getToolByName(context, 'portal_catalog')
for result in catalog(dict(
portal_type=('Document', 'News Item',),
review_state=('published', 'public', 'visible',),
review_state = result.review_state
# do something with the review_state

To change the workflow state of an object, we can use the following line of code:

wftool.doActionFor(context, action='publish')

The action here is the name of a transition, which must be available to the current user, from current state of context. There is no (easy) way to directly specify the target state. This is by design: recall that transitions form the paths between states, and may involve additional security restrictions or the triggering of scripts.

Again, the Doc tab for the portal_workflow tool and its sub-objects (the workflow definitions and their states and transitions) should be your first point of call if you need more detail. The workflow code can be found in Products.CMFCore.WorkflowTool and Products.DCWorkflow.

Installing a custom workflow

It is fairly common to create custom workflows when building a Plone website. Plone ships with several useful workflows, but security and approvals processes tend to differ from site to site, so we will often find ourselves creating our own workflows.

Workflows are a form of customization. We should ensure they are installable using GenericSetup. However, the workflow XML syntax is quite verbose, so it is often easier to start from the ZMI and export the workflow definition to the filesystem.

Designing a workflow for Optilux Cinemas

It is important to get the design of a workflow policy right, considering the different roles that need to interact with the objects, and the permissions they should have in the various states. Draft content should be visible to cinema staff, but not customers, and should go through review before being published.

The following diagram illustrates this workflow:

Plone 4 Development: Creating a Custom Workflow

This workflow will be made the default, and should therefore apply to most content. However, we will keep the standard Plone policy of omitting workflow for the File and Image types. This means that permissions for content items of these types will be acquired from the Folder in which they are contained, making them simpler to manage. In particular, this means it is not necessary to separately publish linked files and embedded images when publishing a Page.

Because we need to distinguish between logged-in customers and staff members, we will introduce a new role called StaffMember. This role will be granted View permission by default for all items in the site, much like a Manager or Site Administrator user is by default (although workflow may override this). We will let the Site Administrator role represent site administrators, and the Reviewer role represent content reviewers, as they do in a default Plone installation. We will also create a new group, Staff, which is given the StaffMember role. Among other things, this will allow us to easily grant the Reader, Editor and Contributor role in particular folders to all staff from the Sharing screen.

The preceding workflow is designed for content production and review. This is probably the most common use for workflow in Plone, but it is by no means the only use case. For example, the author once used workflows to control the payment status on an Invoice content type. As you become more proficient with the workflow engine, you will find that it is useful in a number of scenarios.

        Read more about this book      

(For more resources on Plone, see here.)

Building the workflow

We will build our workflow through the ZMI and then export it to the filesystem for incorporation into our policy package.

  1. Before we can start, we must add the StaffMember role, so that we can use this in our workflow definitions. At the Plone site root, we go to the Security tab, and add the new role using the form at the bottom of the page.
  2. Next, we ensure users with this role can view content by default, by finding the View permission in the listing (near the bottom), and ensuring the following roles are ticked: Contributor, Editor, Manager, Site Administrator, Owner, Reader, StaffMember before clicking on Save Changes.
  3. We then create a new skeleton workflow by copying one of the existing ones. This helps set up the standard Plone workflow and review state variables, for example.
  4. In the ZMI, under the portal_workflow tool's Contents tab, we first copy and paste the simple_publication_workflow, which we immediately rename to optilux_sitecontent_workflow.
  5. Clicking on the new workflow definition, we will be presented with its Properties tab. Here, we change the title and description as appropriate:

    Plone 4 Development: Creating a Custom Workflow

  6. Next, we move to the States tab. Here, we can delete the private state, and add a new draft state. We must also set this as the initial state, by selecting it and clicking Set Initial State.

    It is important that every workflow has an initial state. Otherwise, you may get difficult-to-debug errors when creating content items using the workflow.

    Plone 4 Development: Creating a Custom Workflow

  7. Clicking on the new state, we can set some properties—a title, description, and possible exit transitions:

    Plone 4 Development: Creating a Custom Workflow

    It may be that the desired transitions have not been defined yet. In this case, you can always come back to the state definition later.

  8. Next, we move to the Permissions tab, where we define the roles-to-permission map:

    (Move the mouse over the image to enlarge it.)

    There are a few useful rules of thumb for setting up a workflow permission map for standard Plone content. These help ensure the Sharing tab works as expected:
    The View and Access contents information permissions usually have the same role mappings.
    If the Owner role has the View and Access contents information permissions, so should the Reader and Editor roles. Similarly, if the Owner role has the Modify portal content permission, so should the Editor role. If the Add portal content or any type-specific "add" permissions are managed, the Owner and Contributor roles should normally be in sync for such permissions.

    We can now repeat this process for the other states, using our diagram above as a guide.
  9. Next, we go back to the workflow definition and open the Transitions tab. Here, we notice that the reject and retract transitions still list the private state as their target.

    Plone 4 Development: Creating a Custom Workflow

We can click on each to change it. For example:

Plone 4 Development: Creating a Custom Workflow

Notice the guard permission: Review portal content in this case. Also note that it is the Display in actions box – Name (formatted) value that is used for the State dropdown menu, while the Title is used as a tooltip.

The URL (formatted) is used to determine what the relevant item in the State menu links to. It is almost always a URL similar to the following one: %(content_ url)s/content_status_modify?workflow_action=<transition_id>, where <transition_id> is the ID of the current transition. If this field is omitted, Plone will use this pattern as a default, so it is strictly speaking optional.

The Category must be workflow for the State menu to find the transition.

In this case, we do not need to remove or add any transitions, but had we needed to, we could have used the buttons at the bottom of the Transitions tab.

Once our states and transitions are defined, we can take a look at the work list we inherited from the simple_publication_workflow, under the Worklists tab on the workflow itself:

Plone 4 Development: Creating a Custom Workflow

This work list is fine for our purposes, and will make content in the pending state show up in the Review list portlet.

It is common to assign the Review list portlet to the dashboard for the Reviewers group, so that all reviewers see it. This can be done from the Users and Groups control panel.

New work lists can be added using the form at the bottom. Be aware, though, that having too many work lists can have a performance impact on pages where the Review list portlet is shown. A work list definition looks similar to the following screenshot:

Plone 4 Development: Creating a Custom Workflow

Notice how the state name is used in the Cataloged variable matches box, as well as in the URL (formatted) box. The former is what drives the work list. The latter is used to provide a link to a fuller work listing. The work list action category should be global, as shown. Guard permissions are used to control who can view the work list, which in this case is everyone who can approve content.


With our workflow defined, we should test it through the web to ensure it is working as expected. When we are satisfied, we can create a GenericSetup export that contains our new settings. This will be used as the basis for our amendments to the policy package shortly.

In the portal_setup tool in the ZMI, we go to the Export tab, and select the export steps we are interested in: Role / Permission Map and Workflow Tool. At the bottom, we then click Export selected steps, which will present a .tar.gz archive for download. Expanding this, we should find the files rolemap.xml and workflows.xml, and the directory workflows/.

Amending the policy package

With our GenericSetup export in hand, we can now amend the optilux.policy package by selectively copying elements of the export into our custom GenericSetup profile.

To test these amendments through the web, we must reapply the policy package's GenericSetup profile through the portal_setup tool, or deactivate and reactivate it through the Add-ons control panel in Plone. Alternatively, we could create a new Plone site in the ZMI and apply our profile to it.

Role and permission settings

Roles and site-wide permission settings are managed using rolemap.xml, which should be placed in the profiles/default/ directory. Our export contains all roles and permissions in the site, which is much more than we want for our extension profile. The application-specific file we create from the export is much shorter:

<?xml version="1.0"?>
<role name="StaffMember" />
<permission name="View" acquire="True">
<role name="Owner" />
<role name="Manager" />
<role name="Site Administrator" />
<role name="Contributor" />
<role name="Reader" />
<role name="Editor" />
<role name="StaffMember" />

Here, we add the new StaffMember role, and then ensure members of this role can, by default, view any content, on par with users who have the Manager or Site Administrator role.

Workflow definition

Workflow definitions in a GenericSetup profile are stored in the workflows/ directory, in which there should be one subdirectory named after each workflow. In this directory, a definition.xml file describes the workflow.

The reason for this extra subdirectory is that some workflows may have associated Python scripts stored in the ZODB, which are exported here. These in-ZODB scripts are rarely used in Plone applications, however.

Our workflow definition, in profiles/default/workflows/optilux_ sitecontent_workflow/definition.xml, is shown below. For brevity, we have omitted some states, transitions and variable definitions. Please refer to the source code for the full listing.

If you are looking for a more compact syntax for creating workflows directly on the filesystem, the package can be used to create workflows from a CSV file. It does not support every feature of the workflow tool, but has support for the bits Plone uses, and provides more Plone-like defaults. One major drawback, however, is that it does not support making workflows translatable. See

The workflow definition begins with some basic information—its ID, title, description, state variable name, and initial state—followed by the list of permissions that the workflow will manage:

<?xml version="1.0"?>
title="Simple Publication Workflow"
<permission>Access contents information</permission>
<permission>Change portal events</permission>
<permission>Modify portal content</permission>

Next, multiple states are defined, each with a list of available exit transitions and a role map for each managed permission.

<state state_id="draft" title="Draft">
<description>Content is being drafted</description>
<exit-transition transition_id="publish"/>
<exit-transition transition_id="submit"/>
<permission-map name="Access contents information"
<permission-role>Site Administrator</permission-role>
<permission-map name="Change portal events" acquired="False">
<permission-role>Site Administrator</permission-role>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Site Administrator</permission-role>
<permission-map name="View" acquired="False">
<permission-role>Site Administrator</permission-role>

The transitions are then defined, indicating the target state, guard conditions, trigger type (USER or AUTOMATIC), and an action URL which will be used for the corresponding link in the State menu in Plone.

<transition transition_id="publish"
title="Reviewer publishes content"
new_state="published" trigger="USER"
before_script="" after_script="">
<description>Publishing the item makes it visible to other users.
category="workflow" icon="">Publish</action>
<guard-permission>Review portal content</guard-permission>

One or more work lists may also be defined. These are displayed in the Review portlet.

<worklist worklist_id="reviewer_queue" title="">
<description>Reviewer tasks</description>
<action url="%(portal_url)s/search?review_state=pending"
category="global" icon="">Pending (%(count)d)</action>
<guard-permission>Review portal content</guard-permission>
<match name="review_state" values="pending"/>

Finally, a number of standard variables are defined, most of which are required for the proper operation of a workflow in Plone.

<variable variable_id="action"
for_catalog="False" for_status="True" update_always="True">
<description>Previous transition</description>

Mapping workflows to types

Our new workflow definition must be explicitly registered with the workflow tool. It can then be associated with content types and/or set as the default workflow. Both are done using the workflows.xml file, which lives in the profiles/default/ directory. As with rolemap.xml, the exported file should be pared down to only include the elements relevant to our extension profile:

<?xml version="1.0"?>
<object name="portal_workflow">
<object name="optilux_sitecontent_workflow"
<type type_id="File" />
<type type_id="Image" />

This syntax supports two further operations not shown in this example. To explicitly remove the workflow association (including a "no workflow" association) for a type and return it to use the default binding, use:

<type type_id="SomeType" remove="remove" />

To override the default workflow definition for one specific type, binding a different workflow:

<type type_id="SomeType">
<bound-workflow workflow_id="some_workflow"/>

Adding the Staff group

Finally, to add the Staff group upon profile installation, we are faced with a conundrum: There is (currently) no GenericSetup syntax for creating groups. The solution here is to use imperative configuration, that is, configuration performed in Python code using the relevant APIs, as opposed to declarative configuration, which is performed using (XML) configuration files.

Imperative configuration in GenericSetup relies on a trick: we create a custom import handler which "reads" a placeholder file from the profile being imported. If the file is present, the import handler performs the necessary configuration.

The placeholder file is very important. GenericSetup in effect always runs all registered import handlers for each profile it imports. It is up to each handler to decide how to act. Without some kind of marker to check, the imperative import handler will run indiscriminately for every profile imported into the Plone site.

To register our new import step, we can use the following syntax in configure.zcml:


title="Optilux Site Policy"
description="Turn a Plone site into the Optilux site."

title="Additional Optilux site policy setup"
description="Optilux site policy configuration"
<depends name="rolemap"/>
<!-- -*- extra stuff goes here -*- -->

The name should be unique, so that it does not clash with other import steps. The title and description are used in the portal_setup user interface.

The nested <depends /> tag is used here to ensure that this handler executes after the rolemap import step, ensuring that the StaffMember role is available when we need it.

Most of the standard import steps are found in the exportimport/ configure.zcml file of Products.CMFCore.

The handler attribute gives the dotted name to a function implementing the import handler. The convention is to put this in a module called setuphandlers with a function called importVarious. In, we use the PAS and portal_ groups APIs to check for and add our new group. We explicitly check for a marker file, optilux.policy-various.txt, and abort if this is not found in the current profile.

from Products.CMFCore.utils import getToolByName
def setupGroups(portal):
acl_users = getToolByName(portal, 'acl_users')
if not acl_users.searchGroups(name='Staff'):
gtool = getToolByName(portal, 'portal_groups')
gtool.addGroup('Staff', roles=['StaffMember'])
def importVarious(context):
"""Miscellanous steps import handle
if context.readDataFile('optilux.policy-various.txt') is None:
portal = context.getSite()

Take a look at the Doc tab for the acl_users and portal_groups objects inside the Plone site in the ZMI for details about their API.

Finally, we must add the optilux.policy-various.txt marker file to our profiles/default/ directory. This file may be blank.

Writing the tests

Of course, we must not forget the tests for our new functionality. We amend tests. py as follows:

def test_role_added(self):
portal = self.layer['portal']
self.assertTrue("StaffMember" in portal.validRoles())

def test_workflow_installed(self):
portal = self.layer['portal']
workflow = getToolByName(portal, 'portal_workflow')
self.assertTrue('optilux_sitecontent_workflow' in workflow)

def test_workflows_mapped(self):
portal = self.layer['portal']
workflow = getToolByName(portal, 'portal_workflow')

def test_view_permisison_for_staffmember(self):
portal = self.layer['portal']
self.assertTrue('View' in [r['name']
for r in portal.permissionsOfRole('Reader')
if r['selected']])
self.assertTrue('View' in [r['name']
for r in portal.permissionsOfRole('StaffMember')
if r['selected']])
def test_staffmember_group_added(self):
portal = self.layer['portal']
acl_users = portal['acl_users']

These tests make use of the RoleManager, WorkflowTool, and PAS user folder APIs to inspect the security settings in the site after creation.


In this article, we have taken a look at Plone's approach to security, including:

  • The role played by workflows in managing security
  • Creating custom workflow and apply it using GenericSetup

Further resources related to this subject:

You've been reading an excerpt of:

Professional Plone 4 Development

Explore Title