Zope3宝典/审批工作流

来自Ubuntu中文
跳到导航跳到搜索

Chapter 20: Approval Workflow for Messages (第 20 章:消息的审批工作流)


原文出处:The Zope 3 Developers Book - An Introduction for Python Programmers

原文作者:StephanRichter, zope.org

授权许可:创作共用协议

翻译人员:

校对人员:Leal

适用版本:Zope 3

文章状态:校正阶段



Difficulty(难度)

Contributor (贡献者)

Skills(技能)

  • A good understanding of the Component Architecture and ZCML is required.
    能很好的理解组件的结构体系且会用ZCML
  • Familiarity with the messageboard package is necessary, so that you can easily follow the new extensions.
    必须熟悉 messageboard 包,因为你可以容易地学习新的扩展。
  • Some familiarity with workflows and various solutions is desired. Optional.
    要求对工作流和不同的解决方案有一定的熟悉。可选

Problem/Task(问题/任务)

Workflows are important in any company. Therefore it is not surprising that software-based workflows became a primary requirement for many computer systems, especially for content management systems (CMS). This chapter will add publication workflow to the messageboard.
在任何公司里工作流都是重要的。因此在许多计算机系统尤其是在内容管理系统(CMS)中基于软件的工作流成为基本要求也就不足为奇了。本章将为消息栏添加发布工作流。

Solution(解决方案)

While this chapter does not deal with every aspect of the zope.app.workflow package - for example it does not explain how to create Process Definitions - it demonstrates the most common use cases by integrating a workflow in an existing content object package. And the realization is amazingly simple. Behind the simple frontend, however, there is a maze of interfaces, their implementation and their presentation. The goal of the last section of the chapter is then to explain the framework from an architectural point of view.
尽管本章并不处理 zope.app.workflow 包的各个方面 - 例如它并不说明如何创建处理定义 - 它只展示最通用的用法示例,将工作流集成到已存在的内容对象包中。而且实现起来惊人的简单。然而,在简单的背后有着相当复杂的接口以及它们的实现和表现。本章最后一节的目标就是用体系结构的角度来解释这个架构。

20.1 Step I: Making your Message Workflow aware(20.1 步骤 I: 明确你的消息工作流 )

In order to make a content object workflow-aware, you simply have to tell the system that it can store workflow data. The simplest way is to add the following interface declaration to the Message content directive in the main configuration file:
为了明确内容对象的工作流,你需要简单地告诉系统它能够保存工作流数据。最简单的方式就是将下列接口声明添加到主配置文件的消息内容指令中:

1  <implements interface=
2      "zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable"/>

Appropriate adapters for storing workflow data are already defined for objects also implementing IAnnotable. Our message object does this already by implementing IAttributeAnnotable as you can see in the same content directive.
为保存工作流数据的适配器已经定义为对象同时它也实现了 IAnnotable。我们的消息对象通过实现 IAttributeAnnotable 也已经做到了这一点,就象你在相同的内容指令中所看到的那样。

Now the object can contain workflows and when you restart your browser, you should notice that Message instances now also have a “Workflow” tab, which is still totally empty.
现在对象可以包含工作流,并且当你重启你的浏览器时,你将发现 Message 实例现在已经有一个内容完全空白的“Workflow” 标签了。

20.2 Step II: Create a Workflow and its Supporting Components via the Browser(20.2 步骤 II: 通过浏览器创建工作流及其支持组件)

Next we have to create the workflow components themselves. For this first attempt we are going to create all the components by hand, since this process provides some enlightenment on how the entire workflow mechanism works.
接下来我们需要创建工作流组件本身。在第一次尝试里,我们打算手工创建全部的组件,因为这样可以对工作流运行机制有着更好的了解。

After you started Zope 3, go to the folder you want to add (or have already) your messageboard. Go to the Site Manager by clicking on “Manage Site”; if the Folder is not yet a site, click on “Make a site”. Now click on the “Tools” tab.
在你开始 Zope 3 之后,到你想在其中添加(或已经存在)消息栏的目录中。点击“Manage Site”到站点管理器;如果目录不是站点的话,点击“Make a site”。现在点击 “Tools” 标签。

If you you just created the site, you now have to create a “Local Utility Service”. This can be accomplished by clicking on the “Service Tool” link. Click on the “Add” button, select the “Utility Service” on the next screen, enter a name (like “utilities” for example) and confirm all this by pressing “Add”. Now you have a fully configured and activated local utility service. Go back to the tools overview screen now.
如果你刚刚创建站点,你需要创建一个“Local Utility Service”。这可以通过点击 “Service Tool” 的链接来完成。点击 “Add” 按钮,在接下来的屏幕中选择 “Utility Service”,键入名字(如“utilities”)并按 “Add” 确认。现在你已经有了一个被完全配置和激活的 local utility service 了。现在回到工具屏幕。

The next step is to define an actual workflow in terms of a process, which contains states and transitions. Therefore, click on the “Workflows” link. Next add a workflow by click on the “Add” button. For this workflow we want a “Stateful Process Definition” (which is likely to be you only choice) and name it “publish-message”. Clicking on the “Add” button will create the workflow and activate it.
接下来就是根据过程,包括状态和转换,定义实际的工作流了。点击 “Workflows” 链接,然后点击 “Add” 按钮添加一个工作流。对该工作流我们选择“Stateful Process Definition”(可能是你唯一的选择)并将其命名为“publish-message”。点击 “Add” 按钮,创建该工作流并激活它。

Since the stateful process defintion component supports a nice XML Import and Export filter, it is best to define the process in XML. For later reference, we are going to store the workflow XML in a file and make it part of our product. Therefore, open a new file called workflow.xml in the messageboard package and add the following process definitions:
因为 stateful process defintion 组件支持XML的导入导出过滤,所以最好在XML中定义过程。For later reference, 我们打算将工作流XML保存在文件里并将其作为我们产品的一部分。因此打开消息栏包中的一个名为 workflow.xml 的文件,并添加下列过程定义:

1  <?xml version="1.0"?>
2  <workflow type="StatefulWorkflow" title="Message Publication Review">
3    <schema name=""/>
4    <states>
5      <state name="INITIAL" title="initial" />
6      <state name="private" title="Private" />
7      <state name="pending" title="Pending Publication" />
8      <state name="published" title="Public" />
9    </states>
10    <transitions>
11  
12      <transition
13          sourceState="published"
14          destinationState="private"
15          name="published_private"
16          title="Unpublish Message"
17          permission="book.messageboard.PublishContent"
18          triggerMode="Manual" />
19  
20      <transition
21          sourceState="private"
22          destinationState="pending"
23          name="private_pending"
24          title="Submit Message"
25          permission="book.messageboard.Edit"
26          triggerMode="Manual" />
27  
28      <transition
29          sourceState="INITIAL"
30          destinationState="private"
31          name="initial_private"
32          title="Make Private"
33          triggerMode="Automatic" />
34  
35      <transition
36          sourceState="pending"
37          destinationState="published"
38          name="pending_published"
39          title="Publish Message"
40          permission="book.messageboard.PublishContent"
41          triggerMode="Manual" />
42  
43      <transition
44          sourceState="pending"
45          destinationState="private"
46          name="pending_private"
47          title="Retract Message"
48          permission="book.messageboard.Edit"
49          triggerMode="Manual" />
50  
51      <transition
52          sourceState="pending"
53          destinationState="private"
54          name="pending_private_reject"
55          title="Reject Message"
56          permission="book.messageboard.PublishContent"
57          triggerMode="Manual" />
58  
59    </transitions>
60  
61  </workflow>
  • Line 2: Define the workflow to be a stateful workflow, the only type that is currently implemented. The “title” is the string under which the workflow will be known.
    第 2 行: 定义工作流为状态工作流,这是当然唯一能实现的类型。 “title” 是用来标识工作流的。
  • Line 3: We do not have a particular data schema, so let’s skip that. These schemas are used to allow the developer to add additional workflow-relevant data (object-specific) to the workflow instances.
    第 3 行: 我们并没有特定的数据模式,所以我们忽略它。这些模式常用来允许开发者添加额外的与工作流相关的数据(特定对象)到工作流实例中。
  • Line 4-9: Define the states a Message can be into. The title, again, serves as a human readable presentation of the state.
    第 4-9 行: 定义消息可能进入的状态。标题再一次被用于增强状态介绍的可读性。
  • Line 10-59: This is a list of all possible transitions the object can undergo. I think the attributes of the transition directive are self explanatory and do not need any further explanation.
    第 10-59 行: 这是一个对象可能会经历的转换列表。我想转换指令属性是可以自说明的并不需要任何更多的解释。

Once you saved the XML file, click on the newly created workflow (in the “Workflows” tool overview) and then on the “Import/Export” tab. Copy the XML from the file and paste it into the textarea of the screen. Then press the “Import” button. The same screen will return with a message saying “Import was successfull!”. You will also see the XML (probably differently formatted) at the botton of the screen. If you now click on ManageStates, you should see the fours states you just added via the XML import. The same is true for the ManageTransitions view.
一旦你保存了 XML 文件之后,点击最新创建的工作流(在 “Workflows” 工具界面)并点击 “Import/Export” 标签。从文件中将 XML 拷至屏幕的文本框里。然后点击 “Import” 按钮。相同的屏幕将返回并提示 “Import was successfull!”. 你也将在屏幕底部看到 XML (也许格式有所不同)。如果你现在点击 ManageStates,你将看到刚才通过 XML 导入的四种状态。这同样也发生在 ManageTransitions 视图中。

You might have already noticed that the workflow requires a new permission named “book.messageboard.PublishContent” to be defined. Therefore go to the messageboard’s configuration file and add the permission:
你也许已经注意到工作流要求定义一个名为 “book.messageboard.PublishContent” 的新权限,因此在消息栏的配置文件中添加权限:

1  <permission
2      id="book.messageboard.PublishContent"
3      title="Publish Message"
4      description="Publish Message."/>

In the security.zcml configuration file grant the “Editor” the permission to publish content.
在 security.zcml 配置文件中允许 “Editor” 有权发布内容。

1  <grant
2      permission="book.messageboard.PublishContent"
3      role="book.messageboard.Editor"/>

Now restart Zope 3. That should be everything that’s to it! Now let’s see whether everything works.
现在重启 Zope 3。That should be everything that’s to it! 现在让我们看看它们是否运行正常。

20.3 Step III: Assigning the Workflow(20.3 步骤 III: 指定工作流)

Now that our message is workflow-aware and a workflow has been created, we have to assign the workflow to IMessage objects. This is done via the “Content Workflow Manager”, which maps workflows to content objects.
现在我们的消息是明确工作流的,并且工作流也已经被创建,我们必须将工作流指向 IMessage 对象。这可以通过专门将工作流映射到内容对象的“Content Workflow Manager”来实现。

Go to the site’s “tools” Site Management Folder. To do that go to the site’s overview and select the “Software” tab. You can now enter the “tools” folder. Once there, add a “Content Workflow Manager” named “ContentWorkflows”. When completed, you are automatically forwarded to the “Registration” view, since the manager is just another utility. Click on the “Register” button, register the utility as “ContentWorkflows” and press the “Add” button. You have now successfully registered and activated the utility.
进入站点的 “tools” 站点管理文件夹。进入站点一览并选择 “Software” 标签。你现在可以进入 “tools” 文件夹,在那添加一个名为 “ContentWorkflows” 的 “Content Workflow Manager” (内容工作流管理器)。当完成时,你会被自动导航到 “Registration” 视图,因为管理器也只是另一个程序。点击 “Register” 按钮,将其注册为 “ContentWorkflows” 并点击 “Add” 按钮。你现在已经将其成功注册并激活了。

The next step is to declare the workflow to interface mapping. To do so, go to the “Content/Process Registry” tab of the workflow manager. On this page you should now see a list of interfaces (many of them) and a list of process definition names, which only contains one entry, the name of our previously created workflow. Select the book.messageboard.interface.IMessage interface and the “publish-message” and click on “Add Mappings”. The previous page should return, but this time with an entry below “Available Mappings”.
下一步就是声明工作流到接口的映射。这样就要到工作流管理器的 “Content/Process Registry” 标签。在该页你可以看到一个接口列表 (many of them) 和一个过程定义名列表,该列表只包含一个条目,即我们以前创建的工作流名。选择 book.messageboard.interface.IMessage 接口和“publish-message” ,并且点击 “Add Mappings”。返回到上一页,但这次在 “Available Mappings” 下就出现了一个条目。

But how does the workflow gets appended to a message object? The content workflow manager is a subscriber to IObjectCreated events. If the created object implements an interface for which we have a workflow, then a process instance of this workflow is added to the object as an annotation. Note that one can assign many different workflows to an object. The workflow manager is subscribed as soon as you make it active as utility, which we already did when we registered it.
那么工作流是怎样被添加到消息对象中去的呢? 内容工作流管理器is a subscriber to IObjectCreated events. 如果被创建的对象实现了工作流接口,那么该工作流的过程实例就做为 annotation 被添加到对象中。注意可以指定多个不同的工作流到一个对象。工作流管理器在我们注册它时就做为程序被激活了,同时也被订阅。

20.4 Step IV: Testing the Workflow(20.4 步骤 IV: 测试工作流)

The workflow will only work with new Messages, of course. So, in the folder you created the workflow components, create a new Message Board and add a new Message to it. If you now click on the Workflows tab you will see that it is not empty anymore. In the selection box you can see all available workflows; currently there should be only one called “Message Publication Review” (remember the workflow title in the XML?). You can choose it.
显然工作流只为新消息工作。因此在你创建工作流组件的文件夹中,创建一个新的消息栏并添加新消息。如果你现在点击 Workflows(工作流)标签时,你可以看到它已不再为空。在选择框中你可以看到所有可用的工作流;当前那里只有一个名为 “Message Publication Review” 的(还记得在 XML 中的工作流标题吗?)。你可以选中它。

Below the selection box you can see the current status of the Message, which is private at this point; remember, the transition from initial to private is automatic based on our workflow definition. In the last entry you now see the possible transitions you can execute from this state. Currently we can only “Submit Message” to submit the message for review. So select this transition and click “Make Transition”.
在选择框下面你可以看到消息的当前状态,这是私有的;记住,基于我们工作流定义的从初始状态到私有状态的转换是自动的。最后部分你可以看到从目前状态可能进行的转换。目前我们只能用 “Submit Message” 来提交消息。所以选择该转换并点击“Make Transition”。

The status will switch to “Pending Publication” (pending) and now you have three transition choices. You might have noticed already that this workflow is not very safe or useful, since every Editor (and only editors) can cause a transitions. See exercise 1 to solve this problem. Feel free to play with the transitions some more.
状态将切换到 “Pending Publication” (未决),现在你有三个转换选择。你也许已经注意到该工作流并不是十分安全和实用,因此每个编辑者(也只有编辑者)都可以引起转换。参见练习1尝试解释该问题。Feel free to play with the transitions some more.

20.5 Step V: Writing a nice “Review Messages” View for Message Boards(20.5 步骤 V: 为消息栏编写一个好的“Review Messages”视图)

Now that we have the basic workflow working, we should look at an example on how we can make use of the workflow mechanism. So the first task will be to provide the Editor with a nice overview over all pending messages from the message board object.
现在我们有一个正在运行的基本工作流,我们应该看一下怎样利用工作流机制的例子。因为第一个任务将是给编辑提供来自消息栏对象的所有待审消息的概要。

So we basically need a view class that recursively walks through the tree and and picks out the pending messages. To do this, it is extremely helpful to write a convenience function that simply checks whether a message has a certain status. The implementation could be something line the following, which we simply place into browser/messageboard.py:
因此我们基本上需要一个视图类用以递归遍历树并选出即将发生的消息。为了做到这点,编写一个函数简单地检查消息是否有某个状态就极为有用了。可以用下列语句实现,我们简单地将其放入 browser/messageboard.py中:

#!python
from zope.app import zapi
from zope.app.workflow.interfaces import IProcessInstanceContainer

def hasMessageStatus(msg, status, workflow='publish-message'):
"""Check whether a particular message matches a given status"""
adapter = IProcessInstanceContainer(msg)
if adapter:
# No workflow is defined, so the message is always shown.
if not adapter.keys():
return True
for item in adapter.values():
if item.processDefinitionName != workflow:
continue
if item.status == status:
return True

return False
  • Line 2 & 6: The returned adapter will provide us access to the message’s workflows (process instances). in our case we only expect to find one workflow.
    第 2 & 6 行: 返回的适配器将提供我们访问消息工作流(过程实例)的权限。在我们的例子中我们只期望得到一个工作流。
  • Line 8-10: This is some backward compatibility for the messages that were created before we added the workflow feature.
    第 8-10 行: 这里为在我们添加工作流功能之前被创建的消息提供向后兼容性。
  • Line 11-15: Look through all the workflows (process instances) and try to find the one we are looking for. If the status matches the state we are checking for, then we can return a positive result. If not, we will eventually return False (Line 16).
    第 11-15 行: 浏览所有工作流(过程实例)并企图找到我们正在查找的。如果状态同我们找的匹配,那么我们返回确切的结果。如果不匹配,我们将最终返回 False(第 16 行)

Next we are going to implement the view class, which will provide the method getPendingMessagesInfo() which will return a list of information structures for pending messages, where each info contains the title and the URL to the workflow view of the message. Place the following view in brower/messageboard.py:
接下来我们打算实现有着 getPendingMessagesInfo() 方法的视图类,该方法将为待审消息返回一个信息结构列表,在该列表中每个信息都包含标题和到消息工作流视图的 URL。

#!python
from book.messageboard.interfaces import IMessage

class ReviewMessages:
"""Workflow: Review all pending messages"""

def getPendingMessages(self, pmsg):
"""Get all pending messages recursively."""
msgs = []
for name, msg in pmsg.items():
if IMessage.providedBy(msg):
if hasMessageStatus(msg, 'pending'):
msgs.append(msg)
msgs += self.getPendingMessages(msg)
return msgs

def getPendingMessagesInfo(self):
"""Get all the display info for pending messages"""
msg_infos = []
for msg in self.getPendingMessages(self.context):
info = {}
info['title'] = msg.title
info['url'] = zapi.getView(
msg, 'absolute_url', self.request)() + '/@@workflows.html'
msg_infos.append(info)
return msg_infos
  • Line 6-14: This is the actual recursive method that searches for all the pending messages.
    第 6-14 行: 这其实是递归方法用以搜索所有待审消息。
    • Line 8: This will be the resulting flat list of pending messages.
      第 8 行: 这将是待审消息列表
    • Line 10: Since we can find replies (messages) and attachments (files) in a message, we have to make sure that we deal with an IMessage object.
      第 10 行: 因为我们可以找到消息的回复(消息)和附件(文件),因此我们必须确保我们处理 IMessage 对象。
    • Line 11-12: If the message is pending, then add it to the list of pending messages.
      第 11-12 行: 如果消息是待审的,那么将其添加到待审消息列表
    • Line 13: Whatever message it is, we definitely want to look at its replies to see whether there are pending messages lying around.
      第 13 行:无论什么消息,我们都一定要看它的回复,以便看看是否有待审消息lying around.
  • Line 16-25: This method creates a list of infos about the messages using the list of pending messages (line 20). This is actually the method that will be called from the page template.
    第 16-25 行: 该方法创建一个使用待审消息列表消息的信息列表(第 20 行)。其实它是从页面模板被调用的方法。

Next we create the template named review.pt that will display the pending messages:
接下来我们创建名为 review.pt 的模板,它将显示待审消息:

1  <html metal:use-macro="views/standard_macros/view">
2    <body>
3      <div metal:fill-slot="body" i18n:domain="messageboard">
4  
5        <h1 i18n:translate="">Pending Messages</h1>
6  
7        <div class="row" tal:repeat="msg view/getPendingMessagesInfo">
8          <div class="field">
9            <a href="" tal:attributes="href msg/url"
10                tal:content="msg/title" />
11          </div>
12        </div>
13  
14      </div>
15    </body>
16  </html>
  • Line 7-12: Iterate over all message entries and create links to each pending message, displaying its title.
    第 7-12 行: 遍历所有消息条目并为每个待审消息创建链接和显示标题。

Finally, we just have to register the new view using simply:
最后,我们只须简单地注册新视图即可:

1  <page
2      name="review.html"
3      for="book.messageboard.interfaces.IMessageBoard"
4      class=".messageboard.ReviewMessages"
5      permission="book.messageboard.PublishContent"
6      template="review.pt"
7      menu="zmi_views" title="Review Messages"/>

Now restart your Zope 3 server and enjoy the new view. You could do much more with this view, but this should give you an idea of the framework’s functionality.
现在重启你的 Zope 3 服务器享受你的新视图吧。你还可以用该视图做更多事,但它应该让你了解了整个架构的功能。

20.6 Step VI: Adjusting the Message Thread(20.6 步骤 VI: 调整消息线程)

Okay, now we have a working workflow, a way for the message writer to request publication and a view for the editor to approve or reject messages. But the workflow does not impinge on the interaction of the user with the message board at all yet. Therefore, let’s modify the message thread view to only show published messages.
好了,现在我们有了一个可运行的工作流,一个消息编写者请求公开发布消息的途径和一个供编辑来核准或拒绝消息的视图。但工作流根本没有在用户和消息栏的交互上起作用。因此,让我们修改消息线程视图以便只显示已公开发布的消息。

The change requires only two more lines in browser/thread.py. First import the hasMessageStatus() function.
所需做的改动就是在browser/thread.py里添加两行代码。首先,import hasMessageStatus() 函数。

#!python
from messageboard import hasMessageStatus

Second, extend the condition that checks that an object implements IMessage to also make sure it is published.
其次,扩展用来检查一个对象是否实现 IMessage 的条件以确保它被发布。

#!python
1  if IMessage.providedBy(child) and \
2         hasMessageStatus(child, 'published'):

And that’s it! Restart Zope and check that it works!
全部搞定!重启 Zope 并检查其是否生效!

20.7 Step VII: Automation of Workflow and Friends creation(20.7 步骤 VII: 自动工作流和友好创建)

Now that we have our workflow support completed, we should direct our attention to one last quirk. Remember when we created the workflow by hand in several steps. You certainly do not want to require your users to add all these objects by hand. It would be neat, if the workflow code would be added after the message board was created and added. And this is actually not hard to do. We only have to create a custom template and an add-view and insert it in the browser:addform directive.
现在我们已经有了我们完整的工作流支持,我们应该将我们的注意力放在最近一次 quirk 上。记得我们在前面几步手工创建工作流时,你明显不想你的用户手工添加这些对象。如果工作流代码在消息栏创建和添加之后被添加的话,那将会很简洁。这其实不难做到。我们仅须创建一个自定义模板、一个附加视图并将其插入到 browser:addform 指令中即可。

So let’s start with the template, which should provide an option for the user to choose whether the workflow objects should be generated or not. Create a new file called messageboard_add.pt and insert the following content:
所以让我们从模板开始,它应该为用户提供一个选项去选择是否应该产生工作流对象。新建一个叫 messageboard_add.pt 的文件并添加下列内容:

1  <html metal:use-macro="views/standard_macros/page">
2    <body>
3      <div metal:fill-slot="body" i18n:domain="messageboard">
4  
5        <div metal:use-macro="views/form_macros/addform">
6  
7          <div metal:fill-slot="extra_bottom" class="row">
8            <div class="field">
9              <h3><input type="checkbox" name="workflow:int"
10                      value="1" checked=""/>
11                <span i18n:translate="">Create Workflow</span>
12              </h3>
13              <span i18n:translate="">Without the workflow you will
14                not be able to review messages before they are
15                published. Note that you can always modify the
16                messageboard workflow later to make all transitions
17                automatically.</span>
18            </div>
19          </div>
20  
21        </div>
22  
23      </div>
24    </body>
25  </html>

Nothing surprising; if we find the workflow attribute in the request, we now the option was set. Next we write the custom create and add view for the messageboard, which I simply placed into browser/messageboard.py: 如果我们在这个请求里发现工作流属性,这没什么好惊讶的,我们现在设置了选项。接下来我们编写自定义创建和添加消息栏视图,我可以简单地将其放在 browser/messageboard.py 文件中。

#!python
import os
from zope.proxy import removeAllProxies

from zope.app.registration.interfaces import ActiveStatus
from zope.app.site.interfaces import ISite
from zope.app.site.service import SiteManager, ServiceRegistration
from zope.app.utility.utility import LocalUtilityService, UtilityRegistration
from zope.app.workflow.interfaces import IProcessDefinitionImportHandler
from zope.app.workflow.stateful.contentworkflow import ContentWorkflowsManager
from zope.app.workflow.stateful.definition import StatefulProcessDefinition
from zope.app.workflow.stateful.interfaces import IContentWorkflowsManager
from zope.app.workflow.stateful.interfaces import IStatefulProcessDefinition

import book.messageboard


class AddMessageBoard(object):
"""Add a message board."""

def createAndAdd(self, data):
content = super(AddMessageBoard, self).createAndAdd(data)

if self.request.get('workflow'):
folder = removeAllProxies(zapi.getParent(content))
if not ISite.providedBy(folder):
sm = SiteManager(folder)
folder.setSiteManager(sm)
default = zapi.traverse(folder.getSiteManager(), 'default')

# Create Local Utility Service
default['Utilities'] = LocalUtilityService()
rm = default.getRegistrationManager()
registration = ServiceRegistration(zapi.servicenames.Utilities,
'Utilities', rm)
key = rm.addRegistration(registration)
zapi.traverse(rm, key).status = ActiveStatus

# Create the process definition
default['publish-message'] = StatefulProcessDefinition()
pd_path = zapi.getPath(default['publish-message'])
registration = UtilityRegistration(
'publish-message', IStatefulProcessDefinition, pd_path)
pd_id = rm.addRegistration(registration)
zapi.traverse(rm, pd_id).status = ActiveStatus

import_util = IProcessDefinitionImportHandler(
default['publish-message'])

xml = os.path.join(
os.path.dirname(book.messageboard.<u>file</u>), 'workflow.xml')

import_util.doImport(open(xml, mode='r').read())

# Create Content Workflows Manager
default['ContentWorkflows'] = ContentWorkflowsManager()
cm_path = zapi.getPath(default['ContentWorkflows'])
registration = UtilityRegistration(
'wfcontentmgr', IContentWorkflowsManager, cm_path)
cm_id = rm.addRegistration(registration)
zapi.traverse(rm, cm_id).status = ActiveStatus

contentmgr = default['ContentWorkflows']
contentmgr.register(IMessage, 'publish-message')

return content
  • Line 1-14: A huge amount of imports, so that all components are available. I think this alone shows what a mess simple configuration objects and ZCML usually save us from.
    第 1-14 行: 大量的导入,使所有的组件可用。I think this alone shows what a mess simple configuration objects and ZCML usually save us from.
  • Line 20-21: The createAndAdd method is the only one we have to override and extend. The good part is that the original method returns the added message board instance itself, so that we store it and make use of it. After this line, the message board is already created and added.
    第 20-21 行: createAndAdd 方法是唯一一个我们不得不重写和扩展的。
  • Line 23: If the user wants us to autogenerate the workflow objects, then let’s do it.
    第 23 行: 如果用户想我们自动生成工作流对象,那么我们就生成。
  • Line 24: Grab the folder that contains the message board.
    第 24 行: 得到包含消息栏的文件夹。
  • Line 25-27: Make sure that the folder is a site. If not make it one.
    第 25-27 行: 确保该文件夹是一个站点。如果不是就生成站点。
  • Line 28: Now we just get the default site management folder, into which we will place all the local component.
    第 28 行: 现在我们正好得到了缺省的站点管理文件夹,将所有本地组件放入其中。
  • Line 30-36: Create a new local utility service, so that we can register our local utilities we are about to create. Note that both the “Content Workflow Manager” and the “Stateful Process Definition” are local utilites.
    第 30-36 行: 创建新的本地工具服务(local utility service),这样我们可以注册我们自己打算创建的本地工具。注意 “Content Workflow Manager” 和 “Stateful Process Definition” 都是本地工具。
  • Line 38-44: Add the a new process definition and register it to be usable (active).
    第 38-44 行: 添加新的过程定义并注册它使它能用(激活)
  • Line 46-52: Here comes the tricky part. We have to create the workflow states and transitions from our saved workflow.xml file. But where to get the directory from? The easiest way is to import the package itself, get the path, then truncate the init.py part and we should be left with the directory path. You then simply add the workflow XML filename at the end and open it for import. The reason we want to use the os module everywhere is that we want to keep Zope packages platform-independent.
    第 46-52 行: 这里比较棘手。我们需要创建工作流状态和我们保存在 workflow.xml 文件中的转换。但从哪里得到目录呢?最容易的方法就是导入包本身,得到路径,然后截掉 init.py 部分,我们就留下了目录路径。然后你只需简单地在最后添加工作流 XML 文件名,然后打开该文件将其导入进来。
  • Line 54-63: Create the content workflows manager, which gets notified when IObjectCreatedEvent events occurr, so it can add process instances to it. On line 63 we tell the system that the “publish-message” workflow (created above) should be used only for IMessage components.
    第 54-63 行: 创建内容工作流管理器,以便在 IObjectCreatedEvent 事件发生时得到通知,因此它可以添加过程实例到它本身。在第 63 行我们告诉系统 “publish-message” 工作流(上面创建的)将仅为 IMessage 组件使用。

Now we need to register the add view class and template with the addform in ZCML. The addform directive for the message board therefore becomes:
现在我们需要在 ZCML 注册添加视图类和带有 addform 的模板。 消息栏的 addform 指令因此成为:

1  <addform
2      label="Add Message Board"
3      name="AddMessageBoard.html"
4      template="messageboard_add.pt"
5      class=".messageboard.AddMessageBoard"
6      schema="book.messageboard.interfaces.IMessageBoard"
7      content_factory="book.messageboard.messageboard.MessageBoard"
8      fields="description"
9      permission="zope.ManageContent"
10      />
  • Line 3-4: See how easy it is to incorporate custom templates and classes for an add form (the same is true for edit forms).
    第 3-4 行: 看看合并为添加表单自定义的模板和类是多么简单(对编辑表单同样适用)。

After restarting Zope, you should be able to enjoy the changes. Create a new Folder and in it a new Message Board. You should now see the new option and after the message board was successfully created, the workflow components should be available in the parent folder.
在重启 Zope 之后,你应该可以享受变化了。创建一个新的文件夹并在该文件夹中创建一个新的消息栏。你现在应该可以看到新的选项并在消息栏被成功创建后,在父文件夹中就可以使用工作流组件了。

20.8 The Theory(20.8 原理)

Now that we have completed the practical part of the chapter, we should look a bit more carefully at the framework supporting all this functionality. The framework was designed to support any type of generic workflow implementation. The Zope community itself has produced two, the “activity” and “entity” model.
既然我们已经完成了本章的实用部分,我们应该更仔细看看框架所支持的所有功能。该框架被设计用来支持任何类型的通用工作流实现。 Zope 社区自己就做了两个,“activity” 和 “entity” 模式。

Activity-based workflows implement workflow in a transition-centric fashion, where an object is moved in a graph of workflow states and transitions outside of its physical hierarchy. This type of model was developed by the Workflow Management Coalition (WfMC) and is implemented in the Zope 2 OpenFlow/CMFFlow product. The advantage of this model is a high degree of flexibility and scalability, which is well established thanks to the WfMC.
基于 Activity 模式的工作流实现以转换为中心的工作流,对象被移入工作流状态图表中并在其物理体系之外被转换。这种模式被工作流管理联盟(WfMC)开发并在 Zope 2 的 OpenFlow/CMFFlow 产品中实现。该模式优势在于其高度的灵活性和伸缩性,感谢WfMC。

Entity-based workflows, on the other hand, store the current workflow state of the object as meta data in the object itself, so that no real workflow graph exists, but is only defined by a set of states and transitions. This model was implemented by DCWorkflow in Zope 2 and is known as “stateful” in Zope 3. One of its advantages is simplicity of implementation and a flatter learning curve. It is the workflow type we used in this chapter.
而在另一方面,基于 Entity-based 的工作流在对象自身作为元数据保存对象当前工作流状态,因此真正意义上的工作流图表是不存在的,但定义了状态和转换集。该模式被在 Zope 2中的 DCWorkflow 实现,在 Zope 3 中被称作 “stateful”。它们的优势在于实现简单并拥有良好的学习曲线。也是我们在本章所采用的工作流类型。

Some terms:
一些术语:

  • Process Definition: This component defines in what states a content object can be and what the possible transitions between them are. It is basically a blue print of the actual workflow.
    过程定义:该组件定义内容对象可以是什么状态,在状态之间可能有哪些转换。它基本上是实际工作流的蓝本。
  • Process Instance: If the Process Definition is the blueprint, then the Process Instance is the workflow itself; it is the realization of the Process Definition, which is used to actually manage the workflow for one particular object, i.e. there is one Process Instance per workflow per content component instance. Note that one object can have several workflows associated with itself.
    过程实例: 如果说过程定义是蓝本的话,那么过程实例就是工作流本身了;它是过程定义的实现,被用于为特定对象实际管理工作流,如对每个内容组件实例的每个工作流都有一个过程实例。
  • Process Instance Container: This object is used to store actual Process Instances and is usually the component that is tagged to an object via an annotation.
    过程实例容器: 该对象用于保存实际的过程实例,通常是通过 annotation 被标签到对象的组件。
  • Content Workflows Manager (stateful): This utility is responsible to add the correct workflows to a content object upon creation.
    内容工作流管理器 (stateful): 这个工具负责将合适的工作流添加到创建的内容对象中。

One of the powerful features of the “stateful” workflow implementation is that every process instance can have workflow-relevant data associated with it. The specifics of this data are specified via a schema in the process definition. When an instance of a process is appended to an object, placeholders for this data are created as well. The workflow-relevant data can be useful for transition conditions, comments and the like.
“stateful” 工作流实现强大的功能之一就是每个过程实例都有工作流相关数据。该数据的特性是由过程定义模式决定的。当过程的一个实例被添加到一个对象时,该数据的占位符也被创建。工作流相关数据在转换条件、评论等方面很有用。

Exercises(练习)

  • The current workflow is not very secure. Any message board Editor can cause all transitions. Therefore create a different permission for the “Submit Message” (private to pending) and the “Retract Message” (pending to private) transition and assign it to the Message Board user. Make sure that now users can only cause these two transitions and editors still can cause them all.
    当前的工作流并不很安全。任何消息栏编辑者都可以引发所有的转换。因此为 “Submit Message” (私有到待审)和 “Retract Message” (待审到私有)转换创建不同的权限并将其指向消息栏用户。确保现在用户只可能引起这两种转换并且编辑者一直可以引发所有的转换。
  • The ReviewMessages view is in some respects pretty boring and not very user-friendly. It would be nice to be able to mass-approve messages or reject them, in case of spamming. Extend the ReviewMessages to support this feature.
    ReviewMessages 视图在某些细节上是相当烦人并且不友好。假如能够集中批准或拒绝消息那就好了。扩展 ReviewMessages 以支持该功能。