工作流管理联盟工作流引擎
项目描述
此软件包提供了一个工作流管理联盟(WFMC)工作流引擎的实现。该引擎提供了一组工作流过程组件。工作流过程可以使用Python或XML过程定义语言(XPDL)定义。
详细文档
工作流管理联盟工作流引擎
此软件包提供了一个工作流管理联盟(WFMC)工作流引擎的实现。该引擎提供了一组工作流过程组件。工作流过程可以使用Python或XML过程定义语言(XPDL)定义。
在本文档中,我们将查看Python定义的过程定义
>>> from zope.wfmc import process >>> pd = process.ProcessDefinition('sample')
过程的参数是进程ID。
一个过程有多个部分。让我们看看一个示例审查过程
----------- -->| Publish | ---------- ---------- / ----------- | Author |-->| Review |- ---------- ---------- ---------- \-->| Reject | ----------
这里有一个单独的开始活动和两个结束活动。我们可以用一个单独的结束活动来模拟这个,但这不是必需的。一个单独的开始活动 是 必需的。过程定义有一组活动,以及它们之间的转换。让我们定义我们的过程定义中的活动
>>> pd.defineActivities( ... author = process.ActivityDefinition(), ... review = process.ActivityDefinition(), ... publish = process.ActivityDefinition(), ... reject = process.ActivityDefinition(), ... )
我们将活动作为关键字参数提供。参数名称提供了活动ID,我们在定义转换时将使用这些ID
>>> pd.defineTransitions( ... process.TransitionDefinition('author', 'review'), ... process.TransitionDefinition('review', 'publish'), ... process.TransitionDefinition('review', 'reject'), ... )
每个转换由一个起始活动的标识符和一个结束活动的标识符组成。
在我们可以使用工作流定义之前,我们必须将其注册为实用程序。这是必要的,以便过程实例可以找到它们的定义。此外,实用程序名称必须与进程ID匹配
>>> import zope.component >>> zope.component.provideUtility(pd, name=pd.id)
现在,有了这个定义,我们可以执行我们的工作流。我们还没有定义任何工作,但我们可以看到工作流正在执行。我们将通过注册一个记录工作流事件的订阅者来查看工作流的执行情况
>>> def log_workflow(event): ... print event>>> import zope.event >>> zope.event.subscribers.append(log_workflow)
要使用工作流定义,我们需要创建一个实例
>>> proc = pd()
现在,如果我们启动工作流
>>> proc.start() ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author')) ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review')) ActivityFinished(Activity('sample.review')) Transition(Activity('sample.review'), Activity('sample.publish')) ActivityStarted(Activity('sample.publish')) ActivityFinished(Activity('sample.publish')) ProcessFinished(Process('sample'))
我们可以看到,我们立即进入作者活动,然后进入评审和发布。通常情况下,我们需要在每个活动中完成一些工作,并且只有完成工作后,转换才会继续。然而,在这种情况下,我们没有定义任何工作,因此每个活动都立即完成。
请注意,我们没有进入被拒绝的活动。默认情况下,当活动完成时,会使用其条件评估为 True 的第一个转换。默认情况下,转换具有布尔条件 [1],评估为 True,因此使用到 发布 的转换,因为它的定义在 拒绝 转换之前。我们想要的是,如果审阅者批准内容发布,则转换为 发布,但如果审阅者拒绝内容发布,则转换为 拒绝。我们可以使用一个条件来做到这一点。
>>> pd = process.ProcessDefinition('sample') >>> zope.component.provideUtility(pd, name=pd.id) Unregistered event: UtilityRegistration(<BaseGlobalComponents base>, IProcessDefinition, 'sample', ProcessDefinition('sample'), None, u'')>>> pd.defineActivities( ... author = process.ActivityDefinition(), ... review = process.ActivityDefinition(), ... publish = process.ActivityDefinition(), ... reject = process.ActivityDefinition(), ... ) >>> pd.defineTransitions( ... process.TransitionDefinition('author', 'review'), ... process.TransitionDefinition( ... 'review', 'publish', condition=lambda data: data.publish), ... process.TransitionDefinition('review', 'reject'), ... )
我们重新定义了工作流过程,指定了转换为 发布 的条件。布尔条件只是可调用的对象,它接受一个数据对象并返回一个布尔值。该数据对象被称为“工作流相关数据”。一个流程实例包含包含此数据的数据对象。在上面的示例中,条件简单地返回了 发布 属性的值。这个属性是如何设置的?它需要由评审活动来设置。为此,我们需要安排活动来设置数据。这使我们来到了应用程序。
流程定义旨在与不同的应用程序一起使用。因此,流程定义不包含应用程序逻辑。它们包含的是要调用的应用程序的规范以及与应用程序之间工作流相关数据的流向。现在,我们可以定义我们的应用程序。
>>> pd.defineApplications( ... author = process.Application(), ... review = process.Application( ... process.OutputParameter('publish')), ... publish = process.Application(), ... reject = process.Application(), ... )
我们使用了与我们的活动相同的名称来命名应用程序。这不是必需的,但这是一个常见的做法。请注意,评审 应用程序包括对输出参数的规范。现在我们已经定义了我们的应用程序,我们需要修改我们的活动来使用它们。
>>> pd.activities['author'].addApplication('author') >>> pd.activities['review'].addApplication('review', ['publish']) >>> pd.activities['publish'].addApplication('publish') >>> pd.activities['reject'].addApplication('reject')
活动可以使用许多应用程序,所以我们调用 addApplication。在“评审”应用程序的应用定义中,我们提供了与应用程序定义的输出参数相对应的工作流相关数据变量的名称。当在活动中使用应用程序时,必须为已标识应用程序签名中的每个参数提供一个工作流相关数据变量名称。当在活动中使用应用程序时,为每个输入参数传递工作流相关数据,并由每个输出参数设置。在这个示例中,输出参数将被用来向工作流相关数据添加一个 发布 属性。
参与者
我们已经声明了一些应用程序,并将它们连接到活动中,但我们还没有指定任何应用程序代码。在我们可以指定应用程序代码之前,我们需要考虑谁将执行应用程序。工作流应用程序通常由人员或其他外部参与者执行。与应用程序一样,流程定义允许在工作流中声明和标识参与者。我们声明参与者的方式与声明应用程序的方式相似,只是不需要参数。
>>> pd.defineParticipants( ... author = process.Participant(), ... reviewer = process.Participant(), ... )
在这种情况下,我们恰好重用了活动名称中的一个,而不是两个参与者。在定义了这些参与者之后,我们可以将它们与活动关联起来。
>>> pd.activities['author'].definePerformer('author') >>> pd.activities['review'].definePerformer('reviewer')
应用程序集成
要使用流程定义来控制应用程序逻辑,我们需要将其与一个“集成”对象关联。
当流程需要获取参与者时,它会调用其集成属性的 createParticipant,传递流程 ID 和执行者 ID。如果活动没有执行者,则使用空执行者 ID 调用上述过程。
类似地,当流程需要工作项时,它会调用其集成属性的 createWorkItem,传递流程 ID 和应用程序 ID。
工作项提供了一个用于启动工作和传递输入参数的 start 方法。工作项的责任是在某个后续时间,调用活动上的 workItemFinished 方法,以通知活动工作项已完成。输出参数传递给 workItemFinished 方法。
创建集成对象的一个简单方法是使用 zope.wfmc.attributeintegration.AttributeIntegration。
>>> from zope.wfmc.attributeintegration import AttributeIntegration >>> integration = AttributeIntegration() >>> pd.integration = integration
我们首先定义一个简单的参与者类
>>> import zope.interface >>> from zope.wfmc import interfaces>>> class Participant(object): ... zope.component.adapts(interfaces.IActivity) ... zope.interface.implements(interfaces.IParticipant) ... ... def __init__(self, activity): ... self.activity = activity
然后我们为每个参与者设置集成上的属性
>>> integration.authorParticipant = Participant >>> integration.reviewerParticipant = Participant
我们还为没有表演者的活动定义了参与者的属性
>>> integration.Participant = Participant
现在我们将定义我们的工作项。首先我们将定义一些类
>>> work_list = []>>> class ApplicationBase: ... zope.component.adapts(interfaces.IParticipant) ... zope.interface.implements(interfaces.IWorkItem) ... ... def __init__(self, participant): ... self.participant = participant ... work_list.append(self) ... ... def start(self): ... pass ... ... def finish(self): ... self.participant.activity.workItemFinished(self)>>> class Review(ApplicationBase): ... def finish(self, publish): ... self.participant.activity.workItemFinished(self, publish)>>> class Publish(ApplicationBase): ... def start(self): ... print "Published" ... self.finish()>>> class Reject(ApplicationBase): ... def start(self): ... print "Rejected" ... self.finish()
然后我们将它们与集成对象连接起来
>>> integration.authorWorkItem = ApplicationBase >>> integration.reviewWorkItem = Review >>> integration.publishWorkItem = Publish >>> integration.rejectWorkItem = Reject
使用工作流过程
要使用流程定义,实例化它并调用其 start 方法以启动执行
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author'))
我们过渡到作者活动并等待工作完成。为了前进,我们需要获取作者工作项,以便完成它。我们的工作项将自己添加到工作列表中,因此我们可以从列表中获取该项。
>>> item = work_list.pop()
现在我们可以通过调用其 finish 方法来完成工作项
>>> item.finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review'))
我们看到我们已经过渡到了审查活动。请注意,finish 方法不是工作流 API 的一部分。它是由我们的示例类定义的。其他应用程序可以使用不同的机制。
现在,我们将通过调用审查工作项的 finish 来完成审查过程。我们将传递 False,表示内容不应该发布
>>> work_list.pop().finish(False) WorkItemFinished('review') ActivityFinished(Activity('sample.review')) Transition(Activity('sample.review'), Activity('sample.reject')) ActivityStarted(Activity('sample.reject')) Rejected WorkItemFinished('reject') ActivityFinished(Activity('sample.reject')) ProcessFinished(Process('sample'))
输出转换排序
通常,输出转换按转换定义的顺序排序,并使用给定活动的所有转换。
如果转换定义的顺序不方便,则工作流可能无法按预期工作。例如,让我们通过交换一些转换的定义顺序来修改上述过程。我们将通过将其传递给定义构造函数重用之前的示例中的集成对象
>>> pd = process.ProcessDefinition('sample', integration) >>> zope.component.provideUtility(pd, name=pd.id) Unregistered event: UtilityRegistration(<BaseGlobalComponents base>, IProcessDefinition, 'sample', ProcessDefinition('sample'), None, u'')>>> pd.defineActivities( ... author = process.ActivityDefinition(), ... review = process.ActivityDefinition(), ... publish = process.ActivityDefinition(), ... reject = process.ActivityDefinition(), ... ) >>> pd.defineTransitions( ... process.TransitionDefinition('author', 'review'), ... process.TransitionDefinition('review', 'reject'), ... process.TransitionDefinition( ... 'review', 'publish', condition=lambda data: data.publish), ... )>>> pd.defineApplications( ... author = process.Application(), ... review = process.Application( ... process.OutputParameter('publish')), ... publish = process.Application(), ... reject = process.Application(), ... )>>> pd.activities['author'].addApplication('author') >>> pd.activities['review'].addApplication('review', ['publish']) >>> pd.activities['publish'].addApplication('publish') >>> pd.activities['reject'].addApplication('reject')>>> pd.defineParticipants( ... author = process.Participant(), ... reviewer = process.Participant(), ... )>>> pd.activities['author'].definePerformer('author') >>> pd.activities['review'].definePerformer('reviewer')
并运行我们的过程
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author'))>>> work_list.pop().finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review'))
这一次,我们将说我们应该发布
>>> work_list.pop().finish(True) WorkItemFinished('review') ActivityFinished(Activity('sample.review')) Transition(Activity('sample.review'), Activity('sample.reject')) ActivityStarted(Activity('sample.reject')) Rejected WorkItemFinished('reject') ActivityFinished(Activity('sample.reject')) ProcessFinished(Process('sample'))
但我们还是到了拒绝活动。为什么?因为转换是按顺序测试的。因为拒绝活动的转换首先被测试并且没有条件,所以我们遵循它而不检查发布活动转换的条件。我们可以通过在审查者活动上直接指定输出转换来修复这个问题。为此,我们还需要在转换中指定 ids。让我们重新定义过程
>>> pd = process.ProcessDefinition('sample', integration) >>> zope.component.provideUtility(pd, name=pd.id) Unregistered event: UtilityRegistration(<BaseGlobalComponents base>, IProcessDefinition, 'sample', ProcessDefinition('sample'), None, u'')>>> pd.defineActivities( ... author = process.ActivityDefinition(), ... review = process.ActivityDefinition(), ... publish = process.ActivityDefinition(), ... reject = process.ActivityDefinition(), ... ) >>> pd.defineTransitions( ... process.TransitionDefinition('author', 'review'), ... process.TransitionDefinition('review', 'reject', id='reject'), ... process.TransitionDefinition( ... 'review', 'publish', id='publish', ... condition=lambda data: data.publish), ... )>>> pd.defineApplications( ... author = process.Application(), ... review = process.Application( ... process.OutputParameter('publish')), ... publish = process.Application(), ... reject = process.Application(), ... )>>> pd.activities['author'].addApplication('author') >>> pd.activities['review'].addApplication('review', ['publish']) >>> pd.activities['publish'].addApplication('publish') >>> pd.activities['reject'].addApplication('reject')>>> pd.defineParticipants( ... author = process.Participant(), ... reviewer = process.Participant(), ... )>>> pd.activities['author'].definePerformer('author') >>> pd.activities['review'].definePerformer('reviewer')>>> pd.activities['review'].addOutgoing('publish') >>> pd.activities['review'].addOutgoing('reject')
现在,当我们运行过程时,我们将按照预期到达发布活动
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author'))>>> work_list.pop().finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review'))>>> work_list.pop().finish(True) WorkItemFinished('review') ActivityFinished(Activity('sample.review')) Transition(Activity('sample.review'), Activity('sample.publish')) ActivityStarted(Activity('sample.publish')) Published WorkItemFinished('publish') ActivityFinished(Activity('sample.publish')) ProcessFinished(Process('sample'))
让我们看看我们也应该过渡到的拒绝活动
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author'))>>> work_list.pop().finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review'))>>> work_list.pop().finish(False) WorkItemFinished('review') ActivityFinished(Activity('sample.review')) Transition(Activity('sample.review'), Activity('sample.reject')) ActivityStarted(Activity('sample.reject')) Rejected WorkItemFinished('reject') ActivityFinished(Activity('sample.reject')) ProcessFinished(Process('sample'))
复杂流程
让我们看看一个更复杂的例子。在这个例子中,我们将扩展过程以处理多个审查者。我们还将使工作列表处理更复杂。我们还将介绍一些新概念
分割和连接
流程参数
考虑以下显示的发布过程
Author: Tech Tech Editorial Reviewer 1: Reviewer 2: Reviewer: =========== =========== =========== ============== --------- ----------------------------------------------------| Start | / --------- | V ----------- | Prepare |<------------------------------\ ----------- \ | ------------ \ | | Tech |--------------- \ \ |------->| Review 1 | V | | ------------ ---------- ------------- \ | Tech | | Editorial | ---------- ------------------->| Review |--->| Review |-->| Reject | | 2 | ------------- ---------- ---------- | | ----------- / \ | Prepare | / \--------\ | Final |<----------------------------/ | ----------- | ^ | ---------- V | \------------------------------->| Review | ----------- \ | Final |----->| Publish | ------------------------------------| | ----------- ----------
这里我们将过程图排列成列,每个参与者的活动。我们有四个参与者,作者,两位技术审查员和一位编辑审查员。作者准备草稿。作者将草稿发送给 两位 技术审查员进行审查。当技术审查完成时,编辑审查进行初步编辑审查。基于技术审查,编辑可以选择
拒绝文档
按原样发布文档
根据技术审查员的评论请求技术更改,或
请求编辑更改。
如果需要技术更改,工作流程将回退到“准备”活动。如果需要编辑更改,则工作流程将流向“准备最终版”活动。当作者完成编辑更改后,工作流程将流向“审查最终版”。编辑可能要求进行额外的更改,在这种情况下,工作流程将回退到“准备最终版”,否则,工作流程将流向“发布”。
此示例说明了不同类型的“连接”和“拆分”。术语“连接”指的是处理活动输入过渡的方式。有两种类型的连接:“与”和“异或”。使用“与”连接时,活动等待每个输入过渡。在此示例中,“编辑审查”活动的输入形成一个“与”连接。编辑审查等待每个技术审查完成。本例中其余的连接都是“异或”连接。活动可以在进入活动时启动。
术语“拆分”指的是处理活动输出过渡的方式。通常,从活动中使用恰好一个过渡。这称为“异或”拆分。使用“与”拆分时,使用所有布尔条件评估为True的过渡。在此示例中,“准备”活动有一个“与”拆分。工作流程同时流向两个技术审查活动。本例中其余的拆分都是“异或”拆分。
让我们创建我们的新工作流程过程。我们将重用我们现有的集成对象
>>> Publication = process.ProcessDefinition('Publication') >>> Publication.integration = integration >>> zope.component.provideUtility(Publication, name=Publication.id)>>> Publication.defineActivities( ... start = process.ActivityDefinition("Start"), ... prepare = process.ActivityDefinition("Prepare"), ... tech1 = process.ActivityDefinition("Technical Review 1"), ... tech2 = process.ActivityDefinition("Technical Review 2"), ... review = process.ActivityDefinition("Editorial Review"), ... final = process.ActivityDefinition("Final Preparation"), ... rfinal = process.ActivityDefinition("Review Final"), ... publish = process.ActivityDefinition("Publish"), ... reject = process.ActivityDefinition("Reject"), ... )
在此,我们向活动定义传递了字符串以提供名称。名称必须是unicode或ASCII字符串。
我们定义我们的过渡
>>> Publication.defineTransitions( ... process.TransitionDefinition('start', 'prepare'), ... process.TransitionDefinition('prepare', 'tech1'), ... process.TransitionDefinition('prepare', 'tech2'), ... process.TransitionDefinition('tech1', 'review'), ... process.TransitionDefinition('tech2', 'review'), ... ... process.TransitionDefinition( ... 'review', 'reject', ... condition=lambda data: not data.publish ... ), ... process.TransitionDefinition( ... 'review', 'prepare', ... condition=lambda data: data.tech_changes ... ), ... process.TransitionDefinition( ... 'review', 'final', ... condition=lambda data: data.ed_changes ... ), ... process.TransitionDefinition('review', 'publish'), ... ... process.TransitionDefinition('final', 'rfinal'), ... process.TransitionDefinition( ... 'rfinal', 'final', ... condition=lambda data: data.ed_changes ... ), ... process.TransitionDefinition('rfinal', 'publish'), ... )
我们指定我们的“与”拆分和连接
>>> Publication.activities['prepare'].andSplit(True) >>> Publication.activities['review'].andJoin(True)
我们定义我们的参与者和应用程序
>>> Publication.defineParticipants( ... author = process.Participant("Author"), ... tech1 = process.Participant("Technical Reviewer 1"), ... tech2 = process.Participant("Technical Reviewer 2"), ... reviewer = process.Participant("Editorial Reviewer"), ... )>>> Publication.defineApplications( ... prepare = process.Application(), ... tech_review = process.Application( ... process.OutputParameter('publish'), ... process.OutputParameter('tech_changes'), ... ), ... ed_review = process.Application( ... process.InputParameter('publish1'), ... process.InputParameter('tech_changes1'), ... process.InputParameter('publish2'), ... process.InputParameter('tech_changes2'), ... process.OutputParameter('publish'), ... process.OutputParameter('tech_changes'), ... process.OutputParameter('ed_changes'), ... ), ... publish = process.Application(), ... reject = process.Application(), ... final = process.Application(), ... rfinal = process.Application( ... process.OutputParameter('ed_changes'), ... ), ... )>>> Publication.activities['prepare'].definePerformer('author') >>> Publication.activities['prepare'].addApplication('prepare')>>> Publication.activities['tech1'].definePerformer('tech1') >>> Publication.activities['tech1'].addApplication( ... 'tech_review', ['publish1', 'tech_changes1'])>>> Publication.activities['tech2'].definePerformer('tech2') >>> Publication.activities['tech2'].addApplication( ... 'tech_review', ['publish2', 'tech_changes2'])>>> Publication.activities['review'].definePerformer('reviewer') >>> Publication.activities['review'].addApplication( ... 'ed_review', ... ['publish1', 'tech_changes1', 'publish2', 'tech_changes2', ... 'publish', 'tech_changes', 'ed_changes'], ... )>>> Publication.activities['final'].definePerformer('author') >>> Publication.activities['final'].addApplication('final')>>> Publication.activities['rfinal'].definePerformer('reviewer') >>> Publication.activities['rfinal'].addApplication( ... 'rfinal', ['ed_changes'], ... )>>> Publication.activities['publish'].addApplication('publish') >>> Publication.activities['reject'].addApplication('reject')
我们希望在启动过程时能够指定作者。我们还希望知道过程的最终处理情况。为了实现这一点,我们将为我们的过程定义参数
>>> Publication.defineParameters( ... process.InputParameter('author'), ... process.OutputParameter('publish'), ... )
现在我们已经定义了过程,我们需要提供参与者和应用程序组件。让我们从我们的参与者开始。我们不会共享单个工作列表,而是给每个用户他们自己的工作列表。我们还将创建预先存在的参与者并返回它们。最后,我们将创建多个作者并使用所选的一个
>>> class User: ... def __init__(self): ... self.work_list = []>>> authors = {'bob': User(), 'ted': User(), 'sally': User()}>>> reviewer = User() >>> tech1 = User() >>> tech2 = User()>>> class Author(Participant): ... def __init__(self, activity): ... Participant.__init__(self, activity) ... author_name = activity.process.workflowRelevantData.author ... print "Author `%s` selected" % author_name ... self.user = authors[author_name]
在此示例中,我们需要为每个参与者定义一个单独的属性
>>> integration.authorParticipant = Author
当过程创建时,作者名称将传入并分配给与工作流程相关的数据。我们的作者类使用此信息来选择命名用户。
>>> class Reviewer(Participant): ... user = reviewer >>> integration.reviewerParticipant = Reviewer>>> class Tech1(Participant): ... user = tech1 >>> integration.tech1Participant = Tech1>>> class Tech2(Participant): ... user = tech2 >>> integration.tech2Participant = Tech2
我们将为没有表演者的活动使用我们的原始参与类
>>> integration.Participant = Participant
现在我们将创建我们的应用程序。让我们从我们的作者开始
>>> class ApplicationBase(object): ... zope.component.adapts(interfaces.IParticipant) ... zope.interface.implements(interfaces.IWorkItem) ... ... def __init__(self, participant): ... self.participant = participant ... self.activity = participant.activity ... participant.user.work_list.append(self) ... ... def start(self): ... pass ... ... def finish(self): ... self.participant.activity.workItemFinished(self)>>> class Prepare(ApplicationBase): ... ... def summary(self): ... process = self.activity.process ... doc = getattr(process.applicationRelevantData, 'doc', '') ... if doc: ... print 'Previous draft:' ... print doc ... print 'Changes we need to make:' ... for change in process.workflowRelevantData.tech_changes: ... print change ... else: ... print 'Please write the initial draft' ... ... def finish(self, doc): ... self.activity.process.applicationRelevantData.doc = doc ... super(Prepare, self).finish()>>> integration.prepareWorkItem = Prepare
由于我们使用了准备应用程序进行修订以及初始准备,我们提供了一个摘要方法来显示我们必须做什么。
在此,我们通过将作者创建的文档作为参数传递给完成方法来获取文档。在更实际的实现中,作者任务会在任务开始时创建文档并提供用户界面供用户编辑它。我们将文档存储为应用程序相关数据,因为我们希望审阅者能够访问它,但我们不需要它直接用于工作流程控制。
>>> class TechReview(ApplicationBase): ... ... def getDoc(self): ... return self.activity.process.applicationRelevantData.doc ... ... def finish(self, decision, changes): ... self.activity.workItemFinished(self, decision, changes)>>> integration.tech_reviewWorkItem = TechReview
在此,我们提供了一个方法来访问原始文档。
>>> class Review(TechReview): ... ... def start(self, publish1, changes1, publish2, changes2): ... if not (publish1 and publish2): ... # Reject if either tech reviewer rejects ... self.activity.workItemFinished( ... self, False, changes1 + changes2, ()) ... ... if changes1 or changes2: ... # we won't do anything if there are tech changes ... self.activity.workItemFinished( ... self, True, changes1 + changes2, ()) ... ... def finish(self, ed_changes): ... self.activity.workItemFinished(self, True, (), ed_changes)>>> integration.ed_reviewWorkItem = Review
在此实现中,我们决定如果技术编辑推荐拒绝,则直接拒绝,如果存在任何技术更改,则将工作退回到准备阶段。我们还从TechReview中继承了以获取getDoc方法。
我们将重用前一个示例中的publish和reject应用程序。
>>> class Final(ApplicationBase): ... ... def summary(self): ... process = self.activity.process ... doc = getattr(process.applicationRelevantData, 'doc', '') ... print 'Previous draft:' ... print self.activity.process.applicationRelevantData.doc ... print 'Changes we need to make:' ... for change in process.workflowRelevantData.ed_changes: ... print change ... ... def finish(self, doc): ... self.activity.process.applicationRelevantData.doc = doc ... super(Final, self).finish()>>> integration.finalWorkItem = Final
在我们的这个应用程序中,我们只需更新文档以反映更改。
>>> class ReviewFinal(TechReview): ... ... def finish(self, ed_changes): ... self.activity.workItemFinished(self, ed_changes)>>> integration.rfinalWorkItem = ReviewFinal
我们的过程现在返回数据。当我们创建过程时,我们需要提供一个它可以调用的对象
>>> class PublicationContext: ... zope.interface.implements(interfaces.IProcessContext) ... ... def processFinished(self, process, decision): ... self.decision = decision
现在,让我们尝试我们的过程
>>> context = PublicationContext() >>> proc = Publication(context) >>> proc.start('bob') ProcessStarted(Process('Publication')) Transition(None, Activity('Publication.start')) ActivityStarted(Activity('Publication.start')) ActivityFinished(Activity('Publication.start')) Author `bob` selected Transition(Activity('Publication.start'), Activity('Publication.prepare')) ActivityStarted(Activity('Publication.prepare'))
我们应该已经在bob的工作列表中添加了一个条目。让我们获取它并完成它,提交一个文档
>>> item = authors['bob'].work_list.pop() >>> item.finish("I give my pledge, as an American\n" ... "to save, and faithfully to defend from waste\n" ... "the natural resources of my Country.") WorkItemFinished('prepare') ActivityFinished(Activity('Publication.prepare')) Transition(Activity('Publication.prepare'), Activity('Publication.tech1')) ActivityStarted(Activity('Publication.tech1')) Transition(Activity('Publication.prepare'), Activity('Publication.tech2')) ActivityStarted(Activity('Publication.tech2'))
注意我们已过渡到两个活动,tech1 和 tech2。这是因为准备活动有一个“and”分支。现在我们将进行技术审查。看看tech1有什么
>>> item = tech1.work_list.pop() >>> print item.getDoc() I give my pledge, as an American to save, and faithfully to defend from waste the natural resources of my Country.
让我们告诉作者将“American”改为“地球人”
>>> item.finish(True, ['Change "American" to "Earthling"']) WorkItemFinished('tech_review') ActivityFinished(Activity('Publication.tech1')) Transition(Activity('Publication.tech1'), Activity('Publication.review'))
在这里,我们过渡到了编辑审查活动,但并未启动它。这是因为编辑审查活动有一个“and”连接,意味着它将在两个过渡都发生之后才开始。
现在我们将进行另一个技术审查
>>> item = tech2.work_list.pop() >>> item.finish(True, ['Change "Country" to "planet"']) WorkItemFinished('tech_review') ActivityFinished(Activity('Publication.tech2')) Transition(Activity('Publication.tech2'), Activity('Publication.review')) ActivityStarted(Activity('Publication.review')) WorkItemFinished('ed_review') ActivityFinished(Activity('Publication.review')) Author `bob` selected Transition(Activity('Publication.review'), Activity('Publication.prepare')) ActivityStarted(Activity('Publication.prepare'))
现在当我们过渡到编辑审查活动时,我们启动了它,因为每个输入过渡都已经发生。我们的编辑审查应用程序自动将工作退回到准备阶段,因为有一些技术评论。当然,作者仍然是bob。让我们处理这些评论
>>> item = authors['bob'].work_list.pop() >>> item.summary() Previous draft: I give my pledge, as an American to save, and faithfully to defend from waste the natural resources of my Country. Changes we need to make: Change "American" to "Earthling" Change "Country" to "planet">>> item.finish("I give my pledge, as an Earthling\n" ... "to save, and faithfully to defend from waste\n" ... "the natural resources of my planet.") WorkItemFinished('prepare') ActivityFinished(Activity('Publication.prepare')) Transition(Activity('Publication.prepare'), Activity('Publication.tech1')) ActivityStarted(Activity('Publication.tech1')) Transition(Activity('Publication.prepare'), Activity('Publication.tech2')) ActivityStarted(Activity('Publication.tech2'))
和之前一样,在完成初步编辑后,我们再次启动技术审查活动。我们将再次审查它。这次,我们没有评论,因为作者应用了我们的请求更改
>>> item = tech1.work_list.pop() >>> item.finish(True, []) WorkItemFinished('tech_review') ActivityFinished(Activity('Publication.tech1')) Transition(Activity('Publication.tech1'), Activity('Publication.review'))>>> item = tech2.work_list.pop() >>> item.finish(True, []) WorkItemFinished('tech_review') ActivityFinished(Activity('Publication.tech2')) Transition(Activity('Publication.tech2'), Activity('Publication.review')) ActivityStarted(Activity('Publication.review'))
这次,我们留在了技术审查活动中,因为没有技术变更。我们准备进行编辑审查。我们将要求编辑更改
>>> item = reviewer.work_list.pop() >>> print item.getDoc() I give my pledge, as an Earthling to save, and faithfully to defend from waste the natural resources of my planet.>>> item.finish(['change "an" to "a"']) WorkItemFinished('ed_review') ActivityFinished(Activity('Publication.review')) Author `bob` selected Transition(Activity('Publication.review'), Activity('Publication.final')) ActivityStarted(Activity('Publication.final'))
因为我们请求编辑更改,所以我们过渡到了最终编辑活动,以便作者(仍然是bob)可以进行更改
>>> item = authors['bob'].work_list.pop() >>> item.summary() Previous draft: I give my pledge, as an Earthling to save, and faithfully to defend from waste the natural resources of my planet. Changes we need to make: change "an" to "a">>> item.finish("I give my pledge, as a Earthling\n" ... "to save, and faithfully to defend from waste\n" ... "the natural resources of my planet.") WorkItemFinished('final') ActivityFinished(Activity('Publication.final')) Transition(Activity('Publication.final'), Activity('Publication.rfinal')) ActivityStarted(Activity('Publication.rfinal'))
我们过渡到审查最终编辑的活动。我们审查文档并批准其出版
>>> item = reviewer.work_list.pop() >>> print item.getDoc() I give my pledge, as a Earthling to save, and faithfully to defend from waste the natural resources of my planet.>>> item.finish([]) WorkItemFinished('rfinal') ActivityFinished(Activity('Publication.rfinal')) Transition(Activity('Publication.rfinal'), Activity('Publication.publish')) ActivityStarted(Activity('Publication.publish')) Published WorkItemFinished('publish') ActivityFinished(Activity('Publication.publish')) ProcessFinished(Process('Publication'))
此时,其余过程自动完成。此外,决策记录在过程上下文对象中
>>> context.decision True
即将推出
XPDL支持
超时/异常
“否则”条件
另请参阅
http://www.wfmc.org http://www.wfmc.org/standards/standards.htm
XPDL导入
我们可以从XML流程定义语言(XPDL)格式的文件中导入流程定义。一个XPDL文件包含多个流程定义,这些定义组织在一个包中。当我们加载文件时,我们得到一个包含一些流程定义的包。
让我们看看一个例子。文件publication.xpdl包含在“README.txt”文件中开发的发布示例的定义。我们可以使用xpdl模块读取它
>>> from zope.wfmc import xpdl >>> import os >>> package = xpdl.read(open(os.path.join(this_directory, ... 'publication.xpdl')))
这个包包含一个单独的定义
>>> package {u'Publication': ProcessDefinition(u'Publication')}>>> pd = package[u'Publication'] >>> from zope.wfmc.attributeintegration import AttributeIntegration >>> integration = AttributeIntegration() >>> pd.integration = integration
现在,在读取了流程定义之后,我们可以像之前一样使用它(在“README.txt”中)。就像之前一样,我们将创建一个事件订阅者,以便我们可以看到发生了什么
>>> def log_workflow(event): ... print event>>> import zope.event >>> zope.event.subscribers.append(log_workflow)
并将流程定义注册为实用工具
>>> import zope.component >>> zope.component.provideUtility(pd, name=pd.id)
并定义和注册参与者和应用程序适配器
>>> import zope.interface >>> from zope.wfmc import interfaces>>> class Participant(object): ... zope.component.adapts(interfaces.IActivity) ... zope.interface.implements(interfaces.IParticipant) ... ... def __init__(self, activity): ... self.activity = activity>>> class User: ... def __init__(self): ... self.work_list = []>>> authors = {'bob': User(), 'ted': User(), 'sally': User()}>>> reviewer = User() >>> tech1 = User() >>> tech2 = User()>>> class Author(Participant): ... def __init__(self, activity): ... Participant.__init__(self, activity) ... author_name = activity.process.workflowRelevantData.author ... print "Author `%s` selected" % author_name ... self.user = authors[author_name]>>> integration.authorParticipant = Author>>> class Reviewer(Participant): ... user = reviewer >>> integration.reviewerParticipant = Reviewer>>> class Tech1(Participant): ... user = tech1 >>> integration.tech1Participant = Tech1>>> class Tech2(Participant): ... user = tech2 >>> integration.tech2Participant = Tech2>>> integration.SystemParticipant = Participant>>> class ApplicationBase(object): ... zope.component.adapts(interfaces.IParticipant) ... zope.interface.implements(interfaces.IWorkItem) ... ... def __init__(self, participant): ... self.participant = participant ... self.activity = participant.activity ... participant.user.work_list.append(self) ... ... def start(self): ... pass ... ... def finish(self): ... self.participant.activity.workItemFinished(self)>>> class Prepare(ApplicationBase): ... ... def summary(self): ... process = self.activity.process ... doc = getattr(process.applicationRelevantData, 'doc', '') ... if doc: ... print 'Previous draft:' ... print doc ... print 'Changes we need to make:' ... for change in process.workflowRelevantData.tech_changes: ... print change ... else: ... print 'Please write the initial draft' ... ... def finish(self, doc): ... self.activity.process.applicationRelevantData.doc = doc ... super(Prepare, self).finish()>>> integration.prepareWorkItem = Prepare>>> class TechReview(ApplicationBase): ... ... def getDoc(self): ... return self.activity.process.applicationRelevantData.doc ... ... def finish(self, decision, changes): ... self.activity.workItemFinished(self, decision, changes)>>> integration.tech_reviewWorkItem = TechReview>>> class Review(TechReview): ... ... def start(self, publish1, changes1, publish2, changes2): ... if not (publish1 and publish2): ... # Reject if either tech reviewer rejects ... self.activity.workItemFinished( ... self, False, changes1 + changes2, ()) ... ... if changes1 or changes2: ... # we won't do anyting if there are tech changes ... self.activity.workItemFinished( ... self, True, changes1 + changes2, ()) ... ... def finish(self, ed_changes): ... self.activity.workItemFinished(self, True, (), ed_changes)>>> integration.ed_reviewWorkItem = Review>>> class Final(ApplicationBase): ... ... def summary(self): ... process = self.activity.process ... doc = getattr(process.applicationRelevantData, 'doc', '') ... print 'Previous draft:' ... print self.activity.process.applicationRelevantData.doc ... print 'Changes we need to make:' ... for change in process.workflowRelevantData.ed_changes: ... print change ... ... def finish(self, doc): ... self.activity.process.applicationRelevantData.doc = doc ... super(Final, self).finish()>>> integration.finalWorkItem = Final>>> class ReviewFinal(TechReview): ... ... def finish(self, ed_changes): ... self.activity.workItemFinished(self, ed_changes)>>> integration.rfinalWorkItem = ReviewFinal>>> class Publish: ... zope.component.adapts(interfaces.IParticipant) ... zope.interface.implements(interfaces.IWorkItem) ... ... def __init__(self, participant): ... self.participant = participant ... ... def start(self): ... print "Published" ... self.finish() ... ... def finish(self): ... self.participant.activity.workItemFinished(self)>>> integration.publishWorkItem = Publish>>> class Reject(Publish): ... def start(self): ... print "Rejected" ... self.finish()>>> integration.rejectWorkItem = Reject
并定义一个流程上下文,以便我们可以传递参数
>>> class PublicationContext: ... zope.interface.implements(interfaces.IProcessContext) ... ... def processFinished(self, process, decision): ... self.decision = decision
现在,让我们尝试我们的流程。我们将遵循与“README.txt”中相同的步骤,得到相同的结果
>>> context = PublicationContext() >>> proc = pd(context) >>> proc.start('bob') ProcessStarted(Process(u'Publication')) Transition(None, Activity(u'Publication.start')) ActivityStarted(Activity(u'Publication.start')) ActivityFinished(Activity(u'Publication.start')) Author `bob` selected Transition(Activity(u'Publication.start'), Activity(u'Publication.prepare')) ActivityStarted(Activity(u'Publication.prepare'))>>> item = authors['bob'].work_list.pop() >>> item.finish("I give my pledge, as an American\n" ... "to save, and faithfully to defend from waste\n" ... "the natural resources of my Country.") WorkItemFinished(u'prepare') ActivityFinished(Activity(u'Publication.prepare')) Transition(Activity(u'Publication.prepare'), Activity(u'Publication.tech1')) ActivityStarted(Activity(u'Publication.tech1')) Transition(Activity(u'Publication.prepare'), Activity(u'Publication.tech2')) ActivityStarted(Activity(u'Publication.tech2'))>>> item = tech1.work_list.pop() >>> print item.getDoc() I give my pledge, as an American to save, and faithfully to defend from waste the natural resources of my Country.>>> item.finish(True, ['Change "American" to "human"']) WorkItemFinished(u'tech_review') ActivityFinished(Activity(u'Publication.tech1')) Transition(Activity(u'Publication.tech1'), Activity(u'Publication.review'))>>> item = tech2.work_list.pop() >>> item.finish(True, ['Change "Country" to "planet"']) WorkItemFinished(u'tech_review') ActivityFinished(Activity(u'Publication.tech2')) Transition(Activity(u'Publication.tech2'), Activity(u'Publication.review')) ActivityStarted(Activity(u'Publication.review')) WorkItemFinished(u'ed_review') ActivityFinished(Activity(u'Publication.review')) Author `bob` selected Transition(Activity(u'Publication.review'), Activity(u'Publication.prepare')) ActivityStarted(Activity(u'Publication.prepare'))>>> item = authors['bob'].work_list.pop() >>> item.summary() Previous draft: I give my pledge, as an American to save, and faithfully to defend from waste the natural resources of my Country. Changes we need to make: Change "American" to "human" Change "Country" to "planet">>> item.finish("I give my pledge, as an human\n" ... "to save, and faithfully to defend from waste\n" ... "the natural resources of my planet.") WorkItemFinished(u'prepare') ActivityFinished(Activity(u'Publication.prepare')) Transition(Activity(u'Publication.prepare'), Activity(u'Publication.tech1')) ActivityStarted(Activity(u'Publication.tech1')) Transition(Activity(u'Publication.prepare'), Activity(u'Publication.tech2')) ActivityStarted(Activity(u'Publication.tech2'))>>> item = tech1.work_list.pop() >>> item.finish(True, []) WorkItemFinished(u'tech_review') ActivityFinished(Activity(u'Publication.tech1')) Transition(Activity(u'Publication.tech1'), Activity(u'Publication.review'))>>> item = tech2.work_list.pop() >>> item.finish(True, []) WorkItemFinished(u'tech_review') ActivityFinished(Activity(u'Publication.tech2')) Transition(Activity(u'Publication.tech2'), Activity(u'Publication.review')) ActivityStarted(Activity(u'Publication.review'))>>> item = reviewer.work_list.pop() >>> print item.getDoc() I give my pledge, as an human to save, and faithfully to defend from waste the natural resources of my planet.>>> item.finish(['change "an" to "a"']) WorkItemFinished(u'ed_review') ActivityFinished(Activity(u'Publication.review')) Author `bob` selected Transition(Activity(u'Publication.review'), Activity(u'Publication.final')) ActivityStarted(Activity(u'Publication.final'))>>> item = authors['bob'].work_list.pop() >>> item.summary() Previous draft: I give my pledge, as an human to save, and faithfully to defend from waste the natural resources of my planet. Changes we need to make: change "an" to "a">>> item.finish("I give my pledge, as a human\n" ... "to save, and faithfully to defend from waste\n" ... "the natural resources of my planet.") WorkItemFinished(u'final') ActivityFinished(Activity(u'Publication.final')) Transition(Activity(u'Publication.final'), Activity(u'Publication.rfinal')) ActivityStarted(Activity(u'Publication.rfinal'))>>> item = reviewer.work_list.pop() >>> print item.getDoc() I give my pledge, as a human to save, and faithfully to defend from waste the natural resources of my planet.>>> item.finish([]) WorkItemFinished(u'rfinal') ActivityFinished(Activity(u'Publication.rfinal')) Transition(Activity(u'Publication.rfinal'), Activity(u'Publication.publish')) ActivityStarted(Activity(u'Publication.publish')) Published WorkItemFinished(u'publish') ActivityFinished(Activity(u'Publication.publish')) ProcessFinished(Process(u'Publication'))>>> context.decision True
描述
大多数流程元素都可以有名称和描述。
>>> pd.__name__ u'Publication'>>> pd.description u'This is the sample process'>>> pd.applications['prepare'].__name__ u'Prepare'>>> pd.applications['prepare'].description u'Prepare the initial draft'>>> pd.activities['tech1'].__name__ u'Technical Review 1'>>> pd.activities['tech1'].description u'This is the first Technical Review.'>>> pd.participants['tech1'].__name__ u'Technical Reviewer 1'>>> pd.participants['tech1'].description u'He is a smart guy.'>>> sorted([item.__name__ for item in pd.transitions]) [u'Transition', u'Transition', u'Transition', u'Transition', u'Transition', u'Transition', u'Transition', u'Transition', u'Transition', u'Transition', u'Transition to Tech Review 1', u'Transition to Tech Review 2']>>> sorted([item.description for item in pd.transitions]) [None, None, None, None, None, None, None, None, None, None, None, u'Use this transition if there are editorial changes required.']
变更
3.5.0 (2009-07-24)
更新测试到最新包版本。
3.4.0 (2007-11-02)
独立于主Zope树发布。