Zope3Book Part IV 草译帖

Python/PHP/Perl 开发与设计
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次

Zope3Book Part IV 草译帖


帖子 firehare » 2005-11-10 11:19


PART I http://www.woodpecker.org.cn/share/doc/ ... ook_partI/
PART II 尚未完成
PART III http://cvs.woodpecker.org.cn/svn/woodpe ... Zope3Book/
PART IV Content Components – Advanced Techniques
第四部分 内容组件 - 高级技术

Having a well-working basic message board is great, but it is certainly not blowing away anyone. In this section some more advanced APIs are presented.

Chapter 19: Events and Subscribers
第19章 事件和订阅

Events et al are a very powerful idea. This chapter will explain how to write your own event subscribers by implementing a mail subscription feature for messages.
事件是个功能十分强大的概念,本章将通过为留言实现一个邮件订阅功能来说明如何编写自己的事件订阅接口(event sbuscriber整个不翻译会不会更好?)。

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

This chapter will show how to integrate an editorial workflow for a content component.

Chapter 21: Providing Online Help Screens

Every good application should have Online Help screens, which is outlined in this chapter.

Chapter 22: Object to File System mapping using FTP as example

While there are standard hooks for content ob jects to be handled by FTP, it is often useful to write your own FTP handlers, so that the file-to-ob ject conversion (and the other way around) seems more natural.
当内容对象的标准 hooks 由 FTP 处理时,编写你自己的 FTP 处理器通常是有用的,因为文件到对象的转变(或以其他方式)看上去会更加自然。.

Chapter 23: Availability via XML-RPC

If you want to make XML-RPC calls on your content ob jects, you must write a view declaring the methods and define how their output is mapped to a type that XML-RPC understands.

Chapter 24: Developing new Skins

This chapter gives instructions on how to implement a new skin, so that sites can be developed that do not look like the Zope Management Interface, but still allows us to make use of all the auto-generation of forms.
本章介绍如何实现一个新的皮肤,使得站点看上去不象是Zope的管理接口,but still allows us to make use of all the auto-generation of forms.

本章给出了如何实现一个新外观(skin)的步骤,这样就能开发此类站点:它们外观不象是Zope管理界面(Zope Management Interface),但仍允许我们利用表单的自动生成功能(注:该句有待改进,对Zope整个框架不了解啊)
上次由 firehare 在 2005-12-07 22:00,总共编辑 2 次。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-10 14:59

第19章 事件和订阅

Difficulty 难度
Contributor 贡献者

Skills 技能
You should be comfortable with the topics covered in the “Content Components – The Basics” part.
你应该掌握 "内容组件 - 基础" 部分的全面主题。
你应该掌握 "内容组件 - 基础" 部分的全部内容。

Feel comfortable with the Component Architecture.
Be familiar with annotations. Read the appropriate chapters in this book, if necessary.
熟悉 annotation,如有必要,请阅读本书的相关章节。

Problem/Task 问题/任务
Events are a powerful programming tool and are primary citizens in Zope 3. This chapter will concentrate on the subscription of existing events by implementing a mail subscription system for messages – whenever a message is modified, subscribers receive an E-mail about the change. This will also demonstrate again how annotations can b e added to an ob ject. In the last part of the chapter we will talk theoretically ab out triggering events.
事件是一个强大的编程工具也是Zope3重要的部分。本章将集中讲述通过一个消息邮件订阅系统来实现对已有事件的订阅 - 无论什么时候消息发生改变,订阅者都将收到描述该变化的E-Mail。本章也将示范如何将一个注释添加到一个对象中去。在本章的最后,我们将讨论事件的触发。


There are two main components that need to b e develop ed. The first is the mail subscription adapter for the message, which manages the subscription E-mails. The second comp onent is the Event Subscriber , which listens for incoming events and starts the mailing process, if appropriate.
帖子: 58428
注册时间: 2005-08-14 21:55
来自: 长沙
送出感谢: 4 次
接收感谢: 256 次


帖子 eexpress » 2005-11-10 16:26

● 鸣学
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-10 22:03


帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-14 14:18

19.1 Step I: Mail Subscription Interface
19.1 步骤 I:邮件订阅接口

We need to have an interface for managing the subscriptions for a particular message, i.e. add, delete and getting E-mail addresses. So add the following interface to the interfaces module:

代码: 全选

 1 class IMailSubscriptions(Interface):
 2     """This interface allows you to retrieve a list of E-mails for
 3     mailings. In our context these are messages."""
 5     def getSubscriptions():
 6         """Return a list of E-mails."""
 8     def addSubscriptions(emails):
 9         """Add a bunch of subscriptions; one would be okay too."""
11     def removeSubscriptions(emails):
12         """Remove a set of subscriptions."""

This code is simple enough, so that no further explanation is needed at this point.

19.2 Step II: Implementing the Mail Subscription Adapter
19.2 步骤 II:实现邮件订阅适配器
The implementation should b e straightforward. The subscriptions are implemented as a simple tuple data structure, which are accessible via the annotation adapter.
实现它应该是简单的。通过 annotation 适配器,用一个简单的元组结构就可以实现订阅了。

Note that the implementation makes no assumption ab out the typ e of annotation that is going to b e used, i.e. we might have used the AttributeAnnotations out of pure convenience, but the data could just as well b e stored in LDAP without having any effect on the MailSubscriptions implementation.
注意该实现并不指定将使用 annotation 适配器的类型,比如我们可能纯粹出于方便而用 AttributeAnnotations ,但数据同样也只是被保存在LDAP中而不会对邮件订阅有任何影响。
注意该实现并不对将要用到的标记(annotation)类型作任何假定,比如我们可能纯粹出于方便而用 AttributeAnnotation ,但数据同样也可以被保存在LDAP中而不会对邮件订阅的实现有任何影响。

Since there is no need to create a new mo dule, add the following co de to the message.py file:
因此,并不需要创建一个新的模块,而只需要在 message.py 文件中添加下列代码:
因此,并不需要创建一个新模块,只需把下列代码添加到message.py 文件中:

代码: 全选

 1 from zope.app.annotation.interfaces import IAnnotations
 2 from book.messageboard.interfaces import IMailSubscriptions
 4 SubscriberKey='http://www.zope.org/messageboard#1.0/MailSubscriptions/emails'
 7 class MailSubscriptions:
 8     """Message Mail Subscriptions."""
10     implements(IMailSubscriptions)
11     __used_for__ = IMessage
13     def __init__(self, context):
14         self.context = self.__parent__ = context
15         self._annotations = IAnnotations(context)
16         if not self._annotations.get(SubscriberKey):
17             self._annotations[SubscriberKey] = ()
19     def getSubscriptions(self):
20         "See book.messageboard.interfaces.IMailSubscriptions"
21         return self._annotations[SubscriberKey]
23     def addSubscriptions(self, emails):
24         "See book.messageboard.interfaces.IMailsubscriptions"
25         subscribers = list(self._annotations[SubscriberKey])
26         for email in emails:
27             if email not in subscribers:
28                 subscribers.append(email.strip())
29         self._annotations[SubscriberKey] = tuple(subscribers)
31     def removeSubscriptions(self, emails):
32         "See book.messageboard.interfaces.IMailSubscriptions"
33         subscribers = list(self._annotations[SubscriberKey])
34         for email in emails:
35             if email in subscribers:
36                 subscribers.remove(email)
37         self._annotations[subscriberKey] = tuple(subscribers)

Line 4: This is the fully qualified subscrib er annotation key that will uniquely identify this annotation data. Here a URL is used, but dotted names are also common.
第 4 行:这是完全合法的订阅 annotation 关键字,它将唯一标志该 annotation 数据。在这里使用了一个URL,但带点号的名字也是可以的。
第 4 行:这是完全确定的订阅者annotation 关键字,它将唯一确定这份annotation 数据。这里使用了一个URL,以点分隔的名字也很常见。

Line 11: While this declaration is not needed, it clearly signifies that this implementation is an adapter for IMessage objects.
第 11 行:这句声明不是必需的,它清楚的标示该实现是IMessage对象的一个适配器。
第 11 行:尽管这句声明不是必需的,但它能清晰的表明该实现是个IMessage对象适配器。

Line 14: Since this adapter will use annotations, it will b e a trusted adapter, meaning that it will b e a proxied ob ject. All proxied objects must provide a location (at least through a parent attribute) so that p ermission declarations can b e found. Otherwise only global p ermission settings would b e available.
第 14 行:既然该适配器要使用,那么它就是一个被信任的适配器,也就意味着它将是一个被代理的对象。所有被代理的对象必须提供一个定位(至少也要通过一个父属性)以便权限声明能够被发现。否则只能使用全局权限。
第 14 行:既然该适配器要使用nnotation,那么它就将是一个可信任适配器,也就意味着它将是一个被代理的对象。所有被代理的对象必须提供一个位置(至少也要通过一个父属性)以便能找到权限声明。否则只能使用全局权限设置。

Line 15: Here we are getting the Annotations adapter that will provide us with a mapping ob ject in which we will store the annotations. Note that this statement says nothing ab out the typ e of annotation we are ab out to get.
第 15 行:在这里我们得到了一个 Annotations 适配器,以便提供给我们一个在哪保存 annotations 的一个映射对象。注意该语句并未提及我们将使用什么类型的 annotation 。

Line 16–17: Make sure an entry for our subscriber key exists. If not, create an empty one.
第 16–17 行:确保我们的订户关键字存在。如果没有的话,创建一个空的。

Line 19–37: There is nothing interesting going on here. The only fact worth mentioning is the use of tuples instead of lists, which make the co de a bit more complex, but tuples are not mutable, so that they are automatically saved in the ZODB, if we have AttributeAnnotations.
这里没什么可讲的。唯一值得提及的就是用元组代替了列表,使代码复杂了点,但元组是不可变的,因此如果我们有 AttributeAnnotation 的话,它们是会自动存到ZODB中的。

This is pretty much everything that is to the subscription part of this step. We can now register the new comp onent via ZCML using the adapter directive:

代码: 全选

 1 <adapter
 2     factory=".message.MailSubscriptions"
 3     provides=".interfaces.IMailSubscriptions"
 4     for=".interfaces.IMessage"
 5     permission="book.messageboard.Add"
 6     trusted="true" />

Line 2–4: Like for the ISized adapter, we specify the necessary adapter registration information.
第 2–4 行:象 ISized 适配器一样,我们也指定必要的适配器注册信息。

Line 6: If an adapter is declared trusted then its context (the ob ject b eing passed into the adapter constructor) will not b e security proxied. This is necessary so that the annotations adapter can use the annotations attribute to store the annotations. If the adapter is not trusted and the context is security proxied, then a ForbiddenAttribute error will be raised whenever we try to access the annotations.
第 6 行:如果一个适配器声明是受信任的,那么它的 context (被放入适配器构造器中的对象)将不是安全代理。这是必需的,因此 annotation 适配器可以使用 annotation 属性来保存 annotation 。如果适配器是不受信任的且 context 是安全代理,那么我们如果要访问 annotation ,那么就会得到一个 ForbiddenAttribute 错误。

Line 5: Once an adapter is trusted, the adapter itself is security proxied. Therefore we need to define a p ermission that is required to use the adapter.
第 5 行:一旦一个适配器受信任,那么该适配器它自己也被安全地代理。因此我们需要定义一个权限以便使用该适配器。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-17 10:59

19.3 Step III: Test the Adapter
19.3 步骤 III: 测试适配器

The tests are as straightforward as the implementation. In the doc string of the MailSubscriptions class add the following documented testing code.
该测试如同实现一样简单。在 MailSubscriptions 类的文档字符串中添加下列文档测试代码。

代码: 全选

 1 Verify the interface implementation
 3 >>> from zope.interface.verify import verifyClass
 4 >>> verifyClass(IMailSubscriptions, MailSubscriptions)
 5 True
 7 Create a subscription instance of a message
 9 >>> msg = Message()
10 >>> sub = MailSubscriptions(msg)
12 Verify that we have initially no subscriptions and then add some.
14 >>> sub.getSubscriptions()
15 ()
16 >>> sub.addSubscriptions(('foo@bar.com,))
17 >>> sub.getSubscriptions()
18 ('foo@bar.com,)
19 >>> sub.addSubscriptions(('blah@bar.com',))
20 >>> sub.getSubscriptions()
21 ('foo@bar.com, 'blah@bar.com)
22 >>> sub.addSubscriptions(('doh@bar.com',))
23 >>> sub.getSubscriptions()
24 ('foo@bar.com', 'blah@bar.com', 'doh@bar.com')
26 Now let's also check that we can remove entries.
28 >>> sub.removeSubscriptions(('foo@bar.com',))
29 >>> sub.getSubscriptions()
30 ('blah@bar.com', 'doh@bar.com')
32 When we construct a new mail subscription adapter instance, the values
33 should still be there
35 >>> sub1 = MailSubscriptions(msg)
36 >>> sub1.getsubscriptions()
37 ('blah@bar.com', 'doh@bar.com')

Line 3–5: Do a very detailed analysis to ensure that the MailSubscriptions class implements the IMailSubscriptions interface.
第 3–5 行: 对 MailSubscriptions 类进行非常细致的分析,以确保 IMailSubscriptions 接口的实现。

Line 7–10: In doc tests it helps very much if you emphasize how you setup your test case. Here we make that very explicit by creating a separate section and add some explanation to it.
第 7–10 行:在文档测试中,强调如何设定你的测试事例是非常有帮助的。这里我们十分明确地创建了单独的一节并对其增加一些说明。

Line 12–24: Check that we can retrieve the list of subscribers and add new ones as well.
第 12–24 行:检查我们能检索到的订户列表并添加一个新的订户。

Line 26–30: Make sure deleting subscriptions works as well.
第 26-30 行:确认订户删除也运行正常。

Line 32–37: When we create a new adapter using the same message, the subscriptions should still be available. This ensures that the data is not lost when the adapter is destroyed. An even stronger test would be that the persistence also works.
第 32–37 行:当我们使用同样消息创建一个新的适配器时,订阅也一直可用。这样就可以确保在适配器被销毁时数据不会丢失。一个更有效的测试就是一直运行下去。
Note that there is no check for the case the annotation is not there. This is due to the fact that the MailSubscriptions constructor should make sure the annotation is available, even though this means to simply create an empty storage, so we have definitely covered this case in the implementation.
注意这里并没有检查 annotation 事例是否存在。这源于这样一个事实,邮件订阅构造器确保 annotation 是可用, 即使这只意味着简单地创建一个空的存储。因此我们可以确定整个事例在这个实现中。
Since the adapter uses annotations, it requires some setup of the component architecture to run the tests. We already bring the services up for the tests, but now we also have to register an adapter to provide the annotations. Therefore we have to write a custom setUp() method and use it. The testing code in tests/test message.py changes to:
因为适配器使用 annotations,因此在测试时它需要设置组件结构来运行该测试。我们已经为测试提供了服务,但现在我们需要注册一个适配器用来提供 annotations。因此,我们要编写一个自定义的setUp()方法并使用它。将message.py文件里的 tests/test 中的测试代码改成:

代码: 全选

 1 from zope.interface import classImplements
 3 from zope.app.annotation.attribute import AttributeAnnotations
 4 from zope.app.interfaces.annotation import IAnnotations
 5 from zope.app.interfaces.annotation import IttributeAnnotatable
 6 from zope.app.tests import placelesssetup
 7 from zope.app.tests import ztapi
 9 def setUp(test):
10     placelesssetup.setUp()
11     classImplements(Message, IAttributeAnnotatable)
12     ztapi.provideAdapter(IAttributeAnnotatable, IAnnotations,
13                                        AttributeAnnotations)
15 def test_suite():
16     return unittest.TestSuite((
17         DocTestSuite('book.messageboard.message',
18                                setUp=setUp, tearDown=placelesssetup.tearDown),
19         unittest.makeSuite(Test),
20         ))

Line 7: The ztapi module contains some very useful convenience functions to set up the component architecture for a test, such as view and adapter registration.
第 7 行:ztapi 模块包含了一些对测试来说非常方便且有用的功能用来设置测试的组件结构,如视图和适配器注册。
Line 9: Note that the setUp() expects a test argument which is an instance of DocTest. You can use this object to provide global test variables。
第 9 行:注意 setUp() 需要一个DocTest实体的测试参数。你可以用该对象来提供全局的测试变量。

Line 11: We usually use ZCML to declare that Message implements IAttributeAnnotatable. Since ZCML is not executed for unit tests, we have to do it manually here.
第 11 行:我们通常用ZCML来声明消息实现 IAttributeAnnotatable。因为ZCML不会被单元测试执行,所以我们需要在这里手动运行它。

Line 12–13: Setup the adapter that allows us to look up an annotations adapter for any object claiming it is AttributeAnnotatable.
第 12-13 行:将适配器设置成允许我们可以查找在任何对象中声明它是 IAttributeAnnotatable 的 annotations 适配器。

You should now run the tests and ensure they pass.
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-17 14:18

19.4 Step IV: Providing a View for the Mail Subscription
19.4 步骤 IV:为邮件订阅提供一个视图

The last piece we have to provide is a view to manage the subscriptions via the Web. The page template ( subscriptions.pt) could look like this:
最后一步我们要提供一个视图以便可以通过Web来管理订阅。页面模板 ( subscriptions.pt) 看上去就象这样:

代码: 全选

 1 <html metal:use-macro="views/standard_macros/view">
 2   <body>
 3     <div metal:fill-slot="body" i18n:domain="messageboard">
 5       <form action="changeSubscriptions.html" method="post">
 7         <div class="row">
 8           <div class="label"
 9              i18n:translate="">Current Subscriptions</div>
10          <div class="field">
11            <div tal:repeat="email view/subscriptions">
12              <input type="check" name="remails:list"
13                         value="" tal:attributes="value email" />
14              <div tal:replace="email">zope3@zope3.org</div>
15            </div>
16            <input type="submit" name="REMOVE" value="Remove"
17                        i18n:attributes="value remove-button">
18          </div>
19        </div>
21        <div class="row">
22          <div class="label" i18n:translate="">
23            Enter new Users (separate by 'Return')
24          </div>
25          <div class="field">
26            <textarea name="emails" cols="40" rows="10"></textarea>
27          </div>
28        </div>
30        <div class="row">
31          <div class="controls">
32            <input type="submit" value="Refresh"
33                       i18n:attributes="value refresh-button" />
34            <input type="submit" value="ADD" value="Add"
35                       i18n:attributes="value add-button" />
36          </div>
37        </div>
39      </form>
41     </div>
42   </body>
43 </html>

Line 7–19: The first part lists the existing subscriptions and let’s you select them for removal.
第 7–19 行:第一部分列出已有订阅,同时让我们可以选择它们以便删除。

Line 20–38: The second part provides a textarea for adding new subscriptions. Each E-mail address should be separated by a newline (one E-mail per line).
第 20–38 行:第二部分提供一个文本框用来添加新订阅。每个E-mail地址将被一个新行分隔开(每行一个E-mail)。

The supporting View Python class then simply needs to provide a subscriptions() method (see line 11 ab ove) and a form action.

Place the following code into browser/message.py:

代码: 全选

 1 from book.messageboard.interfaces import IMailSubscriptions
 3 class MailSubscriptions:
 5     def subscriptions(self):
 6         return IMailSubscriptions(self.context).getSubscriptions()
 8     def change(self):
 9         if 'ADD' in self.request:
10            emails=self.request['emails'].split('\n')
11            IMailSubscriptions(self.context).addSubscriptions(emails)
12        elif 'REMOVE' in self.request:
13            emails= self.request['remails']
14            if isinstance(emails, (str, unicode)):
15                emails=[emails]
16            IMailSubscriptions(self.context).removeSubscriptions(emails)
18        self.request.response.redirect('./@@subscriptions.html')

Line 9 & 12: We simply use the name of the submit button to decide which action the user intended.
第 9 & 12 行:我们简单地使用提交按钮来确定用户想做的操作。

The rest of the code should be pretty forward. The view can b e registered as follows:
The rest of the code should be pretty forward. 该视图可以象下面那样被注册:

代码: 全选

 1 <pages
 2     for="book.messageboard.interfaces.IMessage"
 3     class=".message.MailSubscriptions"
 4     permission="book.messageboard.Edit"
 5     >
 6 <page
 7     name="subscriptions.html"
 8     template="subscriptions.pt"
 9     menu="zmi_views" title="Subscriptions"
10    />
11 <page
12     name="changeSubscriptions.html"
13     attribute="change"
14     />
15 </pages>

Line 1: The browser:pages directive allows us to register several pages for an interface using the same view class and p ermission at once. This is particularly useful for views that provide a lot of functionality.
第 1 行:浏览器:页面指令允许我们可以马上为使用相同视图类和权限的接口注册相应的页面。这对于视图来说是特别有用的,它可以提供很多的功能。

Line 6–10: This page uses a template for creating the HTML.
第 6-10:该页利用一个模板来创建HTML

Line 11–14: This view on the other hand, uses an attribute of the view class. Usually methods on the view class do not return HTML but redirect the browser to another page.
第 11-14: 另外该视图使用了视类的属性。通常视图类中的方法不会返回HTML,但可以将浏览器重定向到另一个页。

Line 9: Make sure the Subscriptions view becomes a tab for the Message object.
第 9 行:确保订阅视图已经成为一个消息对象标签。

It is amazing how compact the browser:pages and browser:page directives make the registration. In the early development stages we did not have this directive and everything had to b e registered via browser:view, which required a lot of rep etitive boilerplate in the ZCML and Python code.
如此紧凑地使用 browser:pages 和 browser:page 指令进行注册是令人惊奇的。在早期的开发阶段我们没有这个指令,什么都必须通过 browser:view 来注册,它在 ZCML 和 Python 代码中大量的可重用模板是必需的。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-28 20:33

19.5 Step V: Message Mailer - Writing an Event Subscriber
19.5 步骤 V: 消息收发器 - 编写事件订阅

Until now we have not even said one word about events. But this is about to change, since the next task is to implement the subscriber object. The generic event system is very simple: It consists of a list of subscribers and a notify() function. Subscribers can be subscribed to the event system by appending them to the list. To unsubscribe an object it must be removed from the list. Subscribers do not have to be any special type of objects; they merely have to be callable. The notify() function takes an object (the event) as a parameter; it then iterates though the list and calls each subscriber passing through the event.
直到现在我们也还没有说到事件。不过现在不同了,因为下一个任务就是要实现订阅对象。这个通用的事件系统是非常简单的:它包括着一个订户列表和一个 notify() 函数。订户可以被事件系统将其添加到列表中以完成订阅。而取消订阅则需将该对象从列表中移除即可。订户不必是特定类型的对象,它们只是能被调用就行。 notify() 函数将对象(事件)作为参数;然后通过该事件来反复使用列表调用每个订户。

This means that we have to implement a __call__() method as part of our message mailer API in order to make it a subscriber. The entire MessageMailer class should look like this (put it in the message module):
这就意味着我们必须实现 __call__() 方法,并将其做为我们消息收发器 API 的一部分,以便将其做为一个订户。完整的 MessageMailer 类如下所示(将下面这段放到 message 模块中):

代码: 全选

1  from zope.app import zapi
2  from zope.app.container.interfaces import IObjectAddedEvent
3  from zope.app.container.interfaces import IObjectRemovedEvent
4  from zope.app.event.interfaces import IObjectModifiedEvent
5  from zope.app.mail.interfaces import IMailDelivery

7  class MessageMailer:
8      """Class to handle all outgoing mail."""

10      def __call__(self, event):
11          """Called by the event system."""
12          if IMessage.providedBy(event.object):
13              if IObjectAddedEvent.providedBy(event):
14                  self.handleAdded(event.object)
15              elif IObjectModifiedEvent.providedBy(event):
16                  self.handleModified(event.object)
17              elif IObjectRemovedEvent.providedBy(event):
18                  self.handleRemoved(event.object)
20      def handleAdded(self, object):
21          subject = 'Added: '+zapi.getName(object)
22          emails = self.getAllSubscribers(object)
23          body = object.body
24          self.mail(emails, subject, body)
26      def handleModified(self, object):
27          subject = 'Modified: '+zapi.getName(object)
28          emails = self.getAllSubscribers(object)
29          body = object.body
30          self.mail(emails, subject, body)
32      def handleRemoved(self, object):
33          subject = 'Removed: '+zapi.getName(object)
34          emails = self.getAllSubscribers(object)
35          body = subject
36          self.mail(emails, subject, body)
38      def getAllSubscribers(self, object):
39          """Retrieves all email subscribers."""
40          emails = ()
41          msg = object
42          while IMessage.providedBy(msg):
43              emails += tuple(IMailSubscriptions(msg).getSubscriptions())
44              msg = zapi.getParent(msg)
45          return emails
47      def mail(self, toaddrs, subject, body):
48          """Mail out the Message Board change message."""
49          if not toaddrs:
50              return
51          msg = 'Subject: %s\n\n\n%s' %(subject, body)
52          mail_utility = zapi.getUtility(IMailDelivery, 'msgboard-delivery')
53          mail_utility.send('mailer@messageboard.org'  , toaddrs, msg)
55  mailer = MessageMailer()

* Line 2-4: We want our subscriber to handle add, edit and delete events. We import the interfaces of these events, so that we can differentiate among them.
* 第 2-4 行: 我们想我们的订户可以手工添加、编辑和删除事件。我们导入这些事件的接口,以便区分这些操作。
* Line 10-18: This is the heart of the subscriber and this chapter. When an event occurs the __call__() method is called. First we need to check whether the event was caused by a change of an IMessage object; if so, let’s check which event was triggered. Based on the event that occurred, a corresponding handler method is called.
* 第 10-18 行: 这是订户和本章的核心部分。当事件引起 __call__() 方法被调用时,首先我们需要检查是否是 IMessage 对象的改变引发了事件,如果是的话,我们检查触发了什么事件。并根据该事件调用相应的处理方法。
* Line 20-36: These are the three handler methods that handle the various events. Note that the modified event handler should really generate a nice diff, instead of sending the entire message again.
* 第 20-36 行: 这里有三个处理方法用以处理不同的事件。注意被修改后的事件处理真正可以做到 Diff,而不是将整个消息再发一次。
* Line 38-45: This method retrieves all the subscriptions of the current message and all its ancestors. This way someone who subscribed to message HelloEveryone will also get e-mailed about all responses to HelloEveryone.
* 第 38-45 行: 该方法得到当前消息及其父消息的所有订阅。通过这种方式订阅了消息 HelloEveryone 的用户就可以收到所有对 HelloEveryone 消息回复的 e-mail 了。
* Line 47-53: This method is a quick introduction to the Mail Delivery utility. Note how simple the send() method of the Mail Delivery utility is; it is the same API as for smtplib. The policy and configuration on how the mail is sent is fully configured via ZCML. See the configuration part later in this chapter.
* 第 47-53 行: 该方法是对 Mail Delivery utility 的快速说明。注意 Mail Delivery utility 的 send() 方法是多么的简单;它有着同 smtplib 相同的 API。关于如何通过ZCML发送完整配置的策略和配置,可参见本章后面的配置部分。
* Line 60: We can only subscribe callable objects to the event system, so we need to instantiate the MessageMailer component.
* 第 60(55?)行: 我们可以仅仅订阅可调用对象到事件系统,因此我们需要实例化 MessageMailer 组件。

Lastly, we need to register the message mailer component to the event service and setup the mail utility correctly. Go to your configuration file and register the following two namespaces in the configure element:

代码: 全选

1  xmlns:mail="http://namespaces.zope.org/mail"

Next we setup the mail utility:

代码: 全选

1  <mail:smtpMailer name="msgboard-smtp" hostname="localhost" port="25" />

3  <mail:queuedDelivery
4      name="msgboard-delivery"
5      permission="zope.SendMail"
6      queuePath="./mail-queue"
7      mailer="msgboard-smtp" />

* Line 1: Here we decided to send the mail via an SMTP server from localhost on the standard port 25. We could also have chosen to send the mail via the command line tool sendmail.
* 第 1 行: 这里我们决定通过本机 25 端口的 SMTP 服务来发送邮件。我们也可以通过命令行工具 sendmail 来发送邮件。
* Line 3-7: The Queued Mail Delivery utility does not send mails out directly but schedules them to be sent out independent of the current transaction. This has huge advantages, since the request does not have to wait until the mails are sent. However, this version of the Mail Utility requires a directory to store E-mail messages until they are sent. Here we specify the mail-queue directory inside the message board package. The value of the attribute name is used by the MessageMailer to retrieve the Queued Mail Delivery utility. Another Mail utility is the Direct Mail Delivery utility, which blocks the request until the mails are sent.
* 第 3-7 行: Queued Mail Delivery utility 不会直接将邮件发出去,而是将它们以独立于当前事务的方式发送出去。这样做带来极大的好处,就是请求不必再等待直到邮件被发好。不过这样的 Mail Utility 需要一个目录用来保存 E-mail信息直到它们被发送出去。在这里我们在消息栏包中指定了 mail-queue 目录。属性名的值被 MessageMailer 用来获得 Queued Mail Delivery utility。而另一个 Mail utility 是 Direct Mail Delivery utility,它在邮件被发送之前会阻止请求。

Now we register our message mailer object for the events we want to observe:

代码: 全选

1  <subscriber
2      factory=".message.mailer"
3      for="zope.app.event.interfaces.IObjectModifiedEvent"  />

5  <subscriber
6      factory=".message.mailer"
7      for="zope.app.container.interfaces.IObjectAddedEvent"  />

9  <subscriber
10      factory=".message.mailer"
11      for="zope.app.container.interfaces.IObjectRemovedEvent"  />

The subscriber directive adds a new subscriber (specified via the factory attribute) to the subscriber list. The for attribute specifies the interface the event must implement for this subscriber to be called. You might be wondering at this point why such strange attribute names were chosen. In the Zope application server, subscriptions are realized via adapters. So internally, we registered an adapter from IObjectModifiedEvent to None, for example.
订户直接添加新订户到订户列表中(通过 factory 属性来指定)。for 属性指定接口以调用必须为该订户实现的事件。你也许会在这点上觉得惊奇,为什么选择这么奇怪的属性名。在 Zope 应用程序服务中,订阅是通过适配器来实现的。因此从内部来讲,我们是从 IObjectModifiedEvent 到 None 来注册一个适配器的。
Now you might think: “Oh let’s try the new code!”, but you should be careful. We should write some unit tests before testing the code for real.
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-11-29 16:33

19.6 Step VI: Testing the Message Mailer
19.6 步骤 VI: 测试消息收发器

So far we have not written any complicated tests in the previous chapters of the “Content Components - The Basics” part. This changes now. First of all, we have to bring up quite a bit more of the framework to do the tests. The test_message.py module’s setUp() function needs to register the location adapters and the message mail subscription adapter. So it should look like that:
到目前为止我们在先前“内容组件 - 基础”部分的章节中并没有写什么复杂的测试。现在不同了。首先我们提出相当多的框架来做测试。test_message.py 模块的 setUp() 函数需要注册位置适配器和消息邮件订阅适配器。如下所示:

代码: 全选

1  from zope.app.location.traversing import LocationPhysicallyLocatable
2  from zope.app.location.interfaces import ILocation
3  from zope.app.traversing.interfaces import IPhysicallyLocatable

5  from book.messageboard.interfaces import IMailSubscriptions
6  from book.messageboard.interfaces import IMessage
7  from book.messageboard.message import MailSubscriptions

9  def setUp():
10      ...
11      ztapi.provideAdapter(ILocation, IPhysicallyLocatable,
12                           LocationPhysicallyLocatable)
13      ztapi.provideAdapter(IMessage, IMailSubscriptions, MailSubscriptions)

* Line 1-3 & 11-12: This adapter allows us to use the API to access parents of objects or even the entire object path.
* 第 1-3 & 11-12 行: 该适配器允许我们使用 API 来访问父对象甚至是整个对象路径
* Line 5-7 & 13: We simply register the mail subscription adapter that we just developed, so that the mailer can find the subscribers in the messages.
* 第 5-7 & 13 行: 我们简单地注册我们刚刚开发的邮件订阅适配器,因此邮件收发器可以在消息中找到订户。
* Line 10: The three dots stand for the existing content of the function.
* 第 10 行: 三个点表示在函数中已经存在的内容

Now all the preparations are made and we can start writing the doctests. Let’s look at the getAllSubscribers() method tests. We basically want to produce a message and add a reply to it. Both messages will have a subscriber. When the getAllSubscribers() method is called using the reply message, the subscribers for the original message and the reply should be returned. Here is the test code, which you should simply place in the getAllSubscribers() docstring:
现在所有的准备工作已经就绪,我们可以开始编写 doctest。让我们看看测试的 getAllSubscribers() 方法。我们的基本思路是想生成一个消息并且回复它。这两个消息将有一个订户。当在回复消息调用 getAllSubscribers() 时,订阅了原始消息的订户也能得到回复的消息。下面是测试代码,你只须简单地将其放在 getAllSubscribers() 的 docnstring 中即可:

代码: 全选

1  Here a small demonstration of retrieving all subscribers.

3  >>> from zope.interface import directlyProvides
4  >>> from zope.app.traversing.interfaces import IContainmentRoot

6  Create a parent  message as it would be  located in the message
7  board. Also add a subscriber to the message.

9  >>> msg1 = Message()
10  >>> directlyProvides(msg1, IContainmentRoot)
11  >>> msg1.__name__ = 'msg1'
12  >>> msg1.__parent__ = None
13  >>> msg1_sub = MailSubscriptions(msg1)
14  >>> msg1_sub.context.__annotations__[SubscriberKey]  = ('foo@bar.com',)
16  Create a reply to the first message and also give  it a subscriber.
18  >>> msg2 = Message()
19  >>> msg2_sub = MailSubscriptions?(msg2)
20  >>> msg2_sub.context.__annotations__[SubscriberKey]  = ('blah@bar.com',)
21  >>> msg1['msg2'] = msg2
23  When asking for all subscriptions of message 2, we should get the
24  subscriber from message 1 as well.
26  >>> mailer.getAllSubscribers(msg2)
27  ('blah@bar.com', 'foo@bar.com')

* Line 3-4: Import some of the general functions and interfaces we are going to use for the test.
* 第 3-4 行: 导入一些我们在测试中常用的函数和接口。
* Line 6-14: Here the first message is created. Note how the message must be a IContainmentRoot (line 10). This signalizes the traversal lookup to stop looking any further once this message is found. Using the mail subscription adapter (line 13-14), we now register a subscriber for the message.
* 第 6-14 行: 在这里第一个消息被创建。注意消息必须是IContainmentRoot (第 10 行)。
* Line 16-21: Here we create the reply to the first message. The parent and name of the second message will be automatically added during the __setitem__ call.
* 行 16-21 行: 在这里我们创建对第一个消息的回复。父消息和第二个消息的名称将在调用 __setitem__ 时被自动添加。
* Line 23-27: The mailer should now be able to retrieve both subscriptions. If the test passes, it does.
* 第 23-27 行: 邮件收发器现在可以收到两个订阅了,如果测试通过的话。

Finally we test the __call__() method directly, which is the heart of this object and the only public method. For the notification to work properly, we have to create and register an IMailDelivery utility with the name “msgboard-delivery”. Since we do not want to actually send out mail during a test, it is wise to write a stub implementation of the utility. Therefore, start your doctests for the notify() method by adding the following mail delivery implementation to the docstring of the method:
最后我们直接测试 __call__() 方法,它是该对象的核心也是唯一的一个全局方法。为了让通知能正常工作,我们必须创建和注册一个名为“msgboard-delivery”的 IMailDelivery utility 。因为我们并不想在测试时真的将邮件发送出去。因此,添加下列 mail delivery 的实现到 notify() 方法的 docstring 中,以开始你的 doctests。

代码: 全选

1  >>> mail_result = []

3  >>> from zope.interface import implements
4  >>> from zope.app.mail.interfaces import IMailDelivery

6  >>> class MailDeliveryStub(object):
7  ...     implements(IMailDelivery)
8  ...
9  ...     def send(self, fromaddr, toaddrs, message):
10  ...         mail_result.append((fromaddr, toaddrs, message))
12  >>> from zope.app.tests import ztapi
13  >>> ztapi.provideUtility(IMailDelivery, MailDeliveryStub(),
14  ...                      name='msgboard-delivery')

* Line 1: The mail requests are stored in this global variable, so that we can make test assertions about the supposedly sent mail.
* 第 1 行: 邮件请求被保存在该全局变量中,因此我们可以做假想的邮件发送测试。
* Line 6-10: Luckily the Mail utility requires only the send() method to be implemented and there we simply store the data.
* 第 6-10 行: 幸运的是 Mail utility 只要求实现 send() 方法并且我们可以简单地在那里保存数据。
* 12-14: Using the ztapi API, we can quickly register the utility. Be careful that you get the name right, otherwise the test will not work.
* 第 12-14 行: 使用 ztapi API,我们可以快速注册 utility。注意得到正确的名字,否则测试不能正常运行

So far so good. Like for the previous test, we now have to create a message and add a subscriber.

代码: 全选

1  Create a message.

3  >>> from zope.interface import directlyProvides
4  >>> from zope.app.traversing.interfaces import IContainmentRoot?

6  >>> msg = Message()
7  >>> directlyProvides(msg, IContainmentRoot?)
8  >>> msg.__name__ = 'msg'
9  >>> msg.__parent__ = None
10  >>> msg.title = 'Hello'
11  >>> msg.body = 'Hello World!'
13  Add a subscription to message.
15  >>> msg_sub = MailSubscriptions?(msg)
16  >>> msg_sub.context.__annotations__[SubscriberKey]?  = ('foo@bar.com',)

This is equivalent to what we did before, so nothing new here. Finally, we create an modification event using the message and send it to the notify() method. We then problem the global mail_result variable for the correct functioning of the method.
这同我们以前所做的一样,这里没什么新的东西。最终,我们创建了一个修改事件,使用消息并将其发送到 notify() 方法。We then problem the global mail_result variable for the correct functioning of the method.

代码: 全选

1  Now, create an event and  send it to the message mailer object.

3  >>> from zope.app.event.objectevent import ObjectModifiedEvent
4  >>> event = ObjectModifiedEvent(msg)
5  >>> mailer(event)

7  >>> from pprint import pprint
8  >>> pprint(mail_result)
9  [('mailer@messageboard.org',
10    ('foo@bar.com',),
11    'Subject: Modified: msg\n\n\nHello World!')]

* Line 3-4: In this particular test, we use the object modification event. Any IObjectEvent can be initiated by passing the affected object as argument to the constructor of the event.
* 第 3-4 行: 在这个特殊的测试中,我们使用对象修改事件。任何 IObjectEvent 都可以通过将被影响的对象作为事件构造函数的参数来被初始化。
* Line 5: Here we notify the mailer that an object has been modified. Note that the mailer is an instance of the MessageMailer class and is initialized at the end of the module.
* 第 5 行: 在这里我们通知邮件收发器有对象被修改。注意邮件收放器是 MessageMailer 类的一个实例并在模块后面被初始化。
* Line 7-11: The pretty print ( pprint) module comes in very handy when outputting complex data structures.
* 第 7-11 行: 美化打印(pprint)模块在输出复杂数据结构时非常方便。

We are finally done now. You should run the tests to verify your implementation and then head over to the next section to see how we can give this code a real swirl.
现在我们完成了。你可以运行测试来验证你的实现,然后到下一节看看how we can give this code a real swirl.
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-12-07 11:00

19.7 Step VII: Using the new Mail Subscription
19.7 步骤 VII: 使用新的邮件订阅

First of all, we have to restart Zope and make sure in boots up properly. Then you can go to the management interface and view a particular message. You might notice now that you have a new tab called Subscriptions, so click on it.
首先,我们必须重启 Zope 并在引导中做适当的确认。然后你到管理接口可以看到一个特定消息。你也许注意到现在你有个名为“Subscriptions”的新标签,点击它。
首先,我们必须重启 Zope 并确保得以正确启动,然后你可以进入管理界面,会看到一个特定的消息。你可能注意到现在已出现一个名为“Subscriptions”的新标签,点击它。

In the Subscriptions view, you will see a text area in which you can enter subscription E-mail addresses, which will receive E-mails when the message or any children are changed. When adding a test E-mail address, make sure this E-mail address exists and is your own, so you can verify its arrival. Click on the Add submit button to add the E-mail to the subscriber list. Once the screen returns, you will see this E-mail appear under “Current Subscriptions” with a checkbox before it, so you can delete it later, if you wish.
在Subscriptions视图里,你可以看到一个文本框以便让你输入订阅 E-mail 地址,该地址在消息或它的子消息发生改变时用于接收 E-mail。当添加一个测试 E-mail 地址时,确认该 E-mail 地址存在并且是你的,这样你就可以确定收到了邮件。点击 Add 按钮向订户列表中添加 E-mail 。当屏幕返回时,你将看到该邮件地址出现在 “Current Subscriptions”下面,同时在它前面还有一个检查框,以便以后在你需要时可以删除它。
在Subscriptions视图里,你会看到一个文本框供你输入订阅的 E-mail 地址,当该消息或其回复发生改动时,该邮件地址就能收到E-mail。在添加一个测试用的 E-mail 地址时,确保该 E-mail 地址存在并且是你自己的,这样你才能确认收到了邮件。点击 Add 提交按钮把E-mail地址添加到订户列表。当屏幕返回时,你就会看到该E-mail地址出现在 “Current Subscriptions”下,同时在它前面还有一个checkbox,以便日后需要时你可以将其删除。

Next, switch to the Edit view and modify the MessageBody a bit and submit the change. You should notice that the screen returns almost immediately, but that your mail has not necessarily arrived yet. This is thanks to the Queued Mail Delivery Utility, which sends the mails on a separate thread. However, depending on the speed of your E-mail server, a few moments later you should receive an appropriate E-mail.
然后,切换到编辑视图修改消息内容并提交改变。你可以看到屏幕几乎是立刻返回,但你的邮件不一定到达。这要归功于 Queued Mail Delivery Utility,它将邮件发送放在一个独立线程。然而根据你的 E-mail 服务器的连接速度,你将在几分钟之后收到相应的 E-mail。
然后,切换到Edit(编辑)视图对消息内容稍加修改,然后提交这一改动。你会注意到屏幕几乎立即返回,但邮件未必立即到达。这要归功于 队列邮件发送工具(Queued Mail Delivery Utility),它会以一个独立的线程来发送邮件。不过,视你的邮件服务器的速度而异,过一会儿后你就应该收到相应的E-mail。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-12-07 11:03

19.8 Step VIII: The Theory
19.8 Step VIII: 原理
19.8 步骤VIII: 原理

While this chapter demonstrates a classical use of events from an application developer point of view, it is not quite the whole story. So far we have discovered the use of the basic event system.

We did not explain how Zope uses this system. As mentioned before, the subscriber directive does not append the message mailer instance to the subscription list directly, as one may expect. Instead, it registers the message mailer as a “subscription adapter” that adapts the an event providing some event interface, i.e. IObjectModifiedEvent, to None, since it explicitly does not provide any special interface. The difference between regular and subscription adapters is that one can register several subscription adapters having the same required and provided provided interfaces. When requested, all matching adapters are returned. This allows us to have multiple subscribers for an event.
我们并没有解释 Zope 是如何使用该系统的。按照前面所说的,订户并不直接象你所期望的那样把消息邮件实例添加到订阅列表里。相反,它将消息邮件收发器注册成一个“订阅适配器”以适配一个提供事件接口的事件,如IObjectModifiedEvent ,to None,因为它明确并不提供任何特殊接口。规则和订阅适配器之间的区别在于一个可以注册几个有着相同要求并被提供接口的订阅适配器。当被请求时,将返回所有匹配的适配器。这允许我们对一个事件有多个订阅。
我们并没有说明Zope 是如何使用该系统的。按照前面所说的,订户指令并非如你所想,把消息邮件实例直接添加到订阅列表里。相反,它将消息邮件收发器注册成一个“订阅适配器”以适配一个提供事件接口的事件,如IObjectModifiedEvent ,to None,因为它明确并不提供任何特殊接口。规则和订阅适配器之间的区别在于一个可以注册几个有着相同要求并被提供接口的订阅适配器。当被请求时,将返回所有匹配的适配器。这允许我们对一个事件有多个订阅。

The Zope application server adds a special dispatch subscriber ( zope.app.event.dispatching) that forwards the notification to all adapter-based subscriptions. In the following diagram you can see how an event flows through the various parts of the system to the subscriber that will make use of the event. The example is based on the code developed in this chapter.
Zope 应用服务添加了一个特殊的急件订户(zope.app.event.dispatching)用以发送给所有基于适配器订阅的通知。在下图中你可以看到一个事件是如何流经系统的不同部分到达使用该事件的订户的。该示例基于本章的开发代码。

Figure 19.1: Demonstration of an event being distributed to its subscribers.
Figure 19.1: 事件被分发到它订户的示例

A special kind of subscribers are event channels, which change an event and redistribute it or serve as event filters. You could think of them as middle men. We could have written this chapter using event channels by implementing a subscriber that forwards an event only, if the object provides IMessage. An implementation could look as follows:
一类特殊的订户是事件通道,它可以改变和重新分发事件或用于事件过滤。你可以把它们看成中间人。如果对象提供 IMessage,通过实现一个订户只发送一个事件,我们可以用事件通道来写本章。实现如下所示:

代码: 全选

1  def filterEvents(event):
2      if IMessage.providedBy(event.object):
3          zope.event.notify(event.object, event)

The actual mailer would then be a multi-adapter that adapts from both the message and the event:

代码: 全选

1  class MessageMailer:

3      __call__(self, message, event):
4      ...

Multi-subscriptions-adapters are registered in ZCML as follows:

代码: 全选

1  <subscriber
2    factory = ".message.mailer"
3    for = ".interface.IMessage
4           zope.app.event.interface.IObjectEvent" />

The modified sequence diagram can be seen below in figure 19.8.
被修改的顺序图如图 19.8 所示。

Figure 19.2: Modification of the even publication using an event channel.

A final tip: Sometimes events are hard to debug. In these cases it is extremely helpful to write a small subscriber that somehow logs all events. In the simplest case this can be a one-line function that prints the string representation of the event in console. To subscribe a subscriber to all events, simply specify for="*" in the zope:subscriber directive.
最后的提示:有时事件是很难调试的。在这些例子里编写一个小的订户以记录全部事件将是非常有用的。最简单的,它可以是在控制台中打印出事件说明文字的一行函数。为了给一个订户订阅全部事件,在Zope:subscriber directive中简单的指定 for="*"即可。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-12-07 23:56


1. Finish the outlined implementation of the event channel above and rewrite the message mailer to be a multi-adapter.
1. 完成上述事件通道的大致实现并将消息邮件收发器重写成多适配器。
2. Implement a subscriber that subscribes to all events and prints a line to the console for received event. Then extend this subscriber to use the common logging mechanism.
2. 实现订阅全部事件的订户并在控制台中打印出所收到的事件。并扩展成使用公共记录机制的订户。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-12-08 0:18

Chapter 20 Approval Workflow for Messages
第 20 章 消息审批工作流

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.

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 包的各个方面 - 例如它并不说明如何创建处理定义 - 它只展示最通用的用法示例,将工作流集成到已存在的内容对象包中。而且实现起来惊人的简单。然而,在简单的背后有着相当复杂的接口以及它们的实现和表现。本章最后一节的目标就是用体系结构的角度来解释这个架构。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-12-08 9:04

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” 标签了。
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
送出感谢: 0
接收感谢: 2 次


帖子 firehare » 2005-12-08 14:16

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>
12      <transition
13          sourceState="published"
14          destinationState="private"
15          name="published_private"
16          title="Unpublish Message"
17          permission="book.messageboard.PublishContent"
18          triggerMode="Manual" />
20      <transition
21          sourceState="private"
22          destinationState="pending"
23          name="private_pending"
24          title="Submit Message"
25          permission="book.messageboard.Edit"
26          triggerMode="Manual" />
28      <transition
29          sourceState="INITIAL"
30          destinationState="private"
31          name="initial_private"
32          title="Make Private"
33          triggerMode="Automatic" />
35      <transition
36          sourceState="pending"
37          destinationState="published"
38          name="pending_published"
39          title="Publish Message"
40          permission="book.messageboard.PublishContent"
41          triggerMode="Manual" />
43      <transition
44          sourceState="pending"
45          destinationState="private"
46          name="pending_private"
47          title="Retract Message"
48          permission="book.messageboard.Edit"
49          triggerMode="Manual" />
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" />
59    </transitions>
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! 现在让我们看看它们是否运行正常。

回到 “Python/Php/Perl”