{ "info": { "author": "Zope Corporation and Contributors", "author_email": "zope3-dev@zope.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP" ], "description": "This package provides an implementation of a Workflow-Management\nCoalition (WFMC) workflow engine. The engine is provided as a\ncollection of workflow process components. Workflow processes can be\ndefined in Python or via the XML Process-Definition Language, XPDL.\n\n\nDetailed Documentation\n======================\n\n\n\nWorkflow-Management Coalition Workflow Engine\n=============================================\n\nThis package provides an implementation of a Workflow-Management\nCoalition (WFMC) workflow engine. The engine is provided as a\ncollection of workflow process components. Workflow processes can be\ndefined in Python or via the XML Process-Definition Language, XPDL.\n\nIn this document, we'll look at Python-defined process definitions:\n\n >>> from zope.wfmc import process\n >>> pd = process.ProcessDefinition('sample')\n\nThe argument to the process is a process id.\n\nA process has a number of parts. Let's look at a sample review\nprocess::\n\n -----------\n -->| Publish |\n ---------- ---------- / -----------\n | Author |-->| Review |- ----------\n ---------- ---------- \\-->| Reject |\n ----------\n\nHere we have a single start activity and 2 end activities. We could\nhave modeled this with a single end activity, but that is not\nrequired. A single start activity *is* required. A process definition\nhas a set of activities, with transitions between them. Let's define\nthe activities for our process definition:\n\n >>> pd.defineActivities(\n ... author = process.ActivityDefinition(),\n ... review = process.ActivityDefinition(),\n ... publish = process.ActivityDefinition(),\n ... reject = process.ActivityDefinition(),\n ... )\n\nWe supply activities as keyword arguments. The argument names provide\nactivity ids that we'll use when defining transitions:\n\n >>> pd.defineTransitions(\n ... process.TransitionDefinition('author', 'review'),\n ... process.TransitionDefinition('review', 'publish'),\n ... process.TransitionDefinition('review', 'reject'),\n ... )\n\nEach transition is constructed with an identifier for a starting\nactivity, and an identifier for an ending activity.\n\nBefore we can use a workflow definition, we have to register it as a\nutility. This is necessary so that process instances can find their\ndefinitions. In addition, the utility name must match the process id:\n\n >>> import zope.component\n >>> zope.component.provideUtility(pd, name=pd.id)\n\nNow, with this definition, we can execute our workflow. We haven't\ndefined any work yet, but we can see the workflow execute. We'll see\nthe workflow executing by registering a subscriber that logs workflow\nevents:\n\n >>> def log_workflow(event):\n ... print event\n\n >>> import zope.event\n >>> zope.event.subscribers.append(log_workflow)\n\nTo use the workflow definition, we need to create an instance:\n\n >>> proc = pd()\n\nNow, if we start the workflow:\n\n >>> proc.start()\n ProcessStarted(Process('sample'))\n Transition(None, Activity('sample.author'))\n ActivityStarted(Activity('sample.author'))\n ActivityFinished(Activity('sample.author'))\n Transition(Activity('sample.author'), Activity('sample.review'))\n ActivityStarted(Activity('sample.review'))\n ActivityFinished(Activity('sample.review'))\n Transition(Activity('sample.review'), Activity('sample.publish'))\n ActivityStarted(Activity('sample.publish'))\n ActivityFinished(Activity('sample.publish'))\n ProcessFinished(Process('sample'))\n\nwe see that we transition immediately into the author activity, then\ninto review and publish. Normally, we'd need to do some work in each\nactivity, and transitions would continue only after work had been\ndone, however, in this case, we didn't define any work, so each\nactivity completed immediately.\n\nNote that we didn't transition into the rejected activity. By\ndefault, when an activity is completed, the first transition for which\nits condition evaluates to `True` is used. By default, transitions\nhave boolean conditions [1]_ that evaluate to `True`, so the transition\nto `publish` is used because it was defined before the transition to\n`reject`. What we want is to transition to `publish` if a reviewer\napproves the content for publication, but to `reject` if the reviewer\nrejects the content for publication. We can use a condition for this:\n\n >>> pd = process.ProcessDefinition('sample')\n >>> zope.component.provideUtility(pd, name=pd.id)\n Unregistered event:\n UtilityRegistration(, IProcessDefinition,\n 'sample', ProcessDefinition('sample'), None, u'')\n\n >>> pd.defineActivities(\n ... author = process.ActivityDefinition(),\n ... review = process.ActivityDefinition(),\n ... publish = process.ActivityDefinition(),\n ... reject = process.ActivityDefinition(),\n ... )\n >>> pd.defineTransitions(\n ... process.TransitionDefinition('author', 'review'),\n ... process.TransitionDefinition(\n ... 'review', 'publish', condition=lambda data: data.publish),\n ... process.TransitionDefinition('review', 'reject'),\n ... )\n\nWe redefined the workflow process, specifying a condition for the\ntransition to `publish`. Boolean conditions are just callable objects that\ntake a data object and return a boolean value. The data object is\ncalled \"workflow-relevant data\". A process instance has a data object\ncontaining this data. In the example, above, the condition simply\nreturned the value of the `publish` attribute. How does this attribute\nget set? It needs to be set by the review activity. To do that, we\nneed to arrange for the activity to set the data. This brings us to\napplications.\n\nProcess definitions are meant to be used with different\napplications. For this reason, process definitions don't include\napplication logic. What they do include is a specifications of the\napplications to be invoked and the flow of work-flow-relevant data to\nand from the application. Now, we can define our applications:\n\n >>> pd.defineApplications(\n ... author = process.Application(),\n ... review = process.Application(\n ... process.OutputParameter('publish')),\n ... publish = process.Application(),\n ... reject = process.Application(),\n ... )\n\nWe used the same names for the applications that we used for our\nactivities. This isn't required, but is a common practice. Note that\nthe `review` application includes a specification of an output\nparameter. Now that we've defined our applications, we need to modify\nour activities to use them:\n\n >>> pd.activities['author'].addApplication('author')\n >>> pd.activities['review'].addApplication('review', ['publish'])\n >>> pd.activities['publish'].addApplication('publish')\n >>> pd.activities['reject'].addApplication('reject')\n\nAn activity can use many applications, so we call `addApplication`.\nIn the application definition for the 'review' application, we\nprovided the name of a workflow-relevent data variable corresponding\nto the output parameter defined for the application. When using an\napplication in an activity, a workflow-relevent data variable name\nmust be provided for each of the parameters in the identified\napplications's signature. When an application is used in an activity,\nworkflow-relevent data are passed for each of the input parameters and\nare set by each of the output parameters. In this example, the output\nparameter, will be used to add a `publish` attribute to the workflow\nrelevant data.\n\nParticipants\n------------\n\nWe've declared some applications, and we've wired them up to\nactivities, but we still haven't specified any application code. Before\nwe can specify application code, we need to consider who will be\nperforming the application. Workflow applications are normally\nexecuted by people, or other external actors. As with applications,\nprocess definitions allow participants in the workflow to be declared\nand identified with activities. We declare participants much as we\ndeclare applications, except without parameters:\n\n >>> pd.defineParticipants(\n ... author = process.Participant(),\n ... reviewer = process.Participant(),\n ... )\n\nIn this case, we happened to reuse an activity name for one, but\nnot both of the participants. Having defined these participants, we\ncan associate them with activities:\n\n >>> pd.activities['author'].definePerformer('author')\n >>> pd.activities['review'].definePerformer('reviewer')\n\nApplication Integration\n-----------------------\n\nTo use a process definition to control application logic, we need to\nassociate it with an \"integration\" object.\n\nWhen a process needs to get a participant, it calls createParticipant\non its integration attribute, passing the process id and the\nperformer id. If an activity doesn't have a\nperformer, then the procedure above is used with an empty performer id.\n\nSimilarly, when a process needs a work item, it calls createWorkItem\non its integration attribute, passing the process id and the\napplication id.\n\nWork items provide a `start` method, which is used to start the work\nand pass input arguments. It is the responsibility of the work item,\nat some later time, to call the `workItemFinished` method on the\nactivity, to notify the activity that the work item was\ncompleted. Output parameters are passed to the `workItemFinished`\nmethod.\n\nA simple way to create integration objects is with\n`zope.wfmc.attributeintegration.AttributeIntegration`.\n\n >>> from zope.wfmc.attributeintegration import AttributeIntegration\n >>> integration = AttributeIntegration()\n >>> pd.integration = integration\n\nWe'll start by defining a simple Participant class:\n\n >>> import zope.interface\n >>> from zope.wfmc import interfaces\n\n >>> class Participant(object):\n ... zope.component.adapts(interfaces.IActivity)\n ... zope.interface.implements(interfaces.IParticipant)\n ...\n ... def __init__(self, activity):\n ... self.activity = activity\n\nWe set attributes on the integration for each participant:\n\n >>> integration.authorParticipant = Participant\n >>> integration.reviewerParticipant = Participant\n\nWe also define an attribute for participants for activities that don't\nhave performers:\n\n >>> integration.Participant = Participant\n\nNow we'll define our work-items. First we'll define some classes:\n\n >>> work_list = []\n\n >>> class ApplicationBase:\n ... zope.component.adapts(interfaces.IParticipant)\n ... zope.interface.implements(interfaces.IWorkItem)\n ...\n ... def __init__(self, participant):\n ... self.participant = participant\n ... work_list.append(self)\n ...\n ... def start(self):\n ... pass\n ...\n ... def finish(self):\n ... self.participant.activity.workItemFinished(self)\n\n >>> class Review(ApplicationBase):\n ... def finish(self, publish):\n ... self.participant.activity.workItemFinished(self, publish)\n\n >>> class Publish(ApplicationBase):\n ... def start(self):\n ... print \"Published\"\n ... self.finish()\n\n >>> class Reject(ApplicationBase):\n ... def start(self):\n ... print \"Rejected\"\n ... self.finish()\n\nand then we'll hook them up with the integration object:\n\n >>> integration.authorWorkItem = ApplicationBase\n >>> integration.reviewWorkItem = Review\n >>> integration.publishWorkItem = Publish\n >>> integration.rejectWorkItem = Reject\n\nUsing workflow processes\n------------------------\n\nTo use a process definition, instantiate it and call its start method\nto start execution:\n\n >>> proc = pd()\n >>> proc.start()\n ... # doctest: +NORMALIZE_WHITESPACE\n ProcessStarted(Process('sample'))\n Transition(None, Activity('sample.author'))\n ActivityStarted(Activity('sample.author'))\n\nWe transition into the author activity and wait for work to get done.\nTo move forward, we need to get at the authoring work item, so we can\nfinish it. Our work items add themselves to a work list, so we can\nget the item from the list.\n\n >>> item = work_list.pop()\n\nNow we can finish the work item, by calling its finish method:\n\n >>> item.finish()\n WorkItemFinished('author')\n ActivityFinished(Activity('sample.author'))\n Transition(Activity('sample.author'), Activity('sample.review'))\n ActivityStarted(Activity('sample.review'))\n\nWe see that we transitioned to the review activity. Note that the\n`finish` method isn't a part of the workflow APIs. It was defined by\nour sample classes. Other applications could use different mechanisms.\n\nNow, we'll finish the review process by calling the review work item's\n`finish`. We'll pass `False`, indicating that the content should not\nbe published:\n\n >>> work_list.pop().finish(False)\n WorkItemFinished('review')\n ActivityFinished(Activity('sample.review'))\n Transition(Activity('sample.review'), Activity('sample.reject'))\n ActivityStarted(Activity('sample.reject'))\n Rejected\n WorkItemFinished('reject')\n ActivityFinished(Activity('sample.reject'))\n ProcessFinished(Process('sample'))\n\nOrdering output transitions\n---------------------------\n\nNormally, outgoing transitions are ordered in the order of transition\ndefinition and all transitions from a given activity are used.\n\nIf transitions are defined in an inconvenient order, then the workflow\nmight not work as expected. For example, let's modify the above\nprocess by switching the order of definition of some of the\ntransitions. We'll reuse our integration object from the previous\nexample by passing it to the definition constructor:\n\n >>> pd = process.ProcessDefinition('sample', integration)\n >>> zope.component.provideUtility(pd, name=pd.id)\n Unregistered event:\n UtilityRegistration(, IProcessDefinition,\n 'sample', ProcessDefinition('sample'), None, u'')\n\n >>> pd.defineActivities(\n ... author = process.ActivityDefinition(),\n ... review = process.ActivityDefinition(),\n ... publish = process.ActivityDefinition(),\n ... reject = process.ActivityDefinition(),\n ... )\n >>> pd.defineTransitions(\n ... process.TransitionDefinition('author', 'review'),\n ... process.TransitionDefinition('review', 'reject'),\n ... process.TransitionDefinition(\n ... 'review', 'publish', condition=lambda data: data.publish),\n ... )\n\n >>> pd.defineApplications(\n ... author = process.Application(),\n ... review = process.Application(\n ... process.OutputParameter('publish')),\n ... publish = process.Application(),\n ... reject = process.Application(),\n ... )\n\n >>> pd.activities['author'].addApplication('author')\n >>> pd.activities['review'].addApplication('review', ['publish'])\n >>> pd.activities['publish'].addApplication('publish')\n >>> pd.activities['reject'].addApplication('reject')\n\n >>> pd.defineParticipants(\n ... author = process.Participant(),\n ... reviewer = process.Participant(),\n ... )\n\n >>> pd.activities['author'].definePerformer('author')\n >>> pd.activities['review'].definePerformer('reviewer')\n\nand run our process:\n\n >>> proc = pd()\n >>> proc.start()\n ... # doctest: +NORMALIZE_WHITESPACE\n ProcessStarted(Process('sample'))\n Transition(None, Activity('sample.author'))\n ActivityStarted(Activity('sample.author'))\n\n >>> work_list.pop().finish()\n WorkItemFinished('author')\n ActivityFinished(Activity('sample.author'))\n Transition(Activity('sample.author'), Activity('sample.review'))\n ActivityStarted(Activity('sample.review'))\n\nThis time, we'll say that we should publish:\n\n >>> work_list.pop().finish(True)\n WorkItemFinished('review')\n ActivityFinished(Activity('sample.review'))\n Transition(Activity('sample.review'), Activity('sample.reject'))\n ActivityStarted(Activity('sample.reject'))\n Rejected\n WorkItemFinished('reject')\n ActivityFinished(Activity('sample.reject'))\n ProcessFinished(Process('sample'))\n\nBut we went to the reject activity anyway. Why? Because transitions\nare tested in order. Because the transition to the reject activity was\ntested first and had no condition, we followed it without checking the\ncondition for the transition to the publish activity. We can fix this\nby specifying outgoing transitions on the reviewer activity directly.\nTo do this, we'll also need to specify ids in our transitions. Let's\nredefine the process:\n\n\n >>> pd = process.ProcessDefinition('sample', integration)\n >>> zope.component.provideUtility(pd, name=pd.id)\n Unregistered event:\n UtilityRegistration(, IProcessDefinition,\n 'sample', ProcessDefinition('sample'), None, u'')\n\n >>> pd.defineActivities(\n ... author = process.ActivityDefinition(),\n ... review = process.ActivityDefinition(),\n ... publish = process.ActivityDefinition(),\n ... reject = process.ActivityDefinition(),\n ... )\n >>> pd.defineTransitions(\n ... process.TransitionDefinition('author', 'review'),\n ... process.TransitionDefinition('review', 'reject', id='reject'),\n ... process.TransitionDefinition(\n ... 'review', 'publish', id='publish',\n ... condition=lambda data: data.publish),\n ... )\n\n >>> pd.defineApplications(\n ... author = process.Application(),\n ... review = process.Application(\n ... process.OutputParameter('publish')),\n ... publish = process.Application(),\n ... reject = process.Application(),\n ... )\n\n >>> pd.activities['author'].addApplication('author')\n >>> pd.activities['review'].addApplication('review', ['publish'])\n >>> pd.activities['publish'].addApplication('publish')\n >>> pd.activities['reject'].addApplication('reject')\n\n >>> pd.defineParticipants(\n ... author = process.Participant(),\n ... reviewer = process.Participant(),\n ... )\n\n >>> pd.activities['author'].definePerformer('author')\n >>> pd.activities['review'].definePerformer('reviewer')\n\n >>> pd.activities['review'].addOutgoing('publish')\n >>> pd.activities['review'].addOutgoing('reject')\n\nNow, when we run the process, we'll go to the publish activity as\nexpected:\n\n\n >>> proc = pd()\n >>> proc.start()\n ... # doctest: +NORMALIZE_WHITESPACE\n ProcessStarted(Process('sample'))\n Transition(None, Activity('sample.author'))\n ActivityStarted(Activity('sample.author'))\n\n >>> work_list.pop().finish()\n WorkItemFinished('author')\n ActivityFinished(Activity('sample.author'))\n Transition(Activity('sample.author'), Activity('sample.review'))\n ActivityStarted(Activity('sample.review'))\n\n >>> work_list.pop().finish(True)\n WorkItemFinished('review')\n ActivityFinished(Activity('sample.review'))\n Transition(Activity('sample.review'), Activity('sample.publish'))\n ActivityStarted(Activity('sample.publish'))\n Published\n WorkItemFinished('publish')\n ActivityFinished(Activity('sample.publish'))\n ProcessFinished(Process('sample'))\n\n\nLet's see the other way also, where we should transition to reject:\n\n\n >>> proc = pd()\n >>> proc.start()\n ... # doctest: +NORMALIZE_WHITESPACE\n ProcessStarted(Process('sample'))\n Transition(None, Activity('sample.author'))\n ActivityStarted(Activity('sample.author'))\n\n >>> work_list.pop().finish()\n WorkItemFinished('author')\n ActivityFinished(Activity('sample.author'))\n Transition(Activity('sample.author'), Activity('sample.review'))\n ActivityStarted(Activity('sample.review'))\n\n >>> work_list.pop().finish(False)\n WorkItemFinished('review')\n ActivityFinished(Activity('sample.review'))\n Transition(Activity('sample.review'), Activity('sample.reject'))\n ActivityStarted(Activity('sample.reject'))\n Rejected\n WorkItemFinished('reject')\n ActivityFinished(Activity('sample.reject'))\n ProcessFinished(Process('sample'))\n\n\nComplex Flows\n-------------\n\nLets look at a more complex example. In this example, we'll extend\nthe process to work with multiple reviewers. We'll also make the\nwork-list handling a bit more sophisticated. We'll also introduce\nsome new concepts:\n\n- splits and joins\n\n- process arguments\n\nConsider the publication\nprocess shown below::\n\n\n Author: Tech Tech Editorial\n Reviewer 1: Reviewer 2: Reviewer:\n =========== =========== =========== ==============\n ---------\n ----------------------------------------------------| Start |\n / ---------\n |\n V\n -----------\n | Prepare |<------------------------------\\\n ----------- \\\n | ------------ \\\n | | Tech |--------------- \\ \\\n |------->| Review 1 | V |\n | ------------ ---------- -------------\n \\ | Tech | | Editorial | ----------\n ------------------->| Review |--->| Review |-->| Reject |\n | 2 | ------------- ----------\n ---------- | |\n ----------- / \\\n | Prepare | / \\--------\\\n | Final |<----------------------------/ |\n ----------- |\n ^ | ---------- V\n | \\------------------------------->| Review | -----------\n \\ | Final |----->| Publish |\n ------------------------------------| | -----------\n ----------\n\nHere we've arranged the process diagram into columns, with the\nactivities for each participant. We have four participants, the\nauthor, two technical reviewers, and an editorial reviewer. The\nauthor prepares a draft. The author sends the draft to *both*\ntechnical reviewers for review. When the technical reviews have\ncompleted, the editorial review does an initial editorial\nreview. Based on the technical reviews, the editor may choose to:\n\n- Reject the document\n\n- Publish the document as is\n\n- Request technical changes (based on the technical reviewers'\n comments), or\n\n- Request editorial changes.\n\nIf technical changes are required, the work flows back to the\n\"Prepare\" activity. If editorial changes are necessary, then work\nflows to the \"Prepare Final\" activity. When the author has made the\neditorial changes, work flows to \"Review Final\". The editor may\nrequest additional changes, in which case, work flows back to \"Prepare\nFinal\", otherwise, the work flows to \"Publish\".\n\nThis example illustrates different kinds of \"joins\" and \"splits\". The\nterm \"join\" refers to the way incoming transitions to an activity are\nhandled. There are two kinds of joins: \"and\" and \"xor\". With an \"and\"\njoin, the activity waits for each of the incoming transitions. In\nthis example, the inputs to the \"Editorial Review\" activity form an\n\"and\" join. Editorial review waits until each of the technical\nreviews are completed. The rest of the joins in this example are\n\"xor\" joins. The activity starts on any transition into the activity.\n\nThe term \"split\" refers to way outgoing transitions from an activity\nare handled. Normally, exactly one transition out of an activity is\nused. This is called an \"xor\" split. With an \"and\" split, all\ntransitions with boolean conditions that evaluate to `True` are used.\nIn this example, the \"Prepare\" activity has an \"and\" split. Work\nflows simultaneously to the two technical review activities. The rest\nof the splits in this example are \"xor\" splits.\n\nLets create our new workflow process. We'll reuse our existing\nintegration object:\n\n >>> Publication = process.ProcessDefinition('Publication')\n >>> Publication.integration = integration\n >>> zope.component.provideUtility(Publication, name=Publication.id)\n\n >>> Publication.defineActivities(\n ... start = process.ActivityDefinition(\"Start\"),\n ... prepare = process.ActivityDefinition(\"Prepare\"),\n ... tech1 = process.ActivityDefinition(\"Technical Review 1\"),\n ... tech2 = process.ActivityDefinition(\"Technical Review 2\"),\n ... review = process.ActivityDefinition(\"Editorial Review\"),\n ... final = process.ActivityDefinition(\"Final Preparation\"),\n ... rfinal = process.ActivityDefinition(\"Review Final\"),\n ... publish = process.ActivityDefinition(\"Publish\"),\n ... reject = process.ActivityDefinition(\"Reject\"),\n ... )\n\nHere, we've passed strings to the activity definitions providing\nnames. Names must be either unicode or ASCII strings.\n\nWe define our transitions:\n\n >>> Publication.defineTransitions(\n ... process.TransitionDefinition('start', 'prepare'),\n ... process.TransitionDefinition('prepare', 'tech1'),\n ... process.TransitionDefinition('prepare', 'tech2'),\n ... process.TransitionDefinition('tech1', 'review'),\n ... process.TransitionDefinition('tech2', 'review'),\n ...\n ... process.TransitionDefinition(\n ... 'review', 'reject',\n ... condition=lambda data: not data.publish\n ... ),\n ... process.TransitionDefinition(\n ... 'review', 'prepare',\n ... condition=lambda data: data.tech_changes\n ... ),\n ... process.TransitionDefinition(\n ... 'review', 'final',\n ... condition=lambda data: data.ed_changes\n ... ),\n ... process.TransitionDefinition('review', 'publish'),\n ...\n ... process.TransitionDefinition('final', 'rfinal'),\n ... process.TransitionDefinition(\n ... 'rfinal', 'final',\n ... condition=lambda data: data.ed_changes\n ... ),\n ... process.TransitionDefinition('rfinal', 'publish'),\n ... )\n\nWe specify our \"and\" split and join:\n\n >>> Publication.activities['prepare'].andSplit(True)\n >>> Publication.activities['review'].andJoin(True)\n\nWe define our participants and applications:\n\n >>> Publication.defineParticipants(\n ... author = process.Participant(\"Author\"),\n ... tech1 = process.Participant(\"Technical Reviewer 1\"),\n ... tech2 = process.Participant(\"Technical Reviewer 2\"),\n ... reviewer = process.Participant(\"Editorial Reviewer\"),\n ... )\n\n >>> Publication.defineApplications(\n ... prepare = process.Application(),\n ... tech_review = process.Application(\n ... process.OutputParameter('publish'),\n ... process.OutputParameter('tech_changes'),\n ... ),\n ... ed_review = process.Application(\n ... process.InputParameter('publish1'),\n ... process.InputParameter('tech_changes1'),\n ... process.InputParameter('publish2'),\n ... process.InputParameter('tech_changes2'),\n ... process.OutputParameter('publish'),\n ... process.OutputParameter('tech_changes'),\n ... process.OutputParameter('ed_changes'),\n ... ),\n ... publish = process.Application(),\n ... reject = process.Application(),\n ... final = process.Application(),\n ... rfinal = process.Application(\n ... process.OutputParameter('ed_changes'),\n ... ),\n ... )\n\n >>> Publication.activities['prepare'].definePerformer('author')\n >>> Publication.activities['prepare'].addApplication('prepare')\n\n >>> Publication.activities['tech1'].definePerformer('tech1')\n >>> Publication.activities['tech1'].addApplication(\n ... 'tech_review', ['publish1', 'tech_changes1'])\n\n >>> Publication.activities['tech2'].definePerformer('tech2')\n >>> Publication.activities['tech2'].addApplication(\n ... 'tech_review', ['publish2', 'tech_changes2'])\n\n >>> Publication.activities['review'].definePerformer('reviewer')\n >>> Publication.activities['review'].addApplication(\n ... 'ed_review',\n ... ['publish1', 'tech_changes1', 'publish2', 'tech_changes2',\n ... 'publish', 'tech_changes', 'ed_changes'],\n ... )\n\n >>> Publication.activities['final'].definePerformer('author')\n >>> Publication.activities['final'].addApplication('final')\n\n >>> Publication.activities['rfinal'].definePerformer('reviewer')\n >>> Publication.activities['rfinal'].addApplication(\n ... 'rfinal', ['ed_changes'],\n ... )\n\n >>> Publication.activities['publish'].addApplication('publish')\n >>> Publication.activities['reject'].addApplication('reject')\n\nWe want to be able to specify an author when we start the process.\nWe'd also like to be told the final disposition of the process. To\naccomplish this, we'll define parameters for our process:\n\n >>> Publication.defineParameters(\n ... process.InputParameter('author'),\n ... process.OutputParameter('publish'),\n ... )\n\nNow that we've defined the process, we need to provide participant and\napplication components. Let's start with our participants. Rather\nthan sharing a single work list, we'll give each user their own\nwork list. We'll also create preexisting participants and return\nthem. Finally, we'll create multiple authors and use the selected one:\n\n\n >>> class User:\n ... def __init__(self):\n ... self.work_list = []\n\n >>> authors = {'bob': User(), 'ted': User(), 'sally': User()}\n\n >>> reviewer = User()\n >>> tech1 = User()\n >>> tech2 = User()\n\n >>> class Author(Participant):\n ... def __init__(self, activity):\n ... Participant.__init__(self, activity)\n ... author_name = activity.process.workflowRelevantData.author\n ... print \"Author `%s` selected\" % author_name\n ... self.user = authors[author_name]\n\nIn this example, we need to define a separate attribute for each participant:\n\n >>> integration.authorParticipant = Author\n\nWhen the process is created, the author name will be passed in and\nassigned to the workflow-relevant data. Our author class uses this\ninformation to select the named user.\n\n >>> class Reviewer(Participant):\n ... user = reviewer\n >>> integration.reviewerParticipant = Reviewer\n\n >>> class Tech1(Participant):\n ... user = tech1\n >>> integration.tech1Participant = Tech1\n\n >>> class Tech2(Participant):\n ... user = tech2\n >>> integration.tech2Participant = Tech2\n\nWe'll use our orginal participation class for activities without\nperformers:\n\n >>> integration.Participant = Participant\n\nNow we'll create our applications. Let's start with our author:\n\n >>> class ApplicationBase(object):\n ... zope.component.adapts(interfaces.IParticipant)\n ... zope.interface.implements(interfaces.IWorkItem)\n ...\n ... def __init__(self, participant):\n ... self.participant = participant\n ... self.activity = participant.activity\n ... participant.user.work_list.append(self)\n ...\n ... def start(self):\n ... pass\n ...\n ... def finish(self):\n ... self.participant.activity.workItemFinished(self)\n\n >>> class Prepare(ApplicationBase):\n ...\n ... def summary(self):\n ... process = self.activity.process\n ... doc = getattr(process.applicationRelevantData, 'doc', '')\n ... if doc:\n ... print 'Previous draft:'\n ... print doc\n ... print 'Changes we need to make:'\n ... for change in process.workflowRelevantData.tech_changes:\n ... print change\n ... else:\n ... print 'Please write the initial draft'\n ...\n ... def finish(self, doc):\n ... self.activity.process.applicationRelevantData.doc = doc\n ... super(Prepare, self).finish()\n\n >>> integration.prepareWorkItem = Prepare\n\nSince we used the prepare application for revisions as well as initial\npreparation, we provide a summary method to show us what we have to do.\n\nHere we get the document created by the author passed in as an\nargument to the finish method. In a more realistic implementation,\nthe author task would create the document at the start of the task and\nprovide a user interface for the user to edit it. We store the\ndocument as application-relevant data, since we'll want reviewers to\nbe able to access it, but we don't need it directly for workflow\ncontrol.\n\n >>> class TechReview(ApplicationBase):\n ...\n ... def getDoc(self):\n ... return self.activity.process.applicationRelevantData.doc\n ...\n ... def finish(self, decision, changes):\n ... self.activity.workItemFinished(self, decision, changes)\n\n >>> integration.tech_reviewWorkItem = TechReview\n\nHere, we provided a method to access the original document.\n\n >>> class Review(TechReview):\n ...\n ... def start(self, publish1, changes1, publish2, changes2):\n ... if not (publish1 and publish2):\n ... # Reject if either tech reviewer rejects\n ... self.activity.workItemFinished(\n ... self, False, changes1 + changes2, ())\n ...\n ... if changes1 or changes2:\n ... # we won't do anything if there are tech changes\n ... self.activity.workItemFinished(\n ... self, True, changes1 + changes2, ())\n ...\n ... def finish(self, ed_changes):\n ... self.activity.workItemFinished(self, True, (), ed_changes)\n\n >>> integration.ed_reviewWorkItem = Review\n\nIn this implementation, we decided to reject outright if either\ntechnical editor recommended rejection and to send work back to\npreparation if there are any technical changes. We also subclassed\n`TechReview` to get the `getDoc` method.\n\nWe'll reuse the `publish` and `reject` application from the previous\nexample.\n\n >>> class Final(ApplicationBase):\n ...\n ... def summary(self):\n ... process = self.activity.process\n ... doc = getattr(process.applicationRelevantData, 'doc', '')\n ... print 'Previous draft:'\n ... print self.activity.process.applicationRelevantData.doc\n ... print 'Changes we need to make:'\n ... for change in process.workflowRelevantData.ed_changes:\n ... print change\n ...\n ... def finish(self, doc):\n ... self.activity.process.applicationRelevantData.doc = doc\n ... super(Final, self).finish()\n\n >>> integration.finalWorkItem = Final\n\nIn our this application, we simply update the document to reflect\nchanges.\n\n >>> class ReviewFinal(TechReview):\n ...\n ... def finish(self, ed_changes):\n ... self.activity.workItemFinished(self, ed_changes)\n\n >>> integration.rfinalWorkItem = ReviewFinal\n\nOur process now returns data. When we create a process, we need to\nsupply an object that it can call back to:\n\n >>> class PublicationContext:\n ... zope.interface.implements(interfaces.IProcessContext)\n ...\n ... def processFinished(self, process, decision):\n ... self.decision = decision\n\nNow, let's try out our process:\n\n >>> context = PublicationContext()\n >>> proc = Publication(context)\n >>> proc.start('bob')\n ProcessStarted(Process('Publication'))\n Transition(None, Activity('Publication.start'))\n ActivityStarted(Activity('Publication.start'))\n ActivityFinished(Activity('Publication.start'))\n Author `bob` selected\n Transition(Activity('Publication.start'), Activity('Publication.prepare'))\n ActivityStarted(Activity('Publication.prepare'))\n\nWe should have added an item to bob's work list. Let's get it and\nfinish it, submitting a document:\n\n >>> item = authors['bob'].work_list.pop()\n >>> item.finish(\"I give my pledge, as an American\\n\"\n ... \"to save, and faithfully to defend from waste\\n\"\n ... \"the natural resources of my Country.\")\n WorkItemFinished('prepare')\n ActivityFinished(Activity('Publication.prepare'))\n Transition(Activity('Publication.prepare'), Activity('Publication.tech1'))\n ActivityStarted(Activity('Publication.tech1'))\n Transition(Activity('Publication.prepare'), Activity('Publication.tech2'))\n ActivityStarted(Activity('Publication.tech2'))\n\nNotice that we transitioned to *two* activities, `tech1` and\n`tech2`. This is because the prepare activity has an \"and\" split.\nNow we'll do a tech review. Let's see what tech1 has:\n\n >>> item = tech1.work_list.pop()\n >>> print item.getDoc()\n I give my pledge, as an American\n to save, and faithfully to defend from waste\n the natural resources of my Country.\n\nLet's tell the author to change \"American\" to \"Earthling\":\n\n >>> item.finish(True, ['Change \"American\" to \"Earthling\"'])\n WorkItemFinished('tech_review')\n ActivityFinished(Activity('Publication.tech1'))\n Transition(Activity('Publication.tech1'), Activity('Publication.review'))\n\nHere we transitioned to the editorial review activity, but we didn't\nstart it. This is because the editorial review activity has an \"and\"\njoin, meaning that it won't start until both transitions have\noccurred.\n\nNow we'll do the other technical review:\n\n >>> item = tech2.work_list.pop()\n >>> item.finish(True, ['Change \"Country\" to \"planet\"'])\n WorkItemFinished('tech_review')\n ActivityFinished(Activity('Publication.tech2'))\n Transition(Activity('Publication.tech2'), Activity('Publication.review'))\n ActivityStarted(Activity('Publication.review'))\n WorkItemFinished('ed_review')\n ActivityFinished(Activity('Publication.review'))\n Author `bob` selected\n Transition(Activity('Publication.review'), Activity('Publication.prepare'))\n ActivityStarted(Activity('Publication.prepare'))\n\nNow when we transitioned to the editorial review activity, we started\nit, because each of the input transitions had happened. Our editorial\nreview application automatically sent the work back to preparation,\nbecause there were technical comments. Of course the author is still `bob`.\nLet's address the comments:\n\n >>> item = authors['bob'].work_list.pop()\n >>> item.summary()\n Previous draft:\n I give my pledge, as an American\n to save, and faithfully to defend from waste\n the natural resources of my Country.\n Changes we need to make:\n Change \"American\" to \"Earthling\"\n Change \"Country\" to \"planet\"\n\n >>> item.finish(\"I give my pledge, as an Earthling\\n\"\n ... \"to save, and faithfully to defend from waste\\n\"\n ... \"the natural resources of my planet.\")\n WorkItemFinished('prepare')\n ActivityFinished(Activity('Publication.prepare'))\n Transition(Activity('Publication.prepare'), Activity('Publication.tech1'))\n ActivityStarted(Activity('Publication.tech1'))\n Transition(Activity('Publication.prepare'), Activity('Publication.tech2'))\n ActivityStarted(Activity('Publication.tech2'))\n\nAs before, after completing the initial edits, we start the technical\nreview activities again. We'll review it again. This time, we have no\ncomments, because the author applied our requested changes:\n\n >>> item = tech1.work_list.pop()\n >>> item.finish(True, [])\n WorkItemFinished('tech_review')\n ActivityFinished(Activity('Publication.tech1'))\n Transition(Activity('Publication.tech1'), Activity('Publication.review'))\n\n >>> item = tech2.work_list.pop()\n >>> item.finish(True, [])\n WorkItemFinished('tech_review')\n ActivityFinished(Activity('Publication.tech2'))\n Transition(Activity('Publication.tech2'), Activity('Publication.review'))\n ActivityStarted(Activity('Publication.review'))\n\nThis time, we are left in the technical review activity because there\nweren't any technical changes. We're ready to do our editorial review.\nWe'll request an editorial change:\n\n >>> item = reviewer.work_list.pop()\n >>> print item.getDoc()\n I give my pledge, as an Earthling\n to save, and faithfully to defend from waste\n the natural resources of my planet.\n\n >>> item.finish(['change \"an\" to \"a\"'])\n WorkItemFinished('ed_review')\n ActivityFinished(Activity('Publication.review'))\n Author `bob` selected\n Transition(Activity('Publication.review'), Activity('Publication.final'))\n ActivityStarted(Activity('Publication.final'))\n\nBecause we requested editorial changes, we transitioned to the final\nediting activity, so that the author (still bob) can make the changes:\n\n >>> item = authors['bob'].work_list.pop()\n >>> item.summary()\n Previous draft:\n I give my pledge, as an Earthling\n to save, and faithfully to defend from waste\n the natural resources of my planet.\n Changes we need to make:\n change \"an\" to \"a\"\n\n >>> item.finish(\"I give my pledge, as a Earthling\\n\"\n ... \"to save, and faithfully to defend from waste\\n\"\n ... \"the natural resources of my planet.\")\n WorkItemFinished('final')\n ActivityFinished(Activity('Publication.final'))\n Transition(Activity('Publication.final'), Activity('Publication.rfinal'))\n ActivityStarted(Activity('Publication.rfinal'))\n\nWe transition to the activity for reviewing the final edits. We\nreview the document and approve it for publication:\n\n >>> item = reviewer.work_list.pop()\n >>> print item.getDoc()\n I give my pledge, as a Earthling\n to save, and faithfully to defend from waste\n the natural resources of my planet.\n\n >>> item.finish([])\n WorkItemFinished('rfinal')\n ActivityFinished(Activity('Publication.rfinal'))\n Transition(Activity('Publication.rfinal'), Activity('Publication.publish'))\n ActivityStarted(Activity('Publication.publish'))\n Published\n WorkItemFinished('publish')\n ActivityFinished(Activity('Publication.publish'))\n ProcessFinished(Process('Publication'))\n\nAt this point, the rest of the process finished automatically. In\naddition, the decision was recorded in the process context object:\n\n >>> context.decision\n True\n\nComing Soon\n------------\n\n- XPDL support\n\n- Timeouts/exceptions\n\n- \"otherwise\" conditions\n\n\n.. [1] There are other kinds of conditions, namely \"otherwise\" and\n \"exception\" conditions.\n\nSee also\n---------\nhttp://www.wfmc.org\nhttp://www.wfmc.org/standards/standards.htm\n\n\n===========\nXPDL Import\n===========\n\nWe can import process definitions from files in the XML Process\nDefinition Language (XPDL) format. An XPDL file contains multiple\nprocess definitions arranged in a package. When we load the file, we\nget a package containing some number of process definitions.\n\nLet's look at an example. The file `publication.xpdl`\ncontains a definition for the publication example developed in the\n\"README.txt\" file. We can read it using the xpdl module:\n\n >>> from zope.wfmc import xpdl\n >>> import os\n >>> package = xpdl.read(open(os.path.join(this_directory,\n ... 'publication.xpdl')))\n\nThis package contains a single definition:\n\n >>> package\n {u'Publication': ProcessDefinition(u'Publication')}\n\n >>> pd = package[u'Publication']\n >>> from zope.wfmc.attributeintegration import AttributeIntegration\n >>> integration = AttributeIntegration()\n >>> pd.integration = integration\n\nNow, having read the process definition, we can use it as we did\nbefore (in \"README.txt\"). As before, we'll create an event subscriber\nso that we can see what's going on:\n\n >>> def log_workflow(event):\n ... print event\n\n >>> import zope.event\n >>> zope.event.subscribers.append(log_workflow)\n\nand we'll register the process definition as a utility:\n\n >>> import zope.component\n >>> zope.component.provideUtility(pd, name=pd.id)\n\nand we'll define and register participant and application adapters:\n\n >>> import zope.interface\n >>> from zope.wfmc import interfaces\n\n >>> class Participant(object):\n ... zope.component.adapts(interfaces.IActivity)\n ... zope.interface.implements(interfaces.IParticipant)\n ...\n ... def __init__(self, activity):\n ... self.activity = activity\n\n >>> class User:\n ... def __init__(self):\n ... self.work_list = []\n\n >>> authors = {'bob': User(), 'ted': User(), 'sally': User()}\n\n >>> reviewer = User()\n >>> tech1 = User()\n >>> tech2 = User()\n\n >>> class Author(Participant):\n ... def __init__(self, activity):\n ... Participant.__init__(self, activity)\n ... author_name = activity.process.workflowRelevantData.author\n ... print \"Author `%s` selected\" % author_name\n ... self.user = authors[author_name]\n\n >>> integration.authorParticipant = Author\n\n >>> class Reviewer(Participant):\n ... user = reviewer\n >>> integration.reviewerParticipant = Reviewer\n\n >>> class Tech1(Participant):\n ... user = tech1\n >>> integration.tech1Participant = Tech1\n\n >>> class Tech2(Participant):\n ... user = tech2\n >>> integration.tech2Participant = Tech2\n\n >>> integration.SystemParticipant = Participant\n\n >>> class ApplicationBase(object):\n ... zope.component.adapts(interfaces.IParticipant)\n ... zope.interface.implements(interfaces.IWorkItem)\n ...\n ... def __init__(self, participant):\n ... self.participant = participant\n ... self.activity = participant.activity\n ... participant.user.work_list.append(self)\n ...\n ... def start(self):\n ... pass\n ...\n ... def finish(self):\n ... self.participant.activity.workItemFinished(self)\n\n >>> class Prepare(ApplicationBase):\n ...\n ... def summary(self):\n ... process = self.activity.process\n ... doc = getattr(process.applicationRelevantData, 'doc', '')\n ... if doc:\n ... print 'Previous draft:'\n ... print doc\n ... print 'Changes we need to make:'\n ... for change in process.workflowRelevantData.tech_changes:\n ... print change\n ... else:\n ... print 'Please write the initial draft'\n ...\n ... def finish(self, doc):\n ... self.activity.process.applicationRelevantData.doc = doc\n ... super(Prepare, self).finish()\n\n >>> integration.prepareWorkItem = Prepare\n\n >>> class TechReview(ApplicationBase):\n ...\n ... def getDoc(self):\n ... return self.activity.process.applicationRelevantData.doc\n ...\n ... def finish(self, decision, changes):\n ... self.activity.workItemFinished(self, decision, changes)\n\n >>> integration.tech_reviewWorkItem = TechReview\n\n >>> class Review(TechReview):\n ...\n ... def start(self, publish1, changes1, publish2, changes2):\n ... if not (publish1 and publish2):\n ... # Reject if either tech reviewer rejects\n ... self.activity.workItemFinished(\n ... self, False, changes1 + changes2, ())\n ...\n ... if changes1 or changes2:\n ... # we won't do anyting if there are tech changes\n ... self.activity.workItemFinished(\n ... self, True, changes1 + changes2, ())\n ...\n ... def finish(self, ed_changes):\n ... self.activity.workItemFinished(self, True, (), ed_changes)\n\n >>> integration.ed_reviewWorkItem = Review\n\n >>> class Final(ApplicationBase):\n ...\n ... def summary(self):\n ... process = self.activity.process\n ... doc = getattr(process.applicationRelevantData, 'doc', '')\n ... print 'Previous draft:'\n ... print self.activity.process.applicationRelevantData.doc\n ... print 'Changes we need to make:'\n ... for change in process.workflowRelevantData.ed_changes:\n ... print change\n ...\n ... def finish(self, doc):\n ... self.activity.process.applicationRelevantData.doc = doc\n ... super(Final, self).finish()\n\n >>> integration.finalWorkItem = Final\n\n >>> class ReviewFinal(TechReview):\n ...\n ... def finish(self, ed_changes):\n ... self.activity.workItemFinished(self, ed_changes)\n\n >>> integration.rfinalWorkItem = ReviewFinal\n\n\n >>> class Publish:\n ... zope.component.adapts(interfaces.IParticipant)\n ... zope.interface.implements(interfaces.IWorkItem)\n ...\n ... def __init__(self, participant):\n ... self.participant = participant\n ...\n ... def start(self):\n ... print \"Published\"\n ... self.finish()\n ...\n ... def finish(self):\n ... self.participant.activity.workItemFinished(self)\n\n\n >>> integration.publishWorkItem = Publish\n\n >>> class Reject(Publish):\n ... def start(self):\n ... print \"Rejected\"\n ... self.finish()\n\n >>> integration.rejectWorkItem = Reject\n\nand a process context, so we can pass parameters:\n\n >>> class PublicationContext:\n ... zope.interface.implements(interfaces.IProcessContext)\n ...\n ... def processFinished(self, process, decision):\n ... self.decision = decision\n\nNow, let's try out our process. We'll follow the same steps we did in\n\"README.txt\", getting the same results:\n\n >>> context = PublicationContext()\n >>> proc = pd(context)\n >>> proc.start('bob')\n ProcessStarted(Process(u'Publication'))\n Transition(None, Activity(u'Publication.start'))\n ActivityStarted(Activity(u'Publication.start'))\n ActivityFinished(Activity(u'Publication.start'))\n Author `bob` selected\n Transition(Activity(u'Publication.start'),\n Activity(u'Publication.prepare'))\n ActivityStarted(Activity(u'Publication.prepare'))\n\n >>> item = authors['bob'].work_list.pop()\n >>> item.finish(\"I give my pledge, as an American\\n\"\n ... \"to save, and faithfully to defend from waste\\n\"\n ... \"the natural resources of my Country.\")\n WorkItemFinished(u'prepare')\n ActivityFinished(Activity(u'Publication.prepare'))\n Transition(Activity(u'Publication.prepare'),\n Activity(u'Publication.tech1'))\n ActivityStarted(Activity(u'Publication.tech1'))\n Transition(Activity(u'Publication.prepare'),\n Activity(u'Publication.tech2'))\n ActivityStarted(Activity(u'Publication.tech2'))\n\n >>> item = tech1.work_list.pop()\n >>> print item.getDoc()\n I give my pledge, as an American\n to save, and faithfully to defend from waste\n the natural resources of my Country.\n\n >>> item.finish(True, ['Change \"American\" to \"human\"'])\n WorkItemFinished(u'tech_review')\n ActivityFinished(Activity(u'Publication.tech1'))\n Transition(Activity(u'Publication.tech1'),\n Activity(u'Publication.review'))\n\n >>> item = tech2.work_list.pop()\n >>> item.finish(True, ['Change \"Country\" to \"planet\"'])\n WorkItemFinished(u'tech_review')\n ActivityFinished(Activity(u'Publication.tech2'))\n Transition(Activity(u'Publication.tech2'),\n Activity(u'Publication.review'))\n ActivityStarted(Activity(u'Publication.review'))\n WorkItemFinished(u'ed_review')\n ActivityFinished(Activity(u'Publication.review'))\n Author `bob` selected\n Transition(Activity(u'Publication.review'),\n Activity(u'Publication.prepare'))\n ActivityStarted(Activity(u'Publication.prepare'))\n\n >>> item = authors['bob'].work_list.pop()\n >>> item.summary()\n Previous draft:\n I give my pledge, as an American\n to save, and faithfully to defend from waste\n the natural resources of my Country.\n Changes we need to make:\n Change \"American\" to \"human\"\n Change \"Country\" to \"planet\"\n\n >>> item.finish(\"I give my pledge, as an human\\n\"\n ... \"to save, and faithfully to defend from waste\\n\"\n ... \"the natural resources of my planet.\")\n WorkItemFinished(u'prepare')\n ActivityFinished(Activity(u'Publication.prepare'))\n Transition(Activity(u'Publication.prepare'),\n Activity(u'Publication.tech1'))\n ActivityStarted(Activity(u'Publication.tech1'))\n Transition(Activity(u'Publication.prepare'),\n Activity(u'Publication.tech2'))\n ActivityStarted(Activity(u'Publication.tech2'))\n\n >>> item = tech1.work_list.pop()\n >>> item.finish(True, [])\n WorkItemFinished(u'tech_review')\n ActivityFinished(Activity(u'Publication.tech1'))\n Transition(Activity(u'Publication.tech1'),\n Activity(u'Publication.review'))\n\n >>> item = tech2.work_list.pop()\n >>> item.finish(True, [])\n WorkItemFinished(u'tech_review')\n ActivityFinished(Activity(u'Publication.tech2'))\n Transition(Activity(u'Publication.tech2'),\n Activity(u'Publication.review'))\n ActivityStarted(Activity(u'Publication.review'))\n\n >>> item = reviewer.work_list.pop()\n >>> print item.getDoc()\n I give my pledge, as an human\n to save, and faithfully to defend from waste\n the natural resources of my planet.\n\n >>> item.finish(['change \"an\" to \"a\"'])\n WorkItemFinished(u'ed_review')\n ActivityFinished(Activity(u'Publication.review'))\n Author `bob` selected\n Transition(Activity(u'Publication.review'),\n Activity(u'Publication.final'))\n ActivityStarted(Activity(u'Publication.final'))\n\n >>> item = authors['bob'].work_list.pop()\n >>> item.summary()\n Previous draft:\n I give my pledge, as an human\n to save, and faithfully to defend from waste\n the natural resources of my planet.\n Changes we need to make:\n change \"an\" to \"a\"\n\n >>> item.finish(\"I give my pledge, as a human\\n\"\n ... \"to save, and faithfully to defend from waste\\n\"\n ... \"the natural resources of my planet.\")\n WorkItemFinished(u'final')\n ActivityFinished(Activity(u'Publication.final'))\n Transition(Activity(u'Publication.final'),\n Activity(u'Publication.rfinal'))\n ActivityStarted(Activity(u'Publication.rfinal'))\n\n >>> item = reviewer.work_list.pop()\n >>> print item.getDoc()\n I give my pledge, as a human\n to save, and faithfully to defend from waste\n the natural resources of my planet.\n\n >>> item.finish([])\n WorkItemFinished(u'rfinal')\n ActivityFinished(Activity(u'Publication.rfinal'))\n Transition(Activity(u'Publication.rfinal'),\n Activity(u'Publication.publish'))\n ActivityStarted(Activity(u'Publication.publish'))\n Published\n WorkItemFinished(u'publish')\n ActivityFinished(Activity(u'Publication.publish'))\n ProcessFinished(Process(u'Publication'))\n\n >>> context.decision\n True\n\n\nDescriptions\n------------\n\nMost process elements can have names and descriptions.\n\n >>> pd.__name__\n u'Publication'\n\n >>> pd.description\n u'This is the sample process'\n\n >>> pd.applications['prepare'].__name__\n u'Prepare'\n\n >>> pd.applications['prepare'].description\n u'Prepare the initial draft'\n\n >>> pd.activities['tech1'].__name__\n u'Technical Review 1'\n\n >>> pd.activities['tech1'].description\n u'This is the first Technical Review.'\n\n >>> pd.participants['tech1'].__name__\n u'Technical Reviewer 1'\n\n >>> pd.participants['tech1'].description\n u'He is a smart guy.'\n\n >>> sorted([item.__name__ for item in pd.transitions])\n [u'Transition', u'Transition', u'Transition', u'Transition',\n u'Transition', u'Transition', u'Transition', u'Transition',\n u'Transition', u'Transition', u'Transition to Tech Review 1',\n u'Transition to Tech Review 2']\n\n >>> sorted([item.description for item in pd.transitions])\n [None, None, None, None, None, None, None, None, None, None, None,\n u'Use this transition if there are editorial changes required.']\n\n\n=======\nCHANGES\n=======\n\n3.5.0 (2009-07-24)\n------------------\n\n- Update tests to latest package versions.\n\n\n3.4.0 (2007-11-02)\n------------------\n\n- Initial release independent of the main Zope tree.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://cheeseshop.python.org/pypi/zope.wfmc", "keywords": "zope3 wfmc xpdl workflow engine", "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "zope.wfmc", "package_url": "https://pypi.org/project/zope.wfmc/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/zope.wfmc/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://cheeseshop.python.org/pypi/zope.wfmc" }, "release_url": "https://pypi.org/project/zope.wfmc/3.5.0/", "requires_dist": null, "requires_python": null, "summary": "Workflow-Management Coalition Workflow Engine", "version": "3.5.0" }, "last_serial": 805526, "releases": { "3.4.0": [ { "comment_text": "", "digests": { "md5": "da62f6147b359dd4be53c367337b7d02", "sha256": "65cdedaf01cc282cbff9fb9c792e5362fc35bb885ae8fa9902ce2cb47d5f7477" }, "downloads": -1, "filename": "zope.wfmc-3.4.0.tar.gz", "has_sig": false, "md5_digest": "da62f6147b359dd4be53c367337b7d02", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46091, "upload_time": "2007-11-03T00:23:11", "url": "https://files.pythonhosted.org/packages/fd/58/6a5bf4394afcda8587425281872e7d758c7968f405968501e73f4bf9aca0/zope.wfmc-3.4.0.tar.gz" } ], "3.5.0": [ { "comment_text": "", "digests": { "md5": "4902b78de392751d6c9d3db3cdd488c4", "sha256": "b62dc6bb573e5275a80856f1937f4860fe51a9aec674af00bde78b0ebae63cf1" }, "downloads": -1, "filename": "zope.wfmc-3.5.0.tar.gz", "has_sig": false, "md5_digest": "4902b78de392751d6c9d3db3cdd488c4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46905, "upload_time": "2009-07-24T14:47:58", "url": "https://files.pythonhosted.org/packages/12/9a/88f167a3bdb6796b2800536c16db35a08e5f1c17955d13d2fb771d952515/zope.wfmc-3.5.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "4902b78de392751d6c9d3db3cdd488c4", "sha256": "b62dc6bb573e5275a80856f1937f4860fe51a9aec674af00bde78b0ebae63cf1" }, "downloads": -1, "filename": "zope.wfmc-3.5.0.tar.gz", "has_sig": false, "md5_digest": "4902b78de392751d6c9d3db3cdd488c4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46905, "upload_time": "2009-07-24T14:47:58", "url": "https://files.pythonhosted.org/packages/12/9a/88f167a3bdb6796b2800536c16db35a08e5f1c17955d13d2fb771d952515/zope.wfmc-3.5.0.tar.gz" } ] }