Zope3Book Part IV 草译帖
- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
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.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 被添加到对象中。注意可以指定多个不同的工作流到一个对象。工作流管理器在我们注册它时就做为程序被激活了,同时也被订阅。
上次由 firehare 在 2005-12-08 16:17,总共编辑 1 次。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
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.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.
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
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中:
* 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。
* Line 6-14: This is the actual recursive method that searches for all the pending messages.
* 第 6-14 行: 这其实是递归方法用以搜索所有待审消息。
o Line 8: This will be the resulting flat list of pending messages.
o 第 8 行: 这将是待审消息列表
o 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.
o 第 10 行: 因为我们可以找到消息的回复(消息)和附件(文件),因此我们必须确保我们处理 IMessage 对象。
o Line 11-12: If the message is pending, then add it to the list of pending messages.
o 第 11-12 行: 如果消息是待审的,那么将其添加到待审消息列表
o Line 13: Whatever message it is, we definitely want to look at its replies to see whether there are pending messages lying around.
o 第 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 的模板,它将显示待审消息:
* 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.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中:
代码: 全选
1 from zope.app import zapi
2 from zope.app.workflow.interfaces import IProcessInstanceContainer
3
4 def hasMessageStatus(msg, status, workflow='publish-message'):
5 """Check whether a particular message matches a given status"""
6 adapter = IProcessInstanceContainer(msg)
7 if adapter:
8 # No workflow is defined, so the message is always shown.
9 if not adapter.keys():
10 return True
11 for item in adapter.values():
12 if item.processDefinitionName != workflow:
13 continue
14 if item.status == status:
15 return True
16
17 return False
* 第 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。
代码: 全选
1 from book.messageboard.interfaces import IMessage
2
3 class ReviewMessages:
4 """Workflow: Review all pending messages"""
5
6 def getPendingMessages(self, pmsg):
7 """Get all pending messages recursively."""
8 msgs = []
9 for name, msg in pmsg.items():
10 if IMessage?.providedBy(msg):
11 if hasMessageStatus(msg, 'pending'):
12 msgs.append(msg)
13 msgs += self.getPendingMessages(msg)
14 return msgs
15
16 def getPendingMessagesInfo(self):
17 """Get all the display info for pending messages"""
18 msg_infos = []
19 for msg in self.getPendingMessages(self.context):
20 info = {}
21 info['title'] = msg.title
22 info['url'] = zapi.getView(
23 msg, 'absolute_url', self.request)() + '/@@workflows.html'
24 msg_infos.append(info)
25 return msg_infos
* 第 6-14 行: 这其实是递归方法用以搜索所有待审消息。
o Line 8: This will be the resulting flat list of pending messages.
o 第 8 行: 这将是待审消息列表
o 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.
o 第 10 行: 因为我们可以找到消息的回复(消息)和附件(文件),因此我们必须确保我们处理 IMessage 对象。
o Line 11-12: If the message is pending, then add it to the list of pending messages.
o 第 11-12 行: 如果消息是待审的,那么将其添加到待审消息列表
o Line 13: Whatever message it is, we definitely want to look at its replies to see whether there are pending messages lying around.
o 第 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>
* 第 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 服务器享受你的新视图吧。你还可以用该视图做更多事,但它应该让你了解了整个架构的功能。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
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.
OKay,现在我们有了一个正在运行的工作流,一个消息编写者要求发布的方式和一个编辑核准和拒绝消息的视图。但工作流根本没有在用户和消息栏的交互上起作用。因此,让我们修改消息线程以便只显示发布消息。
好了,现在我们有了一个可运行的工作流,一个消息编写者请求公开发布消息的途径和一个供编辑来核准或拒绝消息的视图。但工作流根本没有在用户和消息栏的交互上起作用。因此,让我们修改消息线程视图以便只显示已公开发布的消息。
The change requires only two more lines in browser/thread.py. First import the hasMessageStatus() function.
在 browser/thread.py 中仅需改变两行。第一个是导入 hasMessageStatus() 函数。
所需做的改动就是在browser/thread.py里添加两行代码。首先,import hasMessageStatus() 函数。
Second, extend the condition that checks that an object implements IMessage to also make sure it is published.
第二个,扩展对象实现 IMessage 的检查条件以确保它被发布。
其次,扩展用来检查一个对象是否实现 IMessage 的条件以确保它被发布。
And that’s it! Restart Zope and check that it works!
就是这样!重启 Zope 并检查它的运行情况!
全部搞定!重启 Zope 并检查其是否生效!
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.
OKay,现在我们有了一个正在运行的工作流,一个消息编写者要求发布的方式和一个编辑核准和拒绝消息的视图。但工作流根本没有在用户和消息栏的交互上起作用。因此,让我们修改消息线程以便只显示发布消息。
好了,现在我们有了一个可运行的工作流,一个消息编写者请求公开发布消息的途径和一个供编辑来核准或拒绝消息的视图。但工作流根本没有在用户和消息栏的交互上起作用。因此,让我们修改消息线程视图以便只显示已公开发布的消息。
The change requires only two more lines in browser/thread.py. First import the hasMessageStatus() function.
在 browser/thread.py 中仅需改变两行。第一个是导入 hasMessageStatus() 函数。
所需做的改动就是在browser/thread.py里添加两行代码。首先,import hasMessageStatus() 函数。
代码: 全选
1 from messageboard import hasMessageStatus
第二个,扩展对象实现 IMessage 的检查条件以确保它被发布。
其次,扩展用来检查一个对象是否实现 IMessage 的条件以确保它被发布。
代码: 全选
1 if IMessage.providedBy(child) and \
2 hasMessageStatus(child, 'published'):
就是这样!重启 Zope 并检查它的运行情况!
全部搞定!重启 Zope 并检查其是否生效!
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
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 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 的文件并添加下列内容:
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 文件中。
* 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 指令因此成为:
* 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.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 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>
如果我们在这个请求里发现工作流属性,这没什么好惊讶的,我们现在设置了选项。接下来我们编写自定义创建和添加消息栏视图,我可以简单地将其放在 browser/messageboard.py 文件中。
代码: 全选
1 import os
2 from zope.proxy import removeAllProxies
3
4 from zope.app.registration.interfaces import ActiveStatus
5 from zope.app.site.interfaces import ISite
6 from zope.app.site.service import SiteManager, ServiceRegistration
7 from zope.app.utility.utility import LocalUtilityService, UtilityRegistration
8 from zope.app.workflow.interfaces import IProcessDefinitionImportHandler
9 from zope.app.workflow.stateful.contentworkflow import ContentWorkflowsManager
10 from zope.app.workflow.stateful.definition import StatefulProcessDefinition
11 from zope.app.workflow.stateful.interfaces import IContentWorkflowsManager
12 from zope.app.workflow.stateful.interfaces import IStatefulProcessDefinition
13
14 import book.messageboard
15
16
17 class AddMessageBoard(object):
18 """Add a message board."""
19
20 def createAndAdd(self, data):
21 content = super(AddMessageBoard, self).createAndAdd(data)
22
23 if self.request.get('workflow'):
24 folder = removeAllProxies(zapi.getParent(content))
25 if not ISite.providedBy(folder):
26 sm = SiteManager(folder)
27 folder.setSiteManager(sm)
28 default = zapi.traverse(folder.getSiteManager(), 'default')
29
30 # Create Local Utility Service
31 default['Utilities'] = LocalUtilityService()
32 rm = default.getRegistrationManager()
33 registration = ServiceRegistration(zapi.servicenames.Utilities,
34 'Utilities', rm)
35 key = rm.addRegistration(registration)
36 zapi.traverse(rm, key).status = ActiveStatus
37
38 # Create the process definition
39 default['publish-message'] = StatefulProcessDefinition()
40 pd_path = zapi.getPath(default['publish-message'])
41 registration = UtilityRegistration(
42 'publish-message', IStatefulProcessDefinition, pd_path)
43 pd_id = rm.addRegistration(registration)
44 zapi.traverse(rm, pd_id).status = ActiveStatus
45
46 import_util = IProcessDefinitionImportHandler(
47 default['publish-message'])
48
49 xml = os.path.join(
50 os.path.dirname(book.messageboard.__file__), 'workflow.xml')
51
52 import_util.doImport(open(xml, mode='r').read())
53
54 # Create Content Workflows Manager
55 default['ContentWorkflows'] = ContentWorkflowsManager()
56 cm_path = zapi.getPath(default['ContentWorkflows'])
57 registration = UtilityRegistration(
58 'wfcontentmgr', IContentWorkflowsManager, cm_path)
59 cm_id = rm.addRegistration(registration)
60 zapi.traverse(rm, cm_id).status = ActiveStatus
61
62 contentmgr = default['ContentWorkflows']
63 contentmgr.register(IMessage, 'publish-message')
64
65 return content
* 第 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 />
* 第 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 之后,你应该可以享受变化了。创建一个新的文件夹并在该文件夹中创建一个新的消息栏。你现在应该可以看到新的选项并在消息栏被成功创建后,在父文件夹中就可以使用工作流组件了。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
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” 工作流实现强大的功能之一就是每个过程实例都有工作流相关数据。该数据的特性是由过程定义模式决定的。当过程的一个实例被添加到一个对象时,该数据的占位符也被创建。工作流相关数据在转换条件、评论等方面很有用。
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” 工作流实现强大的功能之一就是每个过程实例都有工作流相关数据。该数据的特性是由过程定义模式决定的。当过程的一个实例被添加到一个对象时,该数据的占位符也被创建。工作流相关数据在转换条件、评论等方面很有用。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
Exercises
练习
1. 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.
1. 当前的工作流并不很安全。任何消息栏编辑者都可以引发所有的转换。因此为 “Submit Message” (私有到待审)和 “Retract Message” (待审到私有)转换创建不同的权限并将其指向消息栏用户。确保现在用户只可能引起这两种转换并且编辑者一直可以引发所有的转换。
2. 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.
2. ReviewMessages 视图在某些细节上是相当烦人并且不友好。假如能够集中批准或拒绝消息那就好了。扩展 ReviewMessages 以支持该功能。
练习
1. 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.
1. 当前的工作流并不很安全。任何消息栏编辑者都可以引发所有的转换。因此为 “Submit Message” (私有到待审)和 “Retract Message” (待审到私有)转换创建不同的权限并将其指向消息栏用户。确保现在用户只可能引起这两种转换并且编辑者一直可以引发所有的转换。
2. 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.
2. ReviewMessages 视图在某些细节上是相当烦人并且不友好。假如能够集中批准或拒绝消息那就好了。扩展 ReviewMessages 以支持该功能。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
Chapter 21 Providing Online Help Screens
第 21 章 提供在线帮助屏幕
Difficulty 难度
Newcomer 新人
Skills 技能
* While this chapter has almost no direct prerequisites, the developer should still be familiar with at least the first two chapters of this section.
* 阅读本章几乎没有直接前提,开发者至少应熟悉本部分的开始两章。
* Some ZCML knowledge is of advantage.
* 一些 ZCML 知识是有帮助的。
Problem/Task 问题/任务
Offering help to the user at any point of a GUI is an important feature for all applications. Our message board package does a really bad job with providing help up to this point. This chapter will change that by using Zope 3’s online help package. This has not much to do with Python programming, but is part of the development process.
以GUI的方式给用户提供帮助对于所有应用程序来说都是个重要的特点。我们的消息栏包在提供帮助这一点上无疑做得很差。本章将使用 Zope 3 的在线帮助包来改变它。这并不需要太多的Python编程,但却是开发过程的一部分。
Solution 解决方案
This should be a very quick chapter, since there are only two tasks. First you need to write the actual help screens (can be either pain text, STX, ReST or HTML) and then you simply register them. So let’s dive right into it. Since the help will be for browser views, I prefer to place the help files in a help directory inside messageboard/browser.
这应该是非常短的一章,因为本章只有两个任务。首先你需要写真正的帮助屏幕(可以用 text, STX, ReST 或 HTML),然后简单地注册它们。因此让我们纠正它。因为帮助是基于浏览器视图的,所以我喜欢将帮助文件所在的目录放到 messageboard/browser 里。
First create a file called package_intro.rst and enter the following content:
首先创建名为 package_intro.rst 的文件,并键入以下内容:
Then a file called board_review.rst containing
然后是名为 board_review.rst 的文件,包含以下内容:
Finally add msg_edit.rst with the following text:
最后将下列文字添加到 msg_edit.rst 文件中:
Notice how I do not have titles for the text itself. We will define titles in the configuration, which is displayed as header on the Web site, so that there is no need for another title.
注意我并没有为文本本身设立标题。我将在配置中定义它,它将作为网站头部显示,因此并不需要另设一个标题。
All that’s left to do is to register the new help screens. Help Topics can be organized in a hierarchical manner. In order to keep all of the message board package screens together in one sub-tree, we make the package_info.rst help topic the parent of all the other help screens. Open your configuration file ( messageboard/browser/configure.zcml). Then we need to add the help namespace in the zope:configure element using the following declaration:
所有这一切都是为了注册新的帮助屏幕。帮助主题可以用层级的方式组织起来。为了将所有消息栏包屏幕保留在一棵子树上,我们将 package_info.rst 帮助主题设为其他所有帮助主题的父。打开你的配置文件(messageboard/browser/configure.zcml)。然后我们需要在 zope:configure 元素中添加帮助名称空间,并使用以下声明:
Now you can add the following directives:
现在你可以添加下列语句:
* Line 2: This is the id of the Help Topic as it will be available in the URL.
* 第 2 行: 这是帮助主题的ID,它将用于URL。
* Line 3: The title of the Help Topic that is displayed above the topics content.
* 第 3 行: 帮助主题的标题,它显示在主题内容上方。
* Line 4: The path of the parent help topic. The ui Help Topic comes by default with Zope 3, and you should attach all your “screen help” to it.
* 第 4 行: 父帮助主题的路径。ui帮助主题是 Zope 3 缺省的,你应该将你所有的“screen help” 附在它的下面。
* Line 5: This registers the Help Topic as the default context help for message board objects. This is an optional attribute.
* 第 5 行: 为消息栏对象作为缺省上下文帮助注册帮助主题。这是个可选属性。
* Line 6: The relative path to the help file. Zope 3 will recognize file endings and create the appropriate filters for the output. Possible endings include txt, rst, and html and stx.
* 第 6 行: 到帮助文件的相对路径。Zope 3 将通过文件后缀来识别文件并为输出创建适当的过滤器。可能的后缀包括 txt,rst,html 和 stx。
* Line 12-13: Here we register a topic specifically for the review.html view of a message in the messageboard.
* 第 12-13 行: 这里我们明确地为消息栏的消息 review.html 视图注册了一个主题。
* Line 11 & 19: Be careful to use URI-like syntax to specify the parent.
* 第 11 & 19 行: 在指定父时小心使用类 URI 语法。
Now all you need to do is to restart Zope and go to a message board’s message review view. Like all management pages, there a “Help” link on the very right side below the tabs. Usually this link just brings you to the generic online help screen, but if you click on it from the message board’s review screen, you will see the help for this particular view. Another possibility would be to create special Message and MessageBoard object introduction screens, but I found this to be overkill in this situation.
现在你要做的就是重启 Zope 并到消息栏的消息查看视图。象所有的管理页面一样,在标签下面的右首有一个“Help” 链接。通常该链接只是带你到通用在线帮助屏幕,但如果你从消息栏的消息查看屏幕点它的话,你可以看到该指定视图的帮助。另一个可能是创建一个指定消息和消息栏对象的介绍屏幕,但我发现在这种情况下那样就显得过火了
详见附件
Figure 21.1: The help screen for the message board’s “Review Messages” tab.
图 21.1: 消息栏中 “Review Messages” 标签的帮助屏幕
Exercises
练习
1. Implement online help screens for all other views.
1. 实现其他视图的在线帮助屏幕
第 21 章 提供在线帮助屏幕
Difficulty 难度
Newcomer 新人
Skills 技能
* While this chapter has almost no direct prerequisites, the developer should still be familiar with at least the first two chapters of this section.
* 阅读本章几乎没有直接前提,开发者至少应熟悉本部分的开始两章。
* Some ZCML knowledge is of advantage.
* 一些 ZCML 知识是有帮助的。
Problem/Task 问题/任务
Offering help to the user at any point of a GUI is an important feature for all applications. Our message board package does a really bad job with providing help up to this point. This chapter will change that by using Zope 3’s online help package. This has not much to do with Python programming, but is part of the development process.
以GUI的方式给用户提供帮助对于所有应用程序来说都是个重要的特点。我们的消息栏包在提供帮助这一点上无疑做得很差。本章将使用 Zope 3 的在线帮助包来改变它。这并不需要太多的Python编程,但却是开发过程的一部分。
Solution 解决方案
This should be a very quick chapter, since there are only two tasks. First you need to write the actual help screens (can be either pain text, STX, ReST or HTML) and then you simply register them. So let’s dive right into it. Since the help will be for browser views, I prefer to place the help files in a help directory inside messageboard/browser.
这应该是非常短的一章,因为本章只有两个任务。首先你需要写真正的帮助屏幕(可以用 text, STX, ReST 或 HTML),然后简单地注册它们。因此让我们纠正它。因为帮助是基于浏览器视图的,所以我喜欢将帮助文件所在的目录放到 messageboard/browser 里。
First create a file called package_intro.rst and enter the following content:
首先创建名为 package_intro.rst 的文件,并键入以下内容:
代码: 全选
1 ==========================
2 Message Board Demo Package
3 ==========================
4
5 This package demos various features of the Zope 3 Framework. If you
6 have questions or concerns, please let me know.
然后是名为 board_review.rst 的文件,包含以下内容:
代码: 全选
1 This view lists all messages in the board that are pending for
2 publication. Each listed method is a link that brings you to the
3 message's "Workflow" view where you can initiate a transition.
最后将下列文字添加到 msg_edit.rst 文件中:
代码: 全选
1 This screen allows you to edit the data (i.e. the subject and body) of
2 the Message object.
3
4 title - A one line unicode text string that briefly describes the
5 purpose/subject of the message.
6
7 body - A multiple line unicode text string that is the actual content of
8 the message. It is accepting HTML, but restricts the user to a
9 couple of selected tags. Feel free to type anything you wish.
注意我并没有为文本本身设立标题。我将在配置中定义它,它将作为网站头部显示,因此并不需要另设一个标题。
All that’s left to do is to register the new help screens. Help Topics can be organized in a hierarchical manner. In order to keep all of the message board package screens together in one sub-tree, we make the package_info.rst help topic the parent of all the other help screens. Open your configuration file ( messageboard/browser/configure.zcml). Then we need to add the help namespace in the zope:configure element using the following declaration:
所有这一切都是为了注册新的帮助屏幕。帮助主题可以用层级的方式组织起来。为了将所有消息栏包屏幕保留在一棵子树上,我们将 package_info.rst 帮助主题设为其他所有帮助主题的父。打开你的配置文件(messageboard/browser/configure.zcml)。然后我们需要在 zope:configure 元素中添加帮助名称空间,并使用以下声明:
代码: 全选
1 xmlns:help="http://namespaces.zope.org/help"
现在你可以添加下列语句:
代码: 全选
1 <help:register
2 id="messageboard"
3 title="Message Board Help"
4 parent="ui"
5 for="book.messageboard.interfaces.IMessageBoard"
6 doc_path="./help/package_intro.rst"/>
7
8 <help:register
9 id="board.review"
10 title="Publication Review"
11 parent="ui/messageboard"
12 for="book.messageboard.interfaces.IMessageBoard"
13 view="review.html"
14 doc_path="./help/board_review.rst"/>
15
16 <help:register
17 id="message.edit"
18 title="Change Message"
19 parent="ui/messageboard"
20 for="book.messageboard.interfaces.IMessage"
21 view="edit.html"
22 doc_path="./help/msg_edit.rst"/>
* 第 2 行: 这是帮助主题的ID,它将用于URL。
* Line 3: The title of the Help Topic that is displayed above the topics content.
* 第 3 行: 帮助主题的标题,它显示在主题内容上方。
* Line 4: The path of the parent help topic. The ui Help Topic comes by default with Zope 3, and you should attach all your “screen help” to it.
* 第 4 行: 父帮助主题的路径。ui帮助主题是 Zope 3 缺省的,你应该将你所有的“screen help” 附在它的下面。
* Line 5: This registers the Help Topic as the default context help for message board objects. This is an optional attribute.
* 第 5 行: 为消息栏对象作为缺省上下文帮助注册帮助主题。这是个可选属性。
* Line 6: The relative path to the help file. Zope 3 will recognize file endings and create the appropriate filters for the output. Possible endings include txt, rst, and html and stx.
* 第 6 行: 到帮助文件的相对路径。Zope 3 将通过文件后缀来识别文件并为输出创建适当的过滤器。可能的后缀包括 txt,rst,html 和 stx。
* Line 12-13: Here we register a topic specifically for the review.html view of a message in the messageboard.
* 第 12-13 行: 这里我们明确地为消息栏的消息 review.html 视图注册了一个主题。
* Line 11 & 19: Be careful to use URI-like syntax to specify the parent.
* 第 11 & 19 行: 在指定父时小心使用类 URI 语法。
Now all you need to do is to restart Zope and go to a message board’s message review view. Like all management pages, there a “Help” link on the very right side below the tabs. Usually this link just brings you to the generic online help screen, but if you click on it from the message board’s review screen, you will see the help for this particular view. Another possibility would be to create special Message and MessageBoard object introduction screens, but I found this to be overkill in this situation.
现在你要做的就是重启 Zope 并到消息栏的消息查看视图。象所有的管理页面一样,在标签下面的右首有一个“Help” 链接。通常该链接只是带你到通用在线帮助屏幕,但如果你从消息栏的消息查看屏幕点它的话,你可以看到该指定视图的帮助。另一个可能是创建一个指定消息和消息栏对象的介绍屏幕,但我发现在这种情况下那样就显得过火了
详见附件
Figure 21.1: The help screen for the message board’s “Review Messages” tab.
图 21.1: 消息栏中 “Review Messages” 标签的帮助屏幕
Exercises
练习
1. Implement online help screens for all other views.
1. 实现其他视图的在线帮助屏幕
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
Chapter 22 Object to File System mapping using FTP as example
第 22 章 使用FTP为例介绍对象到文件系统的映射
Difficulty 难度
Newcomer 新手
Skills 技能
* Be familiar with the Message Board Demo package up to this point.
* 熟悉消息栏演示包.
* Good understanding of the Component Architecture, especially Adapters.
* 对组件架构有很好的理解,尤其是适配器.
* Feel comfortable with writing ZCML-based configuration.
* 熟练编写基于 ZCML 的配置.
* Basic knowledge of the filesystem interfaces. Optional.
* 文件系统接口的基本知识. 可选。
Problem/Task 问题/任务
Zope provides by default an FTP server , which is a filesystem based protocol. This immediately raises the question about the way objects are mapped to the filesystem representation and back. To accomplish the mapping in a flexible and exchangeable way, there exists a set of interfaces that can be implemented as adapters to provide a representation that the FTP Publisher understands. This chapter shows how to implement some of the interfaces for a custom filesystem representation.
Zope 缺省提供了 FTP 服务,一个基于文件系统的协议。这就立即引发了一个关于对象到文件系统表示映射及反向映射的方式。为了能以一种灵活且可替换的方式来完成映射,有一系列的接口能够作为适配器被实现,用来提供一种能让 FTP 发布者理解的表示。本章展示了如何为自定义的文件系统表示实现一些接口。
Solution 解决方案
At this point you might wonder: “Why in the world would we have to write our own filesystem support? Is Zope not providing any implementations by default?” Well, to answer the latter question: Yes and no. There is an adapter registered for IContainer to IReadDirectory and IWriteDirectory. However, they are not very useful, since our Message Board and Message objects are not only containers but also contain content that is not displayed anywhere. Just start Zope 3 and check it out yourself. Thus it will be the goal of this chapter to create a representation that handles the containment and the content at the same time.
有一点你可能比较迷惑:“到底为什么我们必须要写自己的文件系统支持呢?Zope 不能缺省提供一些实现吗?” 好吧,对于后一个问题的回答是:能也不能。是有一个适配器将 IContainer 注册到 IReadDirectory 和 IWriteDirectory。然而,它们并不是很有用,因为我们的消息栏和消息对象不仅是个容器而且它还包含了无论哪里都无法显示的内容。只需启动 Zope 3 并自己测试一下。因而创建一个表达方式以便可以同时处理包含和内容将是本章的目标。
Since the core has already a lot of code presenting IContainer implementations as directories, we should reuse this part of the framework. The content of an object could be simply represented by a virtual file called contents, which contains all relevant data normalized as simple plain text. Note also that we will not need to have a complete mapping between the object and the filesystem representation, since we do not need or want to expose annotations for example. I suggest that the contents of the MessageBoard object simply contains the data of the description attribute and for the Message I propose the following format:
因为核心已经有许多代码提供了 IContainer 作为目录的实现,所以我们应该复用框架的这部分。对象的内容可以简单地通过虚拟文件调用内容来表示,它包含了所有的以纯文本方式表示的相关数据。同时请注意我们并不需要一个已经完成的对象和文件系统表示之间的映射,例如我们并不需要与不想使 annotation 可见。我建议消息栏对象的内容只是简单地包含描述属性的数据,并且我提议消息用以下格式表示:
This way we can also parse easily the title when new contents is submitted, since we want to implement the read and write interfaces of the filesystem representation. One of the main goals is to keep the VirtualContentsFile class as generic as possible, so that we can use it for both message boards and messages. To do that the virtual file must delegate the request to create the plain text representation to a component that knows about the specifics of the respective content object. For this task, we will have a simple IPlainText adapter that provides the plain text representation of each content component’s contents.
通过这种方式我们可以在新内容提交时很容易地分析标题,因为我们想实现文件系统表示的读写接口。主要目标之一是尽可能保持 VirtualContentsFile 类的通用性,以便我们可以在消息栏和消息中使用它。为了做到这一点,虚拟文件必须发送请求创建纯文本表示到组件,以便了解各个内容对象特性。为了这个任务,我们将使用一个简单的 IPlainText 适配器以便提供每个内容组件内容的纯文本表示。
第 22 章 使用FTP为例介绍对象到文件系统的映射
Difficulty 难度
Newcomer 新手
Skills 技能
* Be familiar with the Message Board Demo package up to this point.
* 熟悉消息栏演示包.
* Good understanding of the Component Architecture, especially Adapters.
* 对组件架构有很好的理解,尤其是适配器.
* Feel comfortable with writing ZCML-based configuration.
* 熟练编写基于 ZCML 的配置.
* Basic knowledge of the filesystem interfaces. Optional.
* 文件系统接口的基本知识. 可选。
Problem/Task 问题/任务
Zope provides by default an FTP server , which is a filesystem based protocol. This immediately raises the question about the way objects are mapped to the filesystem representation and back. To accomplish the mapping in a flexible and exchangeable way, there exists a set of interfaces that can be implemented as adapters to provide a representation that the FTP Publisher understands. This chapter shows how to implement some of the interfaces for a custom filesystem representation.
Zope 缺省提供了 FTP 服务,一个基于文件系统的协议。这就立即引发了一个关于对象到文件系统表示映射及反向映射的方式。为了能以一种灵活且可替换的方式来完成映射,有一系列的接口能够作为适配器被实现,用来提供一种能让 FTP 发布者理解的表示。本章展示了如何为自定义的文件系统表示实现一些接口。
Solution 解决方案
At this point you might wonder: “Why in the world would we have to write our own filesystem support? Is Zope not providing any implementations by default?” Well, to answer the latter question: Yes and no. There is an adapter registered for IContainer to IReadDirectory and IWriteDirectory. However, they are not very useful, since our Message Board and Message objects are not only containers but also contain content that is not displayed anywhere. Just start Zope 3 and check it out yourself. Thus it will be the goal of this chapter to create a representation that handles the containment and the content at the same time.
有一点你可能比较迷惑:“到底为什么我们必须要写自己的文件系统支持呢?Zope 不能缺省提供一些实现吗?” 好吧,对于后一个问题的回答是:能也不能。是有一个适配器将 IContainer 注册到 IReadDirectory 和 IWriteDirectory。然而,它们并不是很有用,因为我们的消息栏和消息对象不仅是个容器而且它还包含了无论哪里都无法显示的内容。只需启动 Zope 3 并自己测试一下。因而创建一个表达方式以便可以同时处理包含和内容将是本章的目标。
Since the core has already a lot of code presenting IContainer implementations as directories, we should reuse this part of the framework. The content of an object could be simply represented by a virtual file called contents, which contains all relevant data normalized as simple plain text. Note also that we will not need to have a complete mapping between the object and the filesystem representation, since we do not need or want to expose annotations for example. I suggest that the contents of the MessageBoard object simply contains the data of the description attribute and for the Message I propose the following format:
因为核心已经有许多代码提供了 IContainer 作为目录的实现,所以我们应该复用框架的这部分。对象的内容可以简单地通过虚拟文件调用内容来表示,它包含了所有的以纯文本方式表示的相关数据。同时请注意我们并不需要一个已经完成的对象和文件系统表示之间的映射,例如我们并不需要与不想使 annotation 可见。我建议消息栏对象的内容只是简单地包含描述属性的数据,并且我提议消息用以下格式表示:
代码: 全选
1 Title: <message title>
2
3 <message body>
通过这种方式我们可以在新内容提交时很容易地分析标题,因为我们想实现文件系统表示的读写接口。主要目标之一是尽可能保持 VirtualContentsFile 类的通用性,以便我们可以在消息栏和消息中使用它。为了做到这一点,虚拟文件必须发送请求创建纯文本表示到组件,以便了解各个内容对象特性。为了这个任务,我们将使用一个简单的 IPlainText 适配器以便提供每个内容组件内容的纯文本表示。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
22.1 Step I: Plain Text Adapters
22.1 步骤 I: 纯文本适配器
As said above, IPlainText? adapters are responsible to return and accept the plain text representation of the object’s content and just do the right thing with the data. They are very simple objects, having two methods, one for providing and one for processing the text.
如上所述, IPlainText 适配器是负责返回和接受对象内容的纯文本表示和just do the right thing with the data. 它们是非常简单的对象,有着两个方法,一个负责提供文本而另一个负责处理文本。
22.1.1 (a) The Interface
22.1.1 (a) 接口
The interface is simple, it defines a getText() and a setText() method:
接口是简单的,它定义了一个 getText() 方法和一个 setText() 方法
This interface should be placed in the interfaces module of the messageboard. In the doc strings I refer to the object’s “content” without further qualifying it. With content I mean all data (property values) that should be represented via the plain text representation.
该接口将代替消息栏的模块接口。在 doc strings 中所讲的对象的 “内容”是再合适不过的了。我所讲的内容是指那些所有可以用纯文本表示来表示的数据(属性值)。
The setText() method could in theory become quite complex, depending on how many properties you want to map and how you represent them. You will see that in our two cases it will be still fairly simple.
setText() 方法从理论上讲根据你有多少属性要映射以及你要如何表示它们,是可以变得相当复杂的。但在我们的两个例子中你可以看到它仍然是相当的简单。
22.1.2 (b) The Implementation
22.1.2 (b) 实现
We need two implementations, one for the Message and one for the MessageBoard class. Note that I skipped writing tests at this point, since the functionality of these adapters are carefully tested in the following code.
我们需要两个实现,一个是为消息而另一个是为消息栏类的。注意我在这次没有编写测试,因为这些适配器的功能已经在下列代码中被仔细测试过了。
First, we write the message board adapter, so open the messageboard.py file and add the following code:
首先,我们编写消息栏适配器,因此打开 messageboard.py 文件并添加以下代码:
This is an extremely simple implementation of the IPlainText interface, since we map only one attribute.
这是非常简单的 IPlainText 接口的实现,因为我们只映射了一个属性。
* Line 14: Make sure that the incoming text is unicode, which is very important for the system’s integrity.
* 第 14 行: 确保输入的文本是 unicode 编码的,这对于保持系统的整体性是非常重要的。
The implementation for the Message (put it in message.py) looks like this:
消息的实现 (写入 message.py 文件) 如下所示:
This implementation is more interesting, since we map two properties to the plain text.
该实现更有趣,因为我们映射了两个属性到纯文本。
* Line 11-12: In typical MIME-header style, define a field with the pattern <name>:<value> for the title and place the body as content. Note that the standard requires an empty line after the headers too.
* 第 11-12 行: 典型的 MIME-header 类型,定义一个域用 <name>:<value> 方式来表示域标题并将内容来表示域体。注意该标准同样要求在域头之后要有个空行。
* Line 15-17: Check whether a title was specified in this upload. If so, extract the title from the data and store the title; if not just ignore the title altogether. Finally store the rest of the text as the body.
* 第 15-17 行: 检查在上传时是否指定了标题。如果是的话,从数据中分离出标题并保存;如果不是的话就完全忽略标题。最后将剩余的文本作为域体保存。
22.1.3 (c) The Configuration
22.1.3 (c) 配置
The last step is to register the two adapters with the Component Architecture. Just add the following two adapter directives to configure.zcml:
最后一步就是在组件架构中注册这两个适配器。只需将下列两个适配器语句添加到 configure.zcml:
We are now done. Even though we have two new fully-functional components at this point, we gained no new functionality yet, since these adapters are not used anywhere. We have to complete the next two sections to see any results.
现在我们已经做好了。尽管我们目前已经有了两个新的完整功能的组件,但我们也不能获得新的功能,因为这些适配器还没在任何地方使用。我们还必须完成接下来的两节才可以看到结果。
22.1 步骤 I: 纯文本适配器
As said above, IPlainText? adapters are responsible to return and accept the plain text representation of the object’s content and just do the right thing with the data. They are very simple objects, having two methods, one for providing and one for processing the text.
如上所述, IPlainText 适配器是负责返回和接受对象内容的纯文本表示和just do the right thing with the data. 它们是非常简单的对象,有着两个方法,一个负责提供文本而另一个负责处理文本。
22.1.1 (a) The Interface
22.1.1 (a) 接口
The interface is simple, it defines a getText() and a setText() method:
接口是简单的,它定义了一个 getText() 方法和一个 setText() 方法
代码: 全选
1 class IPlainText(Interface):
2 """This interface allows you to represent an object's content in plain
3 text."""
4
5 def getText():
6 """Get a pure text representation of the object's content."""
7
8 def setText(text):
9 """Write the text to the object."""
该接口将代替消息栏的模块接口。在 doc strings 中所讲的对象的 “内容”是再合适不过的了。我所讲的内容是指那些所有可以用纯文本表示来表示的数据(属性值)。
The setText() method could in theory become quite complex, depending on how many properties you want to map and how you represent them. You will see that in our two cases it will be still fairly simple.
setText() 方法从理论上讲根据你有多少属性要映射以及你要如何表示它们,是可以变得相当复杂的。但在我们的两个例子中你可以看到它仍然是相当的简单。
22.1.2 (b) The Implementation
22.1.2 (b) 实现
We need two implementations, one for the Message and one for the MessageBoard class. Note that I skipped writing tests at this point, since the functionality of these adapters are carefully tested in the following code.
我们需要两个实现,一个是为消息而另一个是为消息栏类的。注意我在这次没有编写测试,因为这些适配器的功能已经在下列代码中被仔细测试过了。
First, we write the message board adapter, so open the messageboard.py file and add the following code:
首先,我们编写消息栏适配器,因此打开 messageboard.py 文件并添加以下代码:
代码: 全选
1 from book.messageboard.interfaces import IPlainText
2
3 class PlainText:
4
5 implements(IPlainText)
6
7 def __init__(self, context):
8 self.context = context
9
10 def getText(self):
11 return self.context.description
12
13 def setText(self, text):
14 self.context.description = unicode(text)
这是非常简单的 IPlainText 接口的实现,因为我们只映射了一个属性。
* Line 14: Make sure that the incoming text is unicode, which is very important for the system’s integrity.
* 第 14 行: 确保输入的文本是 unicode 编码的,这对于保持系统的整体性是非常重要的。
The implementation for the Message (put it in message.py) looks like this:
消息的实现 (写入 message.py 文件) 如下所示:
代码: 全选
1 from book.messageboard.interfaces import IPlainText
2
3 class PlainText:
4
5 implements(IPlainText)
6
7 def __init__(self, context):
8 self.context = context
9
10 def getText(self):
11 return 'Title: %s\n\n%s' %(self.context.title,
12 self.context.body)
13
14 def setText(self, text):
15 if text.startswith('Title: '):
16 title, text = text.split('\n', 1)
17 self.context.title = title[7:]
18
19 self.context.body = text.strip()
该实现更有趣,因为我们映射了两个属性到纯文本。
* Line 11-12: In typical MIME-header style, define a field with the pattern <name>:<value> for the title and place the body as content. Note that the standard requires an empty line after the headers too.
* 第 11-12 行: 典型的 MIME-header 类型,定义一个域用 <name>:<value> 方式来表示域标题并将内容来表示域体。注意该标准同样要求在域头之后要有个空行。
* Line 15-17: Check whether a title was specified in this upload. If so, extract the title from the data and store the title; if not just ignore the title altogether. Finally store the rest of the text as the body.
* 第 15-17 行: 检查在上传时是否指定了标题。如果是的话,从数据中分离出标题并保存;如果不是的话就完全忽略标题。最后将剩余的文本作为域体保存。
22.1.3 (c) The Configuration
22.1.3 (c) 配置
The last step is to register the two adapters with the Component Architecture. Just add the following two adapter directives to configure.zcml:
最后一步就是在组件架构中注册这两个适配器。只需将下列两个适配器语句添加到 configure.zcml:
代码: 全选
1 <adapter
2 factory=".messageboard.PlainText"
3 provides=".interfaces.IPlainText"
4 for=".interfaces.IMessageBoard" />
5
6 <adapter
7 factory=".message.PlainText"
8 provides=".interfaces.IPlainText"
9 for=".interfaces.IMessage" />
现在我们已经做好了。尽管我们目前已经有了两个新的完整功能的组件,但我们也不能获得新的功能,因为这些适配器还没在任何地方使用。我们还必须完成接下来的两节才可以看到结果。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
22.2 Step II: The “Virtual Contents File” Adapter
22.2 步骤 II: “虚拟内容文件” 适配器
How we implement the virtual contents file is fully up to us. However, there are benefits of choosing one way over another, since it will save us some work. The best method is to create a new interface IVirtualContentsFile, which extends zope.app.file.interfaces.IFile. The advantage is that there are already filesystem-specific adapters (implementing zope.app.filerepresentation.interfaces.IReadFile and zope.app.filerepresentation.interfaces.IWriteFile) for the above mentioned interface. IFile might not be the best and most concise interface for our needs, but the advantages of using it are very convincing.
我们如何实现虚拟内容文件是完全由我们决定的。然而,我们可以选择比较好的方式而舍弃其它的,因为它可以省我们不少事。最好的方法就是创建一个新的从 zope.app.file.interfaces.IFile 扩展而来的接口 IVirtualContentsFile 。这样做的好处在于已经有明确文件系统的适配器 (实现了 zope.app.filerepresentation.interfaces.IReadFile 和 zope.app.filerepresentation.interfaces.IWriteFile)来处理上述接口。IFile 也许不是满足我们需要的最佳和最简洁的接口,但用它的好处在于令人信服。
22.2.1 (a) The Interface
22.2.1 (a) 接口
When you look through the Zope 3 source code, you will notice that the IFile and IFileContent interfaces go hand in hand with each. Thus, our virtual contents file interface will extend both of these interfaces.
当你查看 Zope 3 源码时,你将注意到 IFile 和 IFileContent 接口是相互联系的。因此,我们的虚拟内容文件接口要将这些接口一起扩展。
22.2.2 (b) The Implementation
22.2.2 (b) 实现
Now the fun begins. First we note that IFile requires three properties, contentType, data, and size. While data and size are obvious, we need to think a bit about contentType. Since we really just want to return always text/plain, the accessor should statically return text/plain and the mutator should just ignore the input.
现在有趣的事开始了。首先我们注意到 IFile 要求三个参数, contentType, data, 和 size。当然 data 和 size 是很明显的,我们需要考虑的是 contentType。因为我们真正想返回的总是 text/plain 类型,所以 accessor 应该静态地返回 text/plain 且 mutator 则应忽略输入。
To make a long story short, here is the code, which you should place in a new file called filerepresentation.py:
长话短说,将下面的代码放在你新建的 filerepresentation.py 文件中:
* Line 11-13: As promised, the mutator ignores the input totally and is really just an empty method.
* 第 11-13 行: 根据约定,mutator 完全忽略输入成为真正的空方法。
* Line 15-17: Make sure we always return “text/plain”.
* 第 15-17 行: 确保我们始终返回 "text/plain"。
* Line 25-28 & 30-33: Now we are making use of our previously created PlainText adapters. We simply use the two available API methods.
* 第 25-28 & 30-33 行: 现在我们可以利用先前创建的 PlainText 适配器了。我们只需要简单地使用两个可用的 API 方法即可。
This was pretty straightforward. There are really no surprises here.
这相当直接,没什么诧异的。
22.2.3 (c) The Tests
22.2.3 (c) 测试
Since even the last coding step did not provide a functional piece of code, it becomes so much more important to write some careful tests for the VirtualContentsFile component. Another requirement of the tests are that this adapter is being tested with both MessageBoard and Message instances. To realize this, we write an a base test and then realize this test for each component. So in the tests folder, create a new file called test_filerepresentation.py and add the following content:
因为甚至在编程的最后一步都没有提供代码的功能片断,所以这使得为 VirtualContentsFile 组件精心编写代码就变得非常重要。另一个测试的需求就是该适配器必须被消息栏和消息实例测试。认识到这点,我们编写一个基本测试,然后为每个组件实现它。所以在测试文件夹里,新建一个名为 test_filerepresentation.py 的文件,并添加以下内容:
* Line 5: Since we are going to make use of adapters, we will need to bring up the component architecture using the PlacelessSetup.
* 第 5 行: 因为我们想用适配器,所以我们需要用 PlacelessSetup 提出组件架构。
* Line 7-13: Imports all the relevant interfaces and components for this test. This is always so much, since we have to register the components by hand (instead of ZCML).
* 第 7-13 行: 为本测试导入所有相关的接口和组件。这总是非常多的,因为我们必须手工注册组件(代替 ZCML)
* Line 17-18: The implementation of this method should create a VirtualContentsFile adapter having the correct object as context. Since the context varies, the specific test case class has to take of its implementation.
* 第 17-18 行: 本方法的实现将创建一个有着正确对象作为上下文的 VirtualContentsFile 适配器。因为上下文的变化,具体测试案例类必须take of its implementation.
* Line 20-21: Since there is a specific adapter registration required for each case (board and message), we will have to leave that up to the test case implementation as well.
* 第 20-21 行: 因为每个案例(栏和消息)都要求注册一个相应的适配器,所以我们同样也必须使之适应测试案例实施。
* Line 27-32: We need to make sure that the plain/text setting can never be overwritten.
* 第 27-32 行: 我们需要确保 plain/text 设置永远不被覆盖。
* Line 34-43: We can really just make some marginal tests here, since the storage details really depend on the IPlainText implementation. There will be stronger tests in the specific test cases for the message board and message (see below).
* 第 34-43 行: 这里我们可以真正做点最低限度的测试,因为存储的细节真正有赖于 IPlainText 实现。在为消息栏和消息进行的具体测试中还会有更多的测试(参见下面)。
* Line 45-48: Always make sure that the interface is completely implemented by the component.
* 第 45-48 行: 始终确保接口被组件完全实现。
* Line 51: This is the beginning of a concrete test case that implements the base test. Note that you should only make the concrete implementation a TestCase.
* 第 51 行: 这是实现基本测试的具体测试案例的开始。注意你应该只做 TestCase 的具体实现。
* Line 54-55: Just stick a plain, empty Message instance in the adapter.
* 第 54-55 行: 仅仅在适配器中生成一个纯文本的、空的消息实例。
* Line 60-68: Here we test that the written contents of the virtual file is correctly passed and the right properties are set.
* 第 60-68 行: 在这里我们测试虚拟文件所写内容被正确通过,同时正确的属性被设置。
* Line 71-88: Pretty much the same that was done for the Message test.
* 第 71-88 行: 同消息测试相同。
* Line 90-97: The usual test boilerplate.
* 第 90-97 行: 常用测试模板
You can now run the test and verify the functionality of the new tests.
你现在可以运行测试并确认新测试的功能。
22.2.4 (d) The Configuration
22.2.4 (d) 配置
This section would not be complete without a registration. While we do not need to register the file representation component, we are required to make some security assertions about the object’s methods and properties. I simply copied the following security assertions from the File content component’s configuration.
本部分没有注册就没有完成。然而我们并不需要注册文件表示组件,我们被要求做一些关于对象方法和属性的案例声明。我只是简单地将下列案例声明从文件内容的配置中拷过来。
* Line 3-4: We need the virtual file to be annotable, so it can reach the DublinCore for dates/times and owner information.
* 第 3-4 行: 我们需要虚拟文件是可注解的,so it can reach the DublinCore for dates/times and owner information.
22.2 步骤 II: “虚拟内容文件” 适配器
How we implement the virtual contents file is fully up to us. However, there are benefits of choosing one way over another, since it will save us some work. The best method is to create a new interface IVirtualContentsFile, which extends zope.app.file.interfaces.IFile. The advantage is that there are already filesystem-specific adapters (implementing zope.app.filerepresentation.interfaces.IReadFile and zope.app.filerepresentation.interfaces.IWriteFile) for the above mentioned interface. IFile might not be the best and most concise interface for our needs, but the advantages of using it are very convincing.
我们如何实现虚拟内容文件是完全由我们决定的。然而,我们可以选择比较好的方式而舍弃其它的,因为它可以省我们不少事。最好的方法就是创建一个新的从 zope.app.file.interfaces.IFile 扩展而来的接口 IVirtualContentsFile 。这样做的好处在于已经有明确文件系统的适配器 (实现了 zope.app.filerepresentation.interfaces.IReadFile 和 zope.app.filerepresentation.interfaces.IWriteFile)来处理上述接口。IFile 也许不是满足我们需要的最佳和最简洁的接口,但用它的好处在于令人信服。
22.2.1 (a) The Interface
22.2.1 (a) 接口
When you look through the Zope 3 source code, you will notice that the IFile and IFileContent interfaces go hand in hand with each. Thus, our virtual contents file interface will extend both of these interfaces.
当你查看 Zope 3 源码时,你将注意到 IFile 和 IFileContent 接口是相互联系的。因此,我们的虚拟内容文件接口要将这些接口一起扩展。
代码: 全选
1 from zope.app.file.interfaces import IFile, IFileContent
2
3 class IVirtualContentsFile(IFile, IFileContent):
4 """Marker Interface to mark special Message and Message Board
5 Contents files in FS representations."""
22.2.2 (b) The Implementation
22.2.2 (b) 实现
Now the fun begins. First we note that IFile requires three properties, contentType, data, and size. While data and size are obvious, we need to think a bit about contentType. Since we really just want to return always text/plain, the accessor should statically return text/plain and the mutator should just ignore the input.
现在有趣的事开始了。首先我们注意到 IFile 要求三个参数, contentType, data, 和 size。当然 data 和 size 是很明显的,我们需要考虑的是 contentType。因为我们真正想返回的总是 text/plain 类型,所以 accessor 应该静态地返回 text/plain 且 mutator 则应忽略输入。
To make a long story short, here is the code, which you should place in a new file called filerepresentation.py:
长话短说,将下面的代码放在你新建的 filerepresentation.py 文件中:
代码: 全选
1 from zope.interface import implements
2 from interfaces import IVirtualContentsFile, IPlainText
3
4 class VirtualContentsFile(object):
5
6 implements(IVirtualContentsFile)
7
8 def __init__(self, context):
9 self.context = context
10
11 def setContentType(self, contentType):
12 '''See interface IFile'''
13 pass
14
15 def getContentType(self):
16 '''See interface IFile'''
17 return u'text/plain'
18
19 contentType = property(getContentType, setContentType)
20
21 def edit(self, data, contentType=None):
22 '''See interface IFile'''
23 self.setData(data)
24
25 def getData(self):
26 '''See interface IFile'''
27 adapter = IPlainText(self.context)
28 return adapter.getText() or u''
29
30 def setData(self, data):
31 '''See interface IFile'''
32 adapter = IPlainText(self.context)
33 return adapter.setText(data)
34
35 data = property(getData, setData)
36
37 def getSize(self):
38 '''See interface IFile'''
39 return len(self.getData())
40
41 size = property(getSize)
* 第 11-13 行: 根据约定,mutator 完全忽略输入成为真正的空方法。
* Line 15-17: Make sure we always return “text/plain”.
* 第 15-17 行: 确保我们始终返回 "text/plain"。
* Line 25-28 & 30-33: Now we are making use of our previously created PlainText adapters. We simply use the two available API methods.
* 第 25-28 & 30-33 行: 现在我们可以利用先前创建的 PlainText 适配器了。我们只需要简单地使用两个可用的 API 方法即可。
This was pretty straightforward. There are really no surprises here.
这相当直接,没什么诧异的。
22.2.3 (c) The Tests
22.2.3 (c) 测试
Since even the last coding step did not provide a functional piece of code, it becomes so much more important to write some careful tests for the VirtualContentsFile component. Another requirement of the tests are that this adapter is being tested with both MessageBoard and Message instances. To realize this, we write an a base test and then realize this test for each component. So in the tests folder, create a new file called test_filerepresentation.py and add the following content:
因为甚至在编程的最后一步都没有提供代码的功能片断,所以这使得为 VirtualContentsFile 组件精心编写代码就变得非常重要。另一个测试的需求就是该适配器必须被消息栏和消息实例测试。认识到这点,我们编写一个基本测试,然后为每个组件实现它。所以在测试文件夹里,新建一个名为 test_filerepresentation.py 的文件,并添加以下内容:
代码: 全选
1 import unittest
2 from zope.interface.verify import verifyObject
3 from zope.app import zapi
4 from zope.app.tests import ztapi
5 from zope.app.tests.placelesssetup import PlacelessSetup
6
7 from book.messageboard.interfaces import \
8 IVirtualContentsFile, IPlainText, IMessage, IMessageBoard
9 from book.messageboard.message import \
10 Message, PlainText as MessagePlainText
11 from book.messageboard.messageboard import \
12 MessageBoard, PlainText as MessageBoardPlainText
13 from book.messageboard.filerepresentation import VirtualContentsFile
14
15 class VirtualContentsFileTestBase(PlacelessSetup):
16
17 def _makeFile(self):
18 raise NotImplemented
19
20 def _registerPlainTextAdapter(self):
21 raise NotImplemented
22
23 def setUp(self):
24 PlacelessSetup.setUp(self)
25 self._registerPlainTextAdapter()
26
27 def testContentType(self):
28 file = self._makeFile()
29 self.assertEqual(file.getContentType(), 'text/plain')
30 file.setContentType('text/html')
31 self.assertEqual(file.getContentType(), 'text/plain')
32 self.assertEqual(file.contentType, 'text/plain')
33
34 def testData(self):
35 file = self._makeFile()
36
37 file.setData('Foobar')
38 self.assert_(file.getData().find('Foobar') >= 0)
39 self.assert_(file.data.find('Foobar') >= 0)
40
41 file.edit('Blah', 'text/html')
42 self.assertEqual(file.contentType, 'text/plain')
43 self.assert_(file.data.find('Blah') >= 0)
44
45 def testInterface(self):
46 file = self._makeFile()
47 self.failUnless(IVirtualContentsFile.providedBy(file))
48 self.failUnless(verifyObject(IVirtualContentsFile, file))
49
50
51 class MessageVirtualContentsFileTest?(VirtualContentsFileTestBase,
52 unittest.TestCase):
53
54 def _makeFile(self):
55 return VirtualContentsFile(Message())
56
57 def _registerPlainTextAdapter(self):
58 ztapi.provideAdapter(IMessage, IPlainText, MessagePlainText)
59
60 def testMessageSpecifics(self):
61 file = self._makeFile()
62 self.assertEqual(file.context.title, '')
63 self.assertEqual(file.context.body, '')
64 file.data = 'Title: Hello\n\nWorld'
65 self.assertEqual(file.context.title, 'Hello')
66 self.assertEqual(file.context.body, 'World')
67 file.data = 'World 2'
68 self.assertEqual(file.context.body, 'World 2')
69
70
71 class MessageBoardVirtualContentsFileTest(
72 VirtualContentsFileTestBase, unittest.TestCase):
73
74 def _makeFile(self):
75 return VirtualContentsFile(MessageBoard())
76
77 def _registerPlainTextAdapter(self):
78 ztapi.provideAdapter(IMessageBoard, IPlainText,
79 MessageBoardPlainText)
80
81 def testMessageBoardSpecifics(self):
82 file = self._makeFile()
83 self.assertEqual(file.context.description, '')
84 file.data = 'Title: Hello\n\nWorld'
85 self.assertEqual(file.context.description,
86 'Title: Hello\n\nWorld')
87 file.data = 'World 2'
88 self.assertEqual(file.context.description, 'World 2')
89
90 def test_suite():
91 return unittest.TestSuite((
92 unittest.makeSuite(MessageVirtualContentsFileTest),
93 unittest.makeSuite(MessageBoardVirtualContentsFileTest),
94 ))
95
96 if __name__ == '__main__':
97 unittest.main(defaultTest='test_suite')
* 第 5 行: 因为我们想用适配器,所以我们需要用 PlacelessSetup 提出组件架构。
* Line 7-13: Imports all the relevant interfaces and components for this test. This is always so much, since we have to register the components by hand (instead of ZCML).
* 第 7-13 行: 为本测试导入所有相关的接口和组件。这总是非常多的,因为我们必须手工注册组件(代替 ZCML)
* Line 17-18: The implementation of this method should create a VirtualContentsFile adapter having the correct object as context. Since the context varies, the specific test case class has to take of its implementation.
* 第 17-18 行: 本方法的实现将创建一个有着正确对象作为上下文的 VirtualContentsFile 适配器。因为上下文的变化,具体测试案例类必须take of its implementation.
* Line 20-21: Since there is a specific adapter registration required for each case (board and message), we will have to leave that up to the test case implementation as well.
* 第 20-21 行: 因为每个案例(栏和消息)都要求注册一个相应的适配器,所以我们同样也必须使之适应测试案例实施。
* Line 27-32: We need to make sure that the plain/text setting can never be overwritten.
* 第 27-32 行: 我们需要确保 plain/text 设置永远不被覆盖。
* Line 34-43: We can really just make some marginal tests here, since the storage details really depend on the IPlainText implementation. There will be stronger tests in the specific test cases for the message board and message (see below).
* 第 34-43 行: 这里我们可以真正做点最低限度的测试,因为存储的细节真正有赖于 IPlainText 实现。在为消息栏和消息进行的具体测试中还会有更多的测试(参见下面)。
* Line 45-48: Always make sure that the interface is completely implemented by the component.
* 第 45-48 行: 始终确保接口被组件完全实现。
* Line 51: This is the beginning of a concrete test case that implements the base test. Note that you should only make the concrete implementation a TestCase.
* 第 51 行: 这是实现基本测试的具体测试案例的开始。注意你应该只做 TestCase 的具体实现。
* Line 54-55: Just stick a plain, empty Message instance in the adapter.
* 第 54-55 行: 仅仅在适配器中生成一个纯文本的、空的消息实例。
* Line 60-68: Here we test that the written contents of the virtual file is correctly passed and the right properties are set.
* 第 60-68 行: 在这里我们测试虚拟文件所写内容被正确通过,同时正确的属性被设置。
* Line 71-88: Pretty much the same that was done for the Message test.
* 第 71-88 行: 同消息测试相同。
* Line 90-97: The usual test boilerplate.
* 第 90-97 行: 常用测试模板
You can now run the test and verify the functionality of the new tests.
你现在可以运行测试并确认新测试的功能。
22.2.4 (d) The Configuration
22.2.4 (d) 配置
This section would not be complete without a registration. While we do not need to register the file representation component, we are required to make some security assertions about the object’s methods and properties. I simply copied the following security assertions from the File content component’s configuration.
本部分没有注册就没有完成。然而我们并不需要注册文件表示组件,我们被要求做一些关于对象方法和属性的案例声明。我只是简单地将下列案例声明从文件内容的配置中拷过来。
代码: 全选
1 <content class=".filerepresentation.VirtualContentsFile">
2
3 <implements interface="
4 zope.app.annotation.interfaces.IAttributeAnnotatable" />
5
6 <require
7 permission="book.messageboard.View"
8 interface="zope.app.filerepresentation.interfaces.IReadFile" />
9
10 <require
11 permission="zope.messageboard.Edit"
12 interface="zope.app.filerepresentation.interfaces.IWriteFile"
13 set_schema="zope.app.filerepresentation.interfaces.IReadFile" />
14
15 </content>
* 第 3-4 行: 我们需要虚拟文件是可注解的,so it can reach the DublinCore for dates/times and owner information.
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
22.3 Step III: The IReadDirectory implementation
22.3 步骤 III: IReadDirectory 的实现
After all the preparations are complete, we are finally ready to give our content components, MessageBoard? and Message, a cool filesystem representation.
在所有准备完成后,我们最终准备给我们的内容组件,消息栏和消息,一个酷的文件系统表示。
22.3.1 (a) The Implementation
22.3.1 (a) 实现
The first fact we should notice is that zope.app.filerepresentation.ReadDirectory has already a nice implementation, except for the superfluous SiteManager and the missing contents file. So we simply take this class (subclass it) and merely overwrite keys(), get(key,default=None), and __len__(). All other methods depend on these three. So our code for the ReadDirectory looks something like that (place in filerepresentation.py):
我们应该注意的第一个事实是 zope.app.filerepresentation.ReadDirectory 已经有一个好的实现,除了多余的 SiteManager 和缺少的内容文件。因此我们只需简单的使用该类(它的子类)并且只要重写 keys(), get(key,default=None) 和 __len__() 即可。所有其它方法都有赖于这三个。所以我们为 ReadDirectory 所写的代码看上去如下所示(放在 filerepresentation.py 文件中):
* Line 10-12: When being asked for a list of names available for this container, we get the list of keys plus our virtual contents file.
* 第 10-12 行: 当这容器要求可用名字列表时,我们得到一个附加我们虚拟内容文件的关键字列表。
* Line 14-17: All objects are simply found in the context (MessageBoard or Message) itself, except the contents. When the system asks for the contents, we simply give it a VirtualContentsFile instance that we prepared in the previous section and we do not have to worry about anything, since we know that the system knows how to handle zope.app.file.interfaces.IFile objects.
* 第 14-17 行: 除了内容,所有对象都可以在上下文(MessageBoard 或 Message)中被简单的发现。当系统要求内容时,我们可以简单地给它一个我们在上一节准备好的 VirtualContentsFile 实例。同时我们不用担心任何事,因为我们知道系统知道要如何处理 zope.app.file.interfaces.IFile 对象。
* Line 19-21: Obviously, we pretend to have one more object than we actually have.
* 第 19-21 行: 很明显,我们假定拥有比我们真正拥有的对象还多一个。
Now we are done with our implementation. Let’s write some unit tests to ensure the functionality and then register the filesystem components.
现在我们完成了实现。让我们写一些单元测试以保证功能,然后注册文件系统组件。
22.3.2 (b) The Tests
22.3.2 (b) 测试
For testing the ReadDirectory implementation, we again need to test it with the MessageBoard and Message components. So similar to the previous tests, we have a base test with specific implementations. Also note that it will not be necessary to test all IReadDirectory methods, since they are already tested in the base class tests. So we are just going to test the methods we have overridden:
为了测试 ReadDirectory 的实现,我们还是需要用 MessageBoard 和 Message 组件来对它进行测试。所以同前个测试一样,我们对具体实现做一个基本测试。注意,并不需要测试 IReadDirectory 的所有方法,因为它们已经在基本类测试中测试过了。因此我们打算只测试我们没测试过的方法。
* Line 5-6: Return an instance of the object to be tested.
* Line 8-22: Create an interesting message tree on top of the base. This will allow some more detailed testing.
* Line 24-31: Make sure this contents file and the sub-messages are correctly listed.
* Line 33-40: Now let’s also make sure that the objects we get are the right ones.
* Line 42-46: A simple test for the number of contained items (including the contents).
* Line 49-60: The concrete implementations of the base test. Nothing special.
After you are done writing the tests, do not forget to add the two new TestCases to the TestSuite.
在你完成测试之后,不要忘了添加两个 TestCases 到 TestSuite中去。
22.3.3 (c) The Configuration
22.3.3 (c) 配置
Finally we simply register our new components properly using the following ZCML directives:
最终我们适当地使用下列 ZCML 语句来注册我们的新的组件:
That’s it. You can now restart Zope and test the filesystem representation with an FTP client of your choice. In the following sequence diagram you can see how a request is guided to find its information and return it properly.
就是这样。你现在可以重启 Zope 并用你选的FTP客户端来测试文件系统表示。从下图的顺序图中你可以看到一个请求是如何被指引找到它的信息并以适当方式返回的。

Figure 22.1: Collaboration diagram of the inner working from requesting the contents “file” to receiving the actual data.
图 22.1: 从请求内容“文件”到收到实际数据的内部运行的关联图
22.3 步骤 III: IReadDirectory 的实现
After all the preparations are complete, we are finally ready to give our content components, MessageBoard? and Message, a cool filesystem representation.
在所有准备完成后,我们最终准备给我们的内容组件,消息栏和消息,一个酷的文件系统表示。
22.3.1 (a) The Implementation
22.3.1 (a) 实现
The first fact we should notice is that zope.app.filerepresentation.ReadDirectory has already a nice implementation, except for the superfluous SiteManager and the missing contents file. So we simply take this class (subclass it) and merely overwrite keys(), get(key,default=None), and __len__(). All other methods depend on these three. So our code for the ReadDirectory looks something like that (place in filerepresentation.py):
我们应该注意的第一个事实是 zope.app.filerepresentation.ReadDirectory 已经有一个好的实现,除了多余的 SiteManager 和缺少的内容文件。因此我们只需简单的使用该类(它的子类)并且只要重写 keys(), get(key,default=None) 和 __len__() 即可。所有其它方法都有赖于这三个。所以我们为 ReadDirectory 所写的代码看上去如下所示(放在 filerepresentation.py 文件中):
代码: 全选
1 from zope.app.filerepresentation.interfaces import IReadDirectory
2 from zope.app.folder.filerepresentation import \
3 ReadDirectory as ReadDirectoryBase
4
5 class ReadDirectory(ReadDirectoryBase):
6 """An special implementation of the directory."""
7
8 implements(IReadDirectory)
9
10 def keys(self):
11 keys = self.context.keys()
12 return list(keys) + ['contents']
13
14 def get(self, key, default=None):
15 if key == 'contents':
16 return VirtualContentsFile(self.context)
17 return self.context.get(key, default)
18
19 def __len__(self):
20 l = len(self.context)
21 return l+1
* 第 10-12 行: 当这容器要求可用名字列表时,我们得到一个附加我们虚拟内容文件的关键字列表。
* Line 14-17: All objects are simply found in the context (MessageBoard or Message) itself, except the contents. When the system asks for the contents, we simply give it a VirtualContentsFile instance that we prepared in the previous section and we do not have to worry about anything, since we know that the system knows how to handle zope.app.file.interfaces.IFile objects.
* 第 14-17 行: 除了内容,所有对象都可以在上下文(MessageBoard 或 Message)中被简单的发现。当系统要求内容时,我们可以简单地给它一个我们在上一节准备好的 VirtualContentsFile 实例。同时我们不用担心任何事,因为我们知道系统知道要如何处理 zope.app.file.interfaces.IFile 对象。
* Line 19-21: Obviously, we pretend to have one more object than we actually have.
* 第 19-21 行: 很明显,我们假定拥有比我们真正拥有的对象还多一个。
Now we are done with our implementation. Let’s write some unit tests to ensure the functionality and then register the filesystem components.
现在我们完成了实现。让我们写一些单元测试以保证功能,然后注册文件系统组件。
22.3.2 (b) The Tests
22.3.2 (b) 测试
For testing the ReadDirectory implementation, we again need to test it with the MessageBoard and Message components. So similar to the previous tests, we have a base test with specific implementations. Also note that it will not be necessary to test all IReadDirectory methods, since they are already tested in the base class tests. So we are just going to test the methods we have overridden:
为了测试 ReadDirectory 的实现,我们还是需要用 MessageBoard 和 Message 组件来对它进行测试。所以同前个测试一样,我们对具体实现做一个基本测试。注意,并不需要测试 IReadDirectory 的所有方法,因为它们已经在基本类测试中测试过了。因此我们打算只测试我们没测试过的方法。
代码: 全选
1 from book.messageboard.filerepresentation import ReadDirectory
2
3 class ReadDirectoryTestBase(PlacelessSetup?):
4
5 def _makeDirectoryObject(self):
6 raise NotImplemented
7
8 def _makeTree(self):
9 base = self._makeDirectoryObject()
10 msg1 = Message()
11 msg1.title = 'Message 1'
12 msg1.description = 'This is Message 1.'
13 msg11 = Message()
14 msg11.title = 'Message 1-1'
15 msg11.description = 'This is Message 1-1.'
16 msg2 = Message()
17 msg2.title = 'Message 1'
18 msg2.description = 'This is Message 1.'
19 msg1['msg11'] = msg11
20 base['msg1'] = msg1
21 base['msg2'] = msg2
22 return ReadDirectory(base)
23
24 def testKeys(self):
25 tree = self._makeTree()
26 keys = list(tree.keys())
27 keys.sort()
28 self.assertEqual(keys, ['contents', 'msg1', 'msg2'])
29 keys = list(ReadDirectory(tree['msg1']).keys())
30 keys.sort()
31 self.assertEqual(keys, ['contents', 'msg11'])
32
33 def testGet(self):
34 tree = self._makeTree()
35 self.assertEqual(tree.get('msg1'), tree.context['msg1'])
36 self.assertEqual(tree.get('msg3'), None)
37 default = object()
38 self.assertEqual(tree.get('msg3', default), default)
39 self.assertEqual(tree.get('contents').__class__,
40 VirtualContentsFile)
41
42 def testLen(self):
43 tree = self._makeTree()
44 self.assertEqual(len(tree), 3)
45 self.assertEqual(len(ReadDirectory(tree['msg1'])), 2)
46 self.assertEqual(len(ReadDirectory(tree['msg2'])), 1)
47
48
49 class MessageReadDirectoryTest(ReadDirectoryTestBase,
50 unittest.TestCase):
51
52 def _makeDirectoryObject(self):
53 return Message()
54
55
56 class MessageBoardReadDirectoryTest(ReadDirectoryTestBase,
57 unittest.TestCase):
58
59 def _makeDirectoryObject(self):
60 return MessageBoard()
* Line 8-22: Create an interesting message tree on top of the base. This will allow some more detailed testing.
* Line 24-31: Make sure this contents file and the sub-messages are correctly listed.
* Line 33-40: Now let’s also make sure that the objects we get are the right ones.
* Line 42-46: A simple test for the number of contained items (including the contents).
* Line 49-60: The concrete implementations of the base test. Nothing special.
After you are done writing the tests, do not forget to add the two new TestCases to the TestSuite.
在你完成测试之后,不要忘了添加两个 TestCases 到 TestSuite中去。
22.3.3 (c) The Configuration
22.3.3 (c) 配置
Finally we simply register our new components properly using the following ZCML directives:
最终我们适当地使用下列 ZCML 语句来注册我们的新的组件:
代码: 全选
1 <adapter
2 for=".interfaces.IMessageBoard"
3 provides="zope.app.filerepresentation.interfaces.IReadDirectory"
4 factory=".filerepresentation.ReadDirectory"
5 permission="zope.View"/>
6
7 <adapter
8 for=".interfaces.IMessage"
9 provides="zope.app.filerepresentation.interfaces.IReadDirectory"
10 factory=".filerepresentation.ReadDirectory"
11 permission="zope.View"/>
就是这样。你现在可以重启 Zope 并用你选的FTP客户端来测试文件系统表示。从下图的顺序图中你可以看到一个请求是如何被指引找到它的信息并以适当方式返回的。

Figure 22.1: Collaboration diagram of the inner working from requesting the contents “file” to receiving the actual data.
图 22.1: 从请求内容“文件”到收到实际数据的内部运行的关联图
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
22.4 Step IV: The Icing on the Cake - A special Directory Factory
22.4 步骤 IV: 蛋糕上的冰 - 一个特殊的目录生成器
While you were playing around with the new filesystem support, you might have tried to create a directory to see what happened and it probably just caused a system error, since no adapter was found from IMessage/ IMessageBoard to IDirectoryFactory. Since such a behavior is undesirable, we should create a custom adapter that provides IDirectoryFactory. The IWriteDirectory adapter of any container object then knows how to make use of this factory adapter. So we add the following trivial factory to our filesystem code:
当你使用新的文件系统支持时,你也许会试着创建一个目录看看会发生什么,它也许会引发一个系统错误,因为从 IMessage/IMessageBoard 到 IDirectoryFactory 都没有发现适配器。这样是不合规则的,我们应该创建一个自定义适配器以提供 IDirectoryFactory。任何容器对象的 IWriteDirectory 适配器就知道如何利用这个生成适配器。因此我们在我们的文件系统代码中添加这个生成器:
Registering the factory is just a matter of two adapter directives (one for each content component):
注册该生成器只需两个适配器语句(每个内容组件一个)
Now we have finally made it. The filesystem support should be very smooth and usable at this point. You should be able to view all relevant content, upload new contents data and create new messages. The only problem that might remain is that some FTP clients (like KDE’s FTP support) try to upload the contents file as contents.part and then rename it to contents. Since our filesystem code does not support such a feature, this will cause an error; see exercise 2 for details.
现在我们最终完成它。这时文件系统支持应该是非常平滑和有用的。你应该能够看到所有的相关内容,上传新内容数据和创建新消息。剩下的唯一问题也许是一些 FTP 客户端(象 KDE 的 FTP支持)试图将内容文件作为 contents.part 上传,然后将其重命名为内容。因为我们的文件系统代码还不支持该功能,所以这将会引发一个错误;具体细节请参见练习 2。
22.4 步骤 IV: 蛋糕上的冰 - 一个特殊的目录生成器
While you were playing around with the new filesystem support, you might have tried to create a directory to see what happened and it probably just caused a system error, since no adapter was found from IMessage/ IMessageBoard to IDirectoryFactory. Since such a behavior is undesirable, we should create a custom adapter that provides IDirectoryFactory. The IWriteDirectory adapter of any container object then knows how to make use of this factory adapter. So we add the following trivial factory to our filesystem code:
当你使用新的文件系统支持时,你也许会试着创建一个目录看看会发生什么,它也许会引发一个系统错误,因为从 IMessage/IMessageBoard 到 IDirectoryFactory 都没有发现适配器。这样是不合规则的,我们应该创建一个自定义适配器以提供 IDirectoryFactory。任何容器对象的 IWriteDirectory 适配器就知道如何利用这个生成适配器。因此我们在我们的文件系统代码中添加这个生成器:
代码: 全选
1 from zope.app.filerepresentation.interfaces import IDirectoryFactory
2 from message import Message
3
4 class MessageFactory(object):
5 """A simple message factory for file system representations."""
6
7 implements(IDirectoryFactory)
8
9 def __init__(self, context):
10 self.context = context
11
12 def __call__(self, name):
13 """See IDirectoryFactory interface."""
14 return Message()
注册该生成器只需两个适配器语句(每个内容组件一个)
代码: 全选
1 <adapter
2 for=".interfaces.IMessageBoard"
3 provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
4 factory=".filerepresentation.MessageFactory"
5 permission="zope.View" />
6
7 <adapter
8 for=".interfaces.IMessage"
9 provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
10 factory=".filerepresentation.MessageFactory"
11 permission="zope.View" />
现在我们最终完成它。这时文件系统支持应该是非常平滑和有用的。你应该能够看到所有的相关内容,上传新内容数据和创建新消息。剩下的唯一问题也许是一些 FTP 客户端(象 KDE 的 FTP支持)试图将内容文件作为 contents.part 上传,然后将其重命名为内容。因为我们的文件系统代码还不支持该功能,所以这将会引发一个错误;具体细节请参见练习 2。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
Exercises
练习
1. Currently there is no creation/modification date/time or creator defined for the virtual contents file. This is due to the fact that the respective Dublin Core properties were not set. The virtual file should really receive the same Dublin Core the MessageBoard or Message has. The easiest way would be simply to copy the Dublin Core annotation. Do that and make sure the data and the user are shown correctly.
1. 当前代码中没有创建/修改日期或时间,也没有定义虚拟内容文件的创建者。这归根于各自的 Dublin 核心参数没有被设置这一事实。虚拟文件将
2. In the final remarks of this chapter I stated that the current code will not work very well with some clients, such as Konqueror. Fix the code to support this behavior. The best would be to store the temporary file in an annotation and delete it, once it was moved.
2. 在本章结束时我提及当前在一些客户端下无法很好的工作,如 Konqueror,修复代码以支持这项功能。最好是在一个 annotation 中保存临时文件并在该文件被移动时删除它。
练习
1. Currently there is no creation/modification date/time or creator defined for the virtual contents file. This is due to the fact that the respective Dublin Core properties were not set. The virtual file should really receive the same Dublin Core the MessageBoard or Message has. The easiest way would be simply to copy the Dublin Core annotation. Do that and make sure the data and the user are shown correctly.
1. 当前代码中没有创建/修改日期或时间,也没有定义虚拟内容文件的创建者。这归根于各自的 Dublin 核心参数没有被设置这一事实。虚拟文件将
2. In the final remarks of this chapter I stated that the current code will not work very well with some clients, such as Konqueror. Fix the code to support this behavior. The best would be to store the temporary file in an annotation and delete it, once it was moved.
2. 在本章结束时我提及当前在一些客户端下无法很好的工作,如 Konqueror,修复代码以支持这项功能。最好是在一个 annotation 中保存临时文件并在该文件被移动时删除它。
我心无畏,源自于我心无知。


- firehare
- 帖子: 2625
- 注册时间: 2005-04-10 16:54
- 来自: 温州大学
- 联系:
Chapter 23 Availability via XML-RPC
使用XML-RPC访问
Difficulty 难度
Sprinter
Skills 技能
* Be familiar with the Message Board demo package up to this point.
* 熟悉消息栏演示包
* Feel comfortable with writing ZCML-based configuration.
* 能轻松编写基于 ZCML 的配置
* Some insight in the publisher framework. Optional.
* 对发布框架有一些深入了解。可选
Problem/Task 问题/任务
A very common way to communicate with remote systems is via XML-RPC, a very light-weight protocol on top of HTTP. Zope’s HTTP server comes by default with a XML-RPC component. If we want to allow other systems to communicate with our message board, then we need to declare the methods that will be available via XML-RPC.
一个用来同远程系统通讯的非常通用的方式就是通过 XML-RPC ,它是一个基于 HTTP 的非常轻量级的协议。Zope 的 HTTP 服务缺省支持 XML-RPC 组件。如果我们要其他系统同我们的消息栏通讯的话,那么我们就需要通过 XML-RPC 来声明方法。
Solution 解决方案
You might wonder at this point why we don’t simply map all the existing methods to XML-RPC and be done with it. There are three reasons for not doing this. First, XML-RPC handles only a limited amount of data types. In the following table you see a mapping of Python types to XML-RPC data type elements:
这时你也许想知道我们为什么不简单地将所有存在的方法都映射到 XML-RPC 上并通过它来完成呢。有三个方面的原因不能这么做。首先,XML-RPC 只处理几个有限的数据类型。在下表中你可以看到 Python 类型映射到 XML-RPC 的数据类型元素:
* integer <----> <int>
* float <----> <double>
* string <----> <string>
* list <----> <array>
* dict/struct <----> <dict>
* bool <----> <boolean>
* xmlrpclib.Binary <----> <binary>
* xmlrpclib.DateTime <----> <dateTime>
As you can see, there is no support for None and unicode, which are huge drawbacks for XML-RPC in general.
正如你所见,None 和 unicode 类型并不被支持,一般来说这也是 XML-RPC 最大的缺陷。
Second, another disadvantage is the lack of keyword arguments. XML-RPC only understands regular positional arguments and arguments with default values. Third, since Python returns None by default for methods, all methods that do not have a return value are doomed.
其次,另一个缺陷在于没有关键词变量。 XML-RPC 只能理解规定位置的变量和缺省值的变量。第三,因为 Python 方法缺省返回 None,所有没有返回值的方法都会被掉弃。
Now that we have briefly discussed the shortcomings of XML-RPC, we should look ahead and say that XML-RPC is still a very powerful tool that can be used without much hassle.
现在我们大致地讨论了一下 XML-RPC 的缺点,我们应该向前看,应该说 XML-RPC 无疑是能被使用的非常强大的工具。
使用XML-RPC访问
Difficulty 难度
Sprinter
Skills 技能
* Be familiar with the Message Board demo package up to this point.
* 熟悉消息栏演示包
* Feel comfortable with writing ZCML-based configuration.
* 能轻松编写基于 ZCML 的配置
* Some insight in the publisher framework. Optional.
* 对发布框架有一些深入了解。可选
Problem/Task 问题/任务
A very common way to communicate with remote systems is via XML-RPC, a very light-weight protocol on top of HTTP. Zope’s HTTP server comes by default with a XML-RPC component. If we want to allow other systems to communicate with our message board, then we need to declare the methods that will be available via XML-RPC.
一个用来同远程系统通讯的非常通用的方式就是通过 XML-RPC ,它是一个基于 HTTP 的非常轻量级的协议。Zope 的 HTTP 服务缺省支持 XML-RPC 组件。如果我们要其他系统同我们的消息栏通讯的话,那么我们就需要通过 XML-RPC 来声明方法。
Solution 解决方案
You might wonder at this point why we don’t simply map all the existing methods to XML-RPC and be done with it. There are three reasons for not doing this. First, XML-RPC handles only a limited amount of data types. In the following table you see a mapping of Python types to XML-RPC data type elements:
这时你也许想知道我们为什么不简单地将所有存在的方法都映射到 XML-RPC 上并通过它来完成呢。有三个方面的原因不能这么做。首先,XML-RPC 只处理几个有限的数据类型。在下表中你可以看到 Python 类型映射到 XML-RPC 的数据类型元素:
* integer <----> <int>
* float <----> <double>
* string <----> <string>
* list <----> <array>
* dict/struct <----> <dict>
* bool <----> <boolean>
* xmlrpclib.Binary <----> <binary>
* xmlrpclib.DateTime <----> <dateTime>
As you can see, there is no support for None and unicode, which are huge drawbacks for XML-RPC in general.
正如你所见,None 和 unicode 类型并不被支持,一般来说这也是 XML-RPC 最大的缺陷。
Second, another disadvantage is the lack of keyword arguments. XML-RPC only understands regular positional arguments and arguments with default values. Third, since Python returns None by default for methods, all methods that do not have a return value are doomed.
其次,另一个缺陷在于没有关键词变量。 XML-RPC 只能理解规定位置的变量和缺省值的变量。第三,因为 Python 方法缺省返回 None,所有没有返回值的方法都会被掉弃。
Now that we have briefly discussed the shortcomings of XML-RPC, we should look ahead and say that XML-RPC is still a very powerful tool that can be used without much hassle.
现在我们大致地讨论了一下 XML-RPC 的缺点,我们应该向前看,应该说 XML-RPC 无疑是能被使用的非常强大的工具。
我心无畏,源自于我心无知。

