Personal tools
You are here: Home Documentation How-tos Creating Workflows Programmatically
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Creating Workflows Programmatically

This How-to applies to: Any version.
This How-to is intended for: Developers

Instructions for creating workflows in Python code, e.g. installation script.

If you are making a special product, you might want to have different workflows as the default workflows shipped with Plone. It's pain to recreate workflows manually each time installing the product - it's better to create workflows programmatically in the installation script of the product.

Here are few links which help you when you are starting to work with workflows.

Here is a sample how to create simple two state workflow for Archetypes item Issue. Issue can be "in progress" or "sealed" and you need permission EDIT_APPLICATION_FOLDER_PERMISSION two switch between these states. The workflow is registered to portal_workflow tool.

Note that Plone 2.0.5 expects state variable to have name "review_state". More information about this quirk in this bug report

From install.py:

    from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
    from Products.DCWorkflow.Guard import Guard
    from Products.CMFCore.WorkflowTool import addWorkflowFactory, _makeWorkflowFactoryKey

    def createIssueWorkflow(id):
        """ Creates Usability issue workflow for the portal

        """            
        flow=DCWorkflowDefinition(id)    

        # Install Issue workflow
        flow.title = "Usability Issue Workflow"

        # this var name is hardcoded in global_defines.pt
        flow.variables.setStateVar('review_state')    
        flow.initial_state = "inprogress"

        flow.states.addState('inprogress')    
        inprogress = flow.states["inprogress"]
        inprogress.title = "In Progress"
        inprogress.description = "Users are allowed to contribute for this item"
        inprogress.transitions = ("seal",)

        # Only users with management permissions can do state transitions
        transitionGuard = Guard()
        transitionGuard.permissions = (MANAGE_USABILITY_ITEMS_PERMISSION,)

        flow.states.addState("sealed")
        sealed = flow.states["sealed"]
        sealed.title = "Sealed"
        sealed.description = "Item has been closed. It is either a duplicate or fixed."
        sealed.transitions = ("reopen",)

        flow.transitions.addTransition("seal")
        seal = flow.transitions["seal"]
        seal.title = "Seal"
        seal.actbox_name = "Seal"
        seal.description = "Make item sealed"
        seal.new_state_id = "sealed"
        seal.guard = transitionGuard
        seal.actbox_url='%(content_url)s/content_seal_form'

        flow.transitions.addTransition("reopen")
        reopen = flow.transitions["reopen"]
        reopen.title = "Reopen"
        reopen.actbox_name = "Reopen"
        reopen.description = "Reopen item for contribution"
        reopen.new_state_id = "inprogress"
        reopen.guard = transitionGuard
        reopen.actbox_url='%(content_url)s/content_reopen_form'

        # In inprogress state, all members can edit the item. In sealed state, 
        # it is only possible for managers.
        # No one has EDIT_USABILITY_ITEMS_PERMISSION set in any other point of code,
        # it is set only here.
        flow.permissions+=(EDIT_USABILITY_ITEMS_PERMISSION, )

        # Member gains back its permissions
        flow.states.inprogress.setPermission(EDIT_USABILITY_ITEMS_PERMISSION, False, 
                          ('Member','Manager','Owner',USABILITY_MANAGER_ROLE,USABILITY_EDITOR_ROLE,))

        return flow

    def setupWorkflows(portal, out):

        flow_id = "issue_workflow"
        flow_title = "Usability Issue Workflow"

        addWorkflowFactory(createIssueWorkflow, id=flow_id, title=flow_title)    
        workflowTool = portal.portal_workflow    
        workflowTool.setChainForPortalTypes("Issue", flow_id)

        workflow_type=_makeWorkflowFactoryKey(createIssueWorkflow, id=flow_id, title=flow_title)          
        workflowTool.manage_addWorkflow(id=flow_id, workflow_type=workflow_type)
        out.write("Succesfully installed issue_workflow\n")

        portal.portal_catalog.refreshCatalog()
        workflowTool.updateRoleMappings()

Then some unit testing example code, how to test this workflow:

   from Products.CMFCore.Expression import Expression
   from Products.PageTemplates.Expressions import getEngine

    def executeUntrustedExpression(condition, mappings):
        """ A helper function to run code with restricted rights

        Emulates evaluating TALES expressions 

        @param condition TALES expression to evaluate, e.g. "python: object.setTitle('blaa')"

        @param mappings Dictionary of available local variables for expressions, e.g.: 
           data = {
            'object':       object,
            'folder':       folder,
            'portal':       portal,
            'nothing':      None,
            'request':      getattr( object, 'REQUEST', None ),
            'modules':      SecureModuleImporter,
            'member':       member,
            }

        """        
        ec = getEngine().getContext(mappings)
        return Expression(condition)(ec)

   def test_05_workflow(self):
        """ Check that we can switch between workflow states and sealed items cannot be changed

        """ 
        root = self.portal        
        workflowTool = root.portal_workflow

        # Login as editor
        self.login("editor")                

        # Create application folder
        root.invokeFactory(type_name='ApplicationFolder', id="appfolder")
        appfolder = root.appfolder

        # now switch to normal member
        self.logout()                
        user = self.login("user1")

        # Create a application
        appfolder.invokeFactory(type_name = 'Application', id="app")

        # Create issue
        appfolder.app.invokeFactory(type_name="Issue", id="issue")            

        flow = workflowTool.getWorkflowsFor(appfolder.app.issue)[0]
        self.failUnless(flow.id == "issue_workflow")

        # sealing items by user shoudln't be allowed
        try:
            workflowTool.doActionFor(appfolder.app.issue, "seal")
            self.fail("Normal user was able to seal item")
        except: 
            pass

        #dumpRolesAndPermissions(getSecurityManager().getUser(), appfolder.app.issue)

        self.logout()                
        self.login("editor")

        # Ensure we have Seal and Reopen actions listed in UI workflow menu
        actions = workflowTool.getActionsFor(appfolder.app.issue)

        self.failUnless(len(actions) == 1)
        self.failUnless(actions[0]["name"] == "Seal", "No Seal action listed for UI")

        # Check also that we have action available through action tool
        actionTool = root.portal_actions
        actions = actionTool.listFilteredActionsFor(appfolder.app.issue)
        self.failUnless(actions["workflow"][0]["name"] == "Seal", "No Seal action listed for UI")

        # Check that Plone UI receives our state variable
        # TODO: review_state variable is hard coded to global_defines.pt in Plone 2.0.5
        # Other state varibles don't work. This might have been fixed in later versions.        
        info = workflowTool.getInfoFor(appfolder.app.issue, 'review_state', None);
        self.failUnless(info != None, "Plone 2.0.5 uses hard coded state variable review_state")

        # Seal test item 
        workflowTool.doActionFor(appfolder.app.issue, "seal")

        # Ensure we have Seal and Reopen actions listed in UI workflow menu
        actions = workflowTool.getActionsFor(appfolder.app.issue)
        self.failUnless(len(actions) == 1)
        self.failUnless(actions[0]["name"] == "Reopen", "No Reopen action listed for UI")

        # Normal user shouldn't be allowed to change values when the item is sealed
        self.logout()                
        self.login("user1")

        #print "after sealing"        
        #dumpRolesAndPermissions(getSecurityManager().getUser(), appfolder.app.issue)
        #dumpRolesAndPermissions(getSecurityManager().getUser(), appfolder.app)
        #dumpRolesAndPermissions(getSecurityManager().getUser(), appfolder)
        #dumpRole("Anonymous", appfolder.app.issue)

        data = { "issue" : appfolder.app.issue }

        try:           
            executeUntrustedExpression("python: issue.setIssueDescription('bloo')", data)                               
            self.fail("Member user was able to edit sealed item")
        except Unauthorized,e:
            pass        

        try:           
            executeUntrustedExpression("python: issue.setTitle('bloo')", data)
            self.fail("Member user was able to edit sealed item")
        except Unauthorized,e:
            pass                   

        self.logout()                
        self.login("editor")                
        workflowTool.doActionFor(appfolder.app.issue, "reopen")

        # Ensure that we propeply returned to initial in-progress state
        actions = workflowTool.getActionsFor(appfolder.app.issue)
        self.failUnless(len(actions) == 1)
        self.failUnless(actions[0]["name"] == "Seal", "No Seal action listed for UI")

        self.logout()                
        self.login("user1")

        # now user should be able to edit item again
        appfolder.app.issue.setTitle("whooo")

        # Try seal reopened item 
        self.logout()                
        self.login("editor")                                        
        workflowTool.doActionFor(appfolder.app.issue, "seal")

Setting workflow scripts

Workflow scripts need to be directly uploaded to Zope database and as far as I know they cannot exist as file system files.

Here is how the trick is done:

    SET_AD_NUMBER_SCRIPT = """
    ##parameters=state_change

    # Set ad number for property ad during publishing

    from Products.CMFCore.utils import getToolByName

    object = state_change.object
    adnumber_tool = getToolByName(object, 'retypes_adnumber_tool')
    object.setAdNumber(adnumber_tool.nextId())
    """

    def addWorkflowScript(workflow, scriptName, script):

        if hasattr(workflow.scripts, scriptName):
            delattr(worflow.scripts, scriptName)

        manage_addPythonScript(workflow.scripts, id=scriptName)

        wf_script = getattr(workflow.scripts, scriptName)        
        wf_script.ZPythonScript_edit(None, script)    

     addWorkflowScript(portal.portal_workflow.property_workflow, "setAdNumberScript", SET_AD_NUMBER_SCRIPT)

by Mikko Ohtamaa last modified May 29, 2006 - 16:53
Contributors: Mikko Ohtamaa
All content is copyright Plone Foundation and the individual contributors.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by