Zope3Book Part IV 草译帖

软件和网站开发以及相关技术探讨
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#31

帖子 firehare » 2005-12-13 13:55

23.1 Step I: Creating “Methods” XML-RPC Presentation Components
23.1 步骤 I: 创建 XML-RPC 表示组件方法

Obviously we have to create a view component for both content objects, MessageBoard and Message. However, both share the functionality that they can contain and manage sub-messages, so that it is desired to factor out this functionality into a common base class, MessageContainerMethods. Since we want to keep the XML-RPC code separate, create a new module called xmlrpc.py in the messageboard directory and add the following content:
显然我们必须为内容对象 MessageBoard 和 Message 创建视图组件。然而,由于两者共有的功能是它们可以包含和管理子消息,因此,要将该功能分离出来放在一个公共的基类 MessageContainerMethods 中。因为我们想保持 XML-RPC 代码的独立性,因此在消息栏文件夹中新建一个名为 xmlrpc.py 的模块,并添加下列内容:

代码: 全选

1  from zope.event import notify
2  from zope.app.publisher.xmlrpc import MethodPublisher
3  
4  from zope.app.event.objectevent import ObjectCreatedEvent
5  
6  from book.messageboard.message import Message
7  
8  class MessageContainerMethods(MethodPublisher):
9  
10    def getMessageNames(self):
11        """Get a list of all messages."""
12        return list(self.context.keys())
13  
14    def addMessage(self, name, title, body):
15        """Add a message."""
16        msg = Message()
17        msg.title = title
18        msg.body = body
19        notify(ObjectCreatedEvent?(msg))
20        self.context[name]? = msg
21        return name
22  
23    def deleteMessage(self, name):
24        """Delete a message. Return True, if successful."""
25        self.context.__delitem__(name)
26        return True
* Line 8: Make the class a MethodPublisher, which is similar to a BrowserView and its constructor will expect a context object and a XMLRPCRequest instance.
* 第 8 行: 生成一个 MethodPublisher 类,它与 BrowserView 相似且它的构造函数需要一个上下文对象和一个 XMLRPCRequest 实例。
* Line 10-12: Return a list of all message names. But remember, we implemented the containment with BTrees (see first chapter of this part), so that keys() will not return a list or tuple as a result, but a btree-items object. Therefore we need to implicitly cast the result to become a list, so that XML-RPC is able understand it.
* 第 10-12 行: 返回所有消息名列表。但请记住,我们实现与 BTrees 的包容(参见本部分的第一章),因此 key() 不能返回列表或元组作为结果,除了 btree-item 对象。因此我们需要将结果变成一个列表,以便 XML-RPC 能够明白它。
* Line 19: Since we want to play nice with the system, let it know that we created a new content object.
* 第 19 行: 因为我们想让系统运行良好,想让它知道我们已经新建了一个内容对象。
* Line 21, 26: Make sure we return something, so that XML-RPC will not be upset with us. As mentioned before, if no return value is specifed, None is implicitly returned, which XML-RPC does nto understand.
* 第 21, 26 行: 保确我们能返回数据,以便 XML-RPC 不会混乱。如前面所讲,如果没有指定返回值则 None 会被缺省返回,而 XML-RPC 是不能明白的。

The two actual views for our content objects are now also implementing accessor and mutator methods for their properties. So here are the two views:
内容对象的两个真实视图现在也实现了 accessor 和 mutator 方法

代码: 全选

1  from zope.app.event.objectevent import ObjectModifiedEvent
2  
3  class MessageMethods(MessageContainerMethods):
4  
5      def getTitle(self):
6          return self.context.title
7  
8      def setTitle(self, title):
9          self.context.title = title
10          notify(ObjectModifiedEvent(self.context))
11          return True
12  
13      def getBody(self):
14          return self.context.body
15  
16      def setBody(self, body):
17          self.context.body = body
18          notify(ObjectModifiedEvent(self.context))
19          return True
20  
21  
22  class MessageBoardMethods(MessageContainerMethods):
23  
24      def getDescription(self):
25          return self.context.description
26  
27      def setDescription(self, description):
28          self.context.description = description
29          notify(ObjectModifiedEvent(self.context))
30          return True
* Line 10, 18 & 29: When modifying a message board or message, we have to explicitly send out a modification notification event. We did not have to deal with this until now, since for browser forms these events are created automatically by the forms machinery.
* 第 10, 18 & 29 行: 当修改消息栏或消息时,我们必须明确发出修改通知事件。直至目前我们也没必要处理这些,因为这些事件被浏览器表单的事件机制自动创建了。
* Line 11, 19 & 30: Again, we need to make sure we do not just return None from a method.
* 第 11, 19 & 30 行: 再一次,我们需要确保我们不能从方法中返回 None。

That’s already everything from a coding point of perspective. But before we hook up the code in the component architecture, we need to do some testing.
从编程的角度一切准备就绪。但在我们将代码放入组件架构之前,我们还需要做一些测试。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#32

帖子 firehare » 2005-12-13 21:11

23.2 Step II: Testing
23.2 步骤 II: 测试

Of course, the testing code is multiples more complex than the actual implementation, since we have to bring up the component architecture and the event service manually. Similar to the implementation, we can again separate the container-related tests in a base class (the code should be located in tests/test_xmlrpc.py):
当然,测试代码相对实际实现来说是成倍复杂的,因为我们不得不手工提出组件架构和事件服务。与实现相同,我们可以在基类中分离出容器相关的测试(代码被放置在 tests/test_xmlrpc.py 中):

代码: 全选

1  from zope.app import zapi
2  from zope.app.tests.placelesssetup import PlacelessSetup
3  
4  class MessageContainerTest(PlacelessSetup):
5  
6      def _makeMethodObject(self):
7          return NotImplemented
8  
9      def _makeTree(self):
10          methods = self._makeMethodObject()
11          msg1 = Message()
12          msg1.title = 'Message 1'
13          msg1.description = 'This is Message 1.'
14          msg2 = Message()
15          msg2.title = 'Message 1'
16          msg2.description = 'This is Message 1.'
17          methods.context['msg1'] = msg1
18          methods.context['msg2'] = msg2
19          return methods
20  
21      def test_getMessageNames(self):
22          methods = self._makeTree()
23          self.assert_(isinstance(methods.getMessageNames(),  list))
24          self.assertEqual(list(methods.context.keys()),
25                           methods.getMessageNames())
26  
27      def test_addMessage(self):
28          methods = self._makeTree()
29          self.assertEqual(methods.addMessage('msg3',  'M3', 'MB3'),
30                           'msg3')
31          self.assertEqual(methods.context['msg3'].title,  'M3')
32          self.assertEqual(methods.context['msg3'].body,  'MB3')
33  
34      def test_deleteMessage(self):
35          methods = self._makeTree()
36          self.assertEqual(methods.deleteMessage('msg2'),  True)
37          self.assertEqual(list(methods.context.keys()),  ['msg1'])
* Line 6-7: The implementation of this method should return a valid XML-RPC method publisher.
* 第 6-7 行: 该方法的实现将返回一个合法的 XML-RPC 方法发布
* Line 9-19: Create an interesting message tree, so that we have something to test with.
* 第 9-19 行: 创建一个兴趣消息树,以便我们可以用它来进行测试。
* Line 21-25: Make sure the names list is converted to a Python list and all elements are contained in it.
* 第 21-25 行: 确保名称列表被转换成 Python 列表且所有元素都被包含在其中。
* Line 27-32: This method obviously tests the adding capability. We just try to make sure that the correct attributes are assigned to the message.
* 第 27-32 行: 显然该方法测试新加的功能。我们试图确保正确的属性被指定到消息。
* Line 34-37: Simply checks that a message is really deleted.
* 第 34-37 行: 简单检查消息确实被删除了。

Now that we have the base class, we can implement the real test cases and add tests for the property accessors and mutators:
现在我们有了基类,我们可以实现真实测试代码并为 accessors 和 mutators 的属性添加测试。

代码: 全选

1  import unittest
2  
3  from zope.publisher.xmlrpc import TestRequest
4  
5  from book.messageboard.message import Message
6  from book.messageboard.messageboard import MessageBoard
7  from book.messageboard.xmlrpc import MessageBoardMethods, MessageMethods
8  
9  class MessageBoardMethodsTest(MessageContainerTest,  unittest.TestCase):
10  
11      def _makeMethodObject(self):
12          return MessageBoardMethods(MessageBoard(), TestRequest())
13  
14      def test_description(self):
15          methods = self._makeTree()
16          self.assertEqual(methods.getDescription(),  '')
17          self.assertEqual(methods.setDescription('Board  1') , True)
18          self.assertEqual(methods.getDescription(),  'Board 1')
19  
20  class MessageMethodsTest(MessageContainerTest, unittest.TestCase):
21  
22      def _makeMethodObject(self):
23          return MessageMethods(Message(), TestRequest())
24  
25      def test_title(self):
26          methods = self._makeTree()
27          self.assertEqual(methods.getTitle(), '')
28          self.assertEqual(methods.setTitle('Message  1') , True)
29          self.assertEqual(methods.getTitle(), 'Message 1')
30  
31      def test_body(self):
32          methods = self._makeTree()
33          self.assertEqual(methods.getBody(), '')
34          self.assertEqual(methods.setBody('Body 1') , True)
35          self.assertEqual(methods.getBody(), 'Body 1')
36  
37  
38  def test_suite():
39      return unittest.TestSuite((
40          unittest.makeSuite(MessageBoardMethodsTest),
41          unittest.makeSuite(MessageMethodsTest),
42          ))
43  
44  if __name__ == '__main__':
45      unittest.main(defaultTest='test_suite')
* Line 11-12 & 22-23: Create a XML-RPC method publisher for the message board and the message, respectively. To do that we need an object instance (no problem) and an XML-RPC request. Luckily, like for the browser publisher, the XML-RPC publisher provides a TestRequest which was written for its easy usage in unit tests like these.
* 第 11-12 & 22-23 行: 为消息栏和消息各创建一个 XML-RPC 方法发布,要做到这点我们需要一个对象实例(没问题)和一个 XML-RPC 请求。幸运的是,与浏览器发布相似,XML-RPC发布也提供了一个为在单元测试中能方便使用的 TestRequest。
* Line 38-45: And again the usual unit test boiler plate.
* 第 38-45 行: And again the usual unit test boiler plate.

The rest of the code is not so interesting and should be obvious to the reader. Please run these tests now and make sure that everything passes.
代码的剩余部分显然不那么会引起读者的兴趣。现在请运行这些测试并确保通过。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#33

帖子 firehare » 2005-12-13 21:29

23.3 Step III: Configuring the new Views
23.3 步骤 III: 配置新的视图

To register the XML-RPC views, you need to import the xmlrpc namespace into your main configuration file using
为了注册 XML-RPC 视图,你需要将 xmlrpc 名称空间导入到你正在使用的主要配置文件

代码: 全选

1  xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
in the zopeConfigure element. Now you simply add the following two directives to the configuration:
在 zopeConfigure 元素中。现在你可以简单地将下列两个语句添加到配置中:

代码: 全选

1  <xmlrpc:view
2      for=".interfaces.IMessageBoard"
3      permission="book.messageboard.Edit"
4      methods="getMessageNames addMessage deleteMessage
5               getDescription setDescription"
6      class=".xmlrpc.MessageBoardMethods" />
7  
8  <xmlrpc:view
9      for=".interfaces.IMessage"
10      permission="book.messageboard.Edit"
11      methods="getMessageNames addMessage deleteMessage
12               getTitle setTitle getBody setBody"
13      class=".xmlrpc.MessageMethods" />
* Line 2: This view is for IMessageBoard objects.
* 第 2 行: 该视图是针对 IMessageBoard 对象的
* Line 3: XML-RPC views require the book.messageboard.Edit permission, which means that someone has to authenticate before using these methods.
* 第 3 行: XML-RPC 视图要求 book.messageboard.Edit 权限,这就意味着人们在使用这些方法之间需要授权。
* Line 4-5: This is the list of methods that will be available as XML-RPC methods on the messageboard.
* 第 4-5 行: 这是个方法列表,它们在 messageboard 中作为 XML-RPC 方法是可用的。
* Line 6: The method publisher class is .xmlrpc.MessageBoardMethods, which provides the previously defined methods.
* 第 6 行: 方法发布类是 .xmlrpc.MessageBoardMethods,它提供先前定义的方法
* Line 8-13: Repeat the previous precedure for IMessage components.
* 第 8-13 行: 为 IMessage 组件重复前面的步骤

Now you can restart Zope 3 and give your XML-RPC methods a run. But oh no, how do we test this? Certainly a browser will not get us very far.
现在你可以重启 Zope 3 了并运行一下你的 XML-RPC 方法。但是哦不,我们怎么来测试呢?浏览器肯定不会给我们太多帮助。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#34

帖子 firehare » 2005-12-14 21:09

23.4 Step IV: Testing the Features in Action
23.4 步骤 IV: 在运行中测试特性

Python has a really nice XML-RPC module that can be used as XML-RPC client and also provides some extra help with basic authentication. In order to save the reader some typing, I have provided a module called xmlrpc_client.py in the messageboard package which you can call from the command line:
Python 有一个真正好用的 XML-RPC 模块,它可以被为 XML-RPC 客户端来使用,也可以提供一些有着基本授权的额外帮助。为了能保存读者的一些输入,我在消息栏包中提供了一个名为 xmlrpc_client.py 的模块,你可以从命令行中调用:

代码: 全选

1  # ./xmlrpc_client.py
You will be asked for the URL to your messageboard object, your username and password. Once this is done, you are presented with a normal Python prompt, except that there is a local variable called board available, which represents your XML-RPC connection. You can now use the available methods to manipulate the board at your heart’s content.
你将被询问到你消息栏对象的 URL,你的用户名和密码。当这些完成后,你将得到一个正常的 Python 提示符,只是有个叫 board 的本地变量可用,用来表示你的 XML-RPC 连接。你现在可以使用可用的方法在你的中心内容中操作 board 。

代码: 全选

1  # ./messageboard/xmlrpc_client.py
2  Message Board URL [http://localhost:8080/board/]:
3  Username: srichter
4  Password: ........
5  The message board is available as 'board'
6  Python 2.3 (#2, Aug 31 2003, 17:27:29)
7  [GCC 3.3.1 (Mandrake Linux 9.2 3.3.1-1mdk)] on linux2
8  Type "help", "copyright", "credits" or "license" for more information.
9  (InteractiveConsole)
10  >>> board.getDescription()
11  'First Message Board'
12  >>> board.getMessageNames()
13  ['msg1', 'msg2']
14  >>> board.msg1.getMessageNames()
15  ['msg11']
16  >>> board.msg1.msg11.getTitle()
17  'Message 1-1'
18  >>> board.msg1.msg11.getBody()
19  'This is the first response to Message 1.'
20  >>> board.msg1.addMessage('msg12', 'Message 1-2', 'Second response!')
21  'msg12'
22  >>> board.msg1.getMessageNames()
23  ['msg11', 'msg12']
24  >>> board.msg1.msg12.getTitle()
25  'Message 1-2'
26  >>> board.msg1.deleteMessage('msg12')
27  True
28  >>> board.msg1.getMessageNames()
29  ['msg11']
30  >>>

Figure 23.1: A sample XML-RPC session using the provided client.
图 23.1: 一个使用客户端的 XML-RPC 会话示例
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#35

帖子 firehare » 2005-12-14 21:21

Exercises
练习

1. We only made a very limited amount of the message board’s functionality available via XML-RPC. Write a new XML-RPC method publisher to
1. 我们通过 XML-RPC 实现了非常有限的消息栏功能。编写一个新的 XML-RPC 方法发布以实现
1. manage mail subscriptions for messages.
1. 管理消息的邮件描述
2. manage the workflows of messages.
2. 管理消息的工作流
2. Due to the simple implementation of this chapter, message attachments are treated like messages when displaying message names. Improve the implementation and the API to handle attachments properly. This includes writing a method publisher for IFile components.
2.由于本章的简单实现,当显示消息名时对待消息附件就象消息一样。改进实现和 API 以便更适当的处理附件。这包括为 IFile 组件写一个方法发布。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#36

帖子 firehare » 2005-12-15 13:37

Chapter 24 Developing new Skins
第 24 章 开发新的皮肤

Difficulty 难度

Newcomer 新手


Skills 技能

* Be familiar with the Message Board Demo package up to this point.
* 熟悉消息栏演示包
* Feel comfortable with writing ZCML-based view configuration. Optional.
* 能轻松编写基于 ZCML 的视图配置。可选


Problem/Task 问题/任务

Until now we have only enhanced the messageboard by features, but have not done much to improve the user interface. In fact, we are still using the ZMI to do all our message board management, which is totally inappropriate to the end user. Therefore, this chapter will concentrate on developing a skin specifically designed for the message board that implements a user interface as it is typically seen in real world message board applications. While this package has little to do with Python development, I feel that it is a good final task for our two Content Component parts.
直到现在我们也只是在提高消息栏的性能,而对它的用户界面并没做太多的改善。实际上,我们一直用 ZMI 来进行所有的消息栏管理,这对最终用户来说是完全不适合的。因此本章将专注于开发一个皮肤以便为消息栏实现用户界面,就象我们所看到的现实中的消息栏应用程序。尽管该包需要一点 Python 开发,但我认为这对于我们两个内容组件部分都可以说是个好的任务。

Solution 解决方案

Skins (the equivalence of CMF Skins in Zope 2) are a method to implement a custom look and feel to existing views. This is very similar to HTML and CSS (Cascading Style Sheets), where the HTML code are equivalent to views (page templates, view classes) and the style sheets (CSS) are the skin over the HTML structural elements. Skins however, have another abstraction layer beneath.
皮肤(等同于 Zope 2 中的 CMF)是一个用来实现对一个已存在的视图自定义界面的方法。这非常类似于 HTML 和 CSS(层级式样式表),对于 HTML 代码来说视图(页面模板,视图类)和样式表(CSS)就相当于在 HTML 结构元素上的皮肤。然而皮肤之下是有另一个抽象层的。

A skin is really just a stack of layers. Each layer can contain any amount of views and resources. This allows particular views and resources to be overridden. For example, our style sheet (CSS) might have been defined in the default layer. However, this style sheet is really simplistic and inappropriate for our needs. We can then create a new layer board and place a new style sheet in it. Once that is done, we define a skin that places the board layer after the default layer, and all the new style definitions will be adopted.
皮肤实际上是个层的堆栈。每一个层都包含了一些视图和资源。这样就可以让特定的视图和资源失效。举个例子,我们的样式表(CSS)也许定义在缺省层。然而,该样式表对于我们的需要来讲实在是太简单、太不适合了。所以我们可以创建一个新的层 board 并将一个新的样式表放在那里。当完成之后,我们定义一介在在缺省层后 board 层的皮肤,然后所有新的样式定义就会生效。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#37

帖子 firehare » 2005-12-15 13:45

24.1 Step I: Preparation
24.1 步骤 I: 准备

Before we can create our new skin, we need to make some preparations. In order not to confuse the original views and resources with the new ones, we create a package called skin in the messageboard/browser directory; do not forget to make a __init__.py file. Then create an empty configure.zcml file:
在我们创建新皮肤之前,我们需要做些准备。为了不同原来的视图和资源混淆,我们在 messageboard/browser 目录中创建一个名为 skin 的包;不要忘记生成 __init__.py 文件。然后创建一个空的 configure.zcml 文件:

代码: 全选

1  <configure
2      xmlns="http://namespaces.zope.org/browser">
3  
4  </configure>
Now hook up this configuration file from the browser’s configure.zcml using:
现在用下面的语句将该配置导入到浏览器的 configure.zcml 中:

代码: 全选

1  <include package=".skin" /> 
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#38

帖子 firehare » 2005-12-15 13:56

24.2 Step II: Creating a New Skin
24.2 步骤 II: 创建一个新的皮肤

Creating a new skin is very easy and can be accomplished purely with ZCML configuration directives. The browser namespace has a special directive called skin that let’s us do this, so add the following directive to the configuration file of the skin package:
创建一个新的皮肤是非常容易的,用 ZCML 配置语句就可以完全实现。浏览器名字空间有个叫 skin 语句就可以让我们完成,因此添加下列语句到 skin 包的配置文件中:

代码: 全选

1  <layer name="board"/>
2  
3  <skin name="board" layers="board rotterdam default" />
The first directive creates a new layer, in which we will place all the new templates and which will make the skin unique. The second directive creates a skin named board that consists of a three element layer stack. The lowest layer is default which is overridden by rotterdam, which is overridden by board. Every browser presentation directive supports a layer attribute, which defines the layer in which a view or resource is placed. If no layer was specified, the presentation component is placed in the “default” skin.
第一个语句创建一个新的层,在那里我们将放入所有的新模板并生成唯一的皮肤。第二个语句创建一个名为 board 的皮肤,由三个元素层堆栈组成。最底的层是缺省层,被 rotterdam 覆盖,而它又被 board 覆盖。

You might also wonder why the rotterdam layer is placed here. The rotterdam layer contains some nice definitions, like the favicon and some other view code that is useful for us here as well. Other than that, we will not be using this layer actively.
你也许奇怪为什么 rotterdam 层会放在这里。rotterdam 层包含一些比较好的定义,象 favicon 和其他一些对我们也很有用的视图代码。除此之外,我们不会经常使用这个层。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#39

帖子 firehare » 2005-12-15 14:24

24.3 Step III: Customizing the Base Templates
24.3 步骤 III: 自定义基本模板

The first task is always to override the skin_macros and the dialog_macros. Usually the skin macros are defined by a file called template.pt and the dialog macros in dialog_macros.pt. Our new template.pt file might look something like this:
第一个任务总是覆盖 skin_macros 和 dialog_macros。通常皮肤宏定义在 template.pt 而对话宏则在 dialog_macros.pt 中被定义。我们新建的 template.pt 文件如下所示:

代码: 全选

1  <metal:block define-macro="page">
2    <metal:block define-slot="doctype">
3      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
4          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
5    </metal:block>
6  
7  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
8  
9    <head>
10      <title metal:define-slot="title">Message Board for Zope 3</title>
11  
12      <style type="text/css" media="all"
13         tal:content=
14         "string: @import url(${context/++resource++board.css});">
15        @import url(board.css);
16      </style>
17  
18      <meta http-equiv="Content-Type"
19          content="text/html;charset=utf-8" />
20  
21      <link rel="icon" type="image/png"
22            tal:attributes="href context/++resource++favicon.png" />
23    </head>
24  
25    <body>
26  
27      <div id="board_header" i18n:domain="messageboard">
28        <img id="board_logo"
29            tal:attributes="src context/++resource++logo.png" />
30        <div id="board_greeting">&nbsp;
31           <span i18n:translate="">Zope 3 Message Board</span>
32        </div>
33      </div>
34  
35      <div id="workspace">
36  
37        <div metal:define-slot="message" id="message"></div>
38  
39        <div id="content">
40          <metal:block define-slot="body">
41         This is the content.
42          </metal:block>
43        </div>
44  
45      </div>
46  
47      <div id="footer">
48  
49        <div id="actions">
50          <metal:block define-slot="actions" />
51        </div>
52        <div id="credits" i18n:domain="messageboard">
53          Powered by Zope 3.<br>
54          Stephan Richter in 2003
55        </div>
56      </div>
57  
58    </body>
59  
60  </html>
61  
62  </metal:block>
* Line 12-16: Instead of the standard zope3.css we going to use a new board.css style sheet.
* 第 12-16 行: 我们打算用新的 board.css 样式表来代替标准的 zope3.css
* Line 21-22: This favicon is provided by the rotterdam skin.
* 第 21-22 行: 该 favicon 被 rotterdam 皮肤提供。
* Line 27-33: Do you see how simple the header is A couple of styles, a logo, and a simple title will do for us.
* 第 27-33 行: 你可以看到头是多么简单的一对类型,对我们来说只是一个 LOGO 和一个简单的标题
* Line 47-56: The footer consists simply of a placeholder (slot) where we can later drop actions into and a tiny credits section.
* 第 47-56 行: 页脚简音地由一个我们以后可以将 action 丢弃其中的 placeholder (slot) 和一个小小的版权声明段。

There is not really much to this template. Notice how much simpler this is than for example the Rotterdam equivalent that can be found at src/zope/app/rotterdam. Similarly simple is the dialog_macros.pt page template, which you can find in the example code.

In the above template we referred to two new resources, the logo.png and the board.css. Both are configured as follows:
在上面的模板中我们指定了两个新的资源,logo.png 和 board.css。它们两个的配置如下:

代码: 全选

1  <resource
2      name="board.css" file="board.css" layer="board" />
3  
4  <resource
5      name="logo.png" file="logo.png" layer="board" />
Note how the resource directive has a layer attribute to specify the layer. The initial CSS file ( board.css) looks like this:
注意资源语句是如何将设置指定层的层属性的。最始的 CSS 文件( board.css )如下所示:

代码: 全选

1  body {
2      font-family: Verdana, Arial, Helvetica, sans-serif;
3      background: white;
4      color: black;
5      margin: 0;
6      padding: 0pt;
7  }
8  
9  h1, h2, h3, h4, h5, h6 {
10      font-weight: bold;
11      color: black;
12  }
13  
14  /* Different headers are used for  the same purpose,
15     so make them all equal.  */
16  
17  h1, h2, h3 {
18      font-size: 20pt;
19      margin-top: 0px;
20      margin-bottom: .8em;
21      border-bottom: solid 1px #1E5ADB;
22  }
23  
24  ...
25  
26  /* Header Stuff */
27  
28  #board_header {
29      background: #EEEEEE;
30      border: solid 1px #AAAAAA;
31      padding: 3pt;
32      clear: both;
33  }
34  
35  ...
36  
37  /* Footer stuff */
38  
39  #footer {
40      background: #EEEEEE;
41      border: solid 1px #AAAAAA;
42      padding: 0.5em;
43      font-size: 85%;
44  }
45  
46  ...
For the full style sheet, see the example code. The templates are then registered for the board layer as follows:
完整的样式表请参见演示代码。然后注册 board 层的模板,如下所示:

代码: 全选

1  <page
2      for="*"
3      name="skin_macros"
4      permission="zope.View"
5      layer="board"
6      template="template.pt" />
7  
8  <page
9      for="*"
10      name="dialog_macros"
11      permission="zope.View"
12      layer="board"
13      template="dialog_macros.pt" />
* Line 2 & 9: The star means that this page is available for all objects.
* 第 2 & 9 行: 星号的意思就是该页对所有对象有效。
* Line 5 & 12: The additional layer attribute is enough to specify the layer.
* 第 5 & 12 行: 额外的层属性对于指定层是足够的。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#40

帖子 firehare » 2005-12-15 19:39

24.4 Step IV: Adding a Message Board Intro Screen
24.4 步骤 IV: 添加信息栏的介绍屏幕

The simplest view of the message board is some sort of introduction screen for the message board, since it is just a simple page template. The template looks like this:
消息栏的最简单视图是对消息栏的介绍屏幕,因为它只是一个简单的页面模板。该模板如下所示:

代码: 全选

1  <html metal:use-macro="views/standard_macros/page">
2    <body>
3      <div id="content" metal:fill-slot="body"
4          i18n:domain="messageboard">
5  
6        <h2 i18n:translate="">Welcome to the Message Board</h2>
7  
8        <p class="board_description" tal:content="context/description">
9          Description of the Message Board goes here.
10        </p>
11  
12        <div id="login_link">
13          <a href="./posts.html">Click here to enter the board.</a>
14        </div>
15  
16      </div>
17    </body>
18  </html>
Place the template in the skin directory having the name board_intro.pt.
将模板放在 skin 目录中并命名为 board_intro.pt。

The view must be registered for the layer using:
层的视图必须被注册,用下列代码:

代码: 全选

1  <page
2      for="book.messageboard.interfaces.IMessageBoard?"
3      name="intro.html"
4      permission="book.messageboard.View"
5      layer="board"
6      template="board_intro.pt" />
When you restart Zope 3 now, you should be able to reach this view using http://localhost:8080/++skin++board/board/@@intro.html assuming that the MessageBoard instance is called board and lives in the root folder.
现在在你重启 Zope 3 后,你就可以使用 http://localhost:8080/++skin++board/board/@@intro.html 来访问该视图了。在这里假设消息栏实例名叫 board 且在根文件夹中。
图片
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#41

帖子 firehare » 2005-12-15 20:40

24.5 Step V: Viewing a List of all Message Board Posts
24.5 步骤 V: 查看所有消息栏帖子列表

Once the user enters the message board, s/he should be represented with a list of all top-level messages. In the actions section, the user should have an option to add a new message to the board and administrators can go to a review screen to publish messages that are still pending. You can find the source of the template in board_posts.pt in the skin directory of the message board example code.
一旦用户进入消息栏,她/他将看到所有顶层消息。在 actions 部分,用户可以选择新添一个消息到消息栏中,并且管理者也可以到复审屏幕来发布待审消息。你可以在消息栏演示代码中的 skin 文件夹中的 board_posts.pt 文件中找到模板的源代码。

The view is configured with the following instruction:
视图配置指令如下:

代码: 全选

1  <page
2      for="book.messageboard.interfaces.IMessageBoard"
3      name="posts.html"
4      permission="book.messageboard.View"
5      layer="board"
6      class=".views.Posts"
7      template="board_posts.pt" />
As you can see, the page uses a view class which is located in views.py. The template board_posts.pt uses a method of the view that returns a list containing a dictionary with detailed information for each message. However, this does nothing new. We have functionality like this in the existing views, so we will not explain it here.
如你所见,页面使用在 views.py 中的视图类。模板 board_posts.pt 使用视图的方法返回一个列表,它包含一个有着每个消息详细信息的字典。然而,这并没有什么新的东西。我们在已有视图中已经有象这样的功能,所以我们将不再在这里说明它了。

You should now be able to see a list of posts of the messageboard (but only those who are in the “published” workflow state).
现在你应该可以看到一个消息栏帖子的列表了(但只是那些在 “published” 状态中的)
图片
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#42

帖子 firehare » 2005-12-15 21:38

24.6 Step VI: Adding a Post to the Message Board
24.6 步骤 VI: 添加一个帖子到消息栏中

When looking at the posts screen, you might already have clicked already on the “Create New Post” link and added a new message. You will have noticed that the add form did not bring you back to the posts overview but some management screen. To change that, we have to create a customized add form screen that specifies the return URL. The implementation in the views.py module looks as like that:
当查看帖子屏幕时,你也许已经点击过 “Create New Post” 链接并新添了一个消息。你将注意到添加表单并没有带你返回帖子浏览屏幕而是一些管理屏幕。为了改变它,我们必须创建一个自定义的添加表单以指定URL。在 views.py 模块中的实现如下所示:

代码: 全选

1  class AddMessage:
2      """Add-Form supporting class."""
3  
4      def nextURL(self):
5          return ../@@posts.html
This was straightforward.
这很明确。

By default you can enter a message id for the message. This is undesirable for our user-friendly skin. You may have noticed that you can leave the field empty, in which case the message id will be something like “Message” or “Message-2”. That’s because there is a mechanism that creates these generic names. The mechanism is used by the add form, if no name was specified. You can tell the add form to always use this mechanism for the MessageBoard and Message components by having them implement the IContainerNamesContainer interface. You can do this by adding the following directive to the zope:content directive of both objects in the main configure.zcml file:
缺省状态下你可以输入一个消息ID来找到消息。但这对我们用户友好的皮肤来说是不受欢迎的。你也许注意到你可以将该处留空,这样消息ID就会象 “Message” 或 “Message-2” 一样。这是因为有个创建这样通用名字的机制。如果没有指定名字的时该机制就会被添加表单使用。你可以让添加表单在 MessageBoard 和 Message 组件中总是使用该机制,且由它们实现 IContainerNamesContainer 接口。你可以在主 configure.zcml 文件中的两个对象的 zope:content 语句中添加下列语句来做到这一点:

代码: 全选

1  <implements
2      interface="zope.app.container.interfaces.IContainerNamesContainer"
3      />
Finally we have to configure the new add form using
最后我们必须配置新的添加表单,用:

代码: 全选

1  <addform
2      label="Add Message"
3      name="AddMessage.html"
4      schema="book.messageboard.interfaces.IMessage?"
5      content_factory="book.messageboard.message.Message"
6      permission="book.messageboard.Add"
7      class=".views.AddMessage?"
8      layer="board"/>
This is not very hard either, right? After you restarted Zope, you can now admire the correct functionality of the add form.
这并不十分困难,对吧? 在你重启 Zope 之后,你现在可以为添加表单修正的性能感到惊讶。
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#43

帖子 firehare » 2005-12-16 8:26

24.7 Step VII: Reviewing “pending” Messages
24.7 步骤 VII: 复审 “待审” 消息

Did you try to add a message? But where did it go? Well, remember that only published messages are visible.
你想添加消息?但它到哪去了?哦,请记住只有发布了的消息才是可见。

While we have a message review screen for the management interface, it is not very usable. So we develop a much more simplified functionality in the board skin that can only publish messages. The following template displays all the pending messages and provides a checkbox for the moderator to select whether this message is about to be published ( board_review.pt):
尽管我们在管理接口上有一个消息复审屏幕,但它并不很有用。因此我们在 board 皮肤中开发一个非常简单的只能发布信息的功能。下面的模块将显示所有的待审消息并提供一个检查框以便让版主选择该消息是否要发布( board_review.pt ):

代码: 全选

1  <html metal:use-macro="views/standard_macros/page">
2    <body>
3      <div metal:fill-slot="body" i18n:domain="messageboard">
4  
5        <h2 i18n:translate="">Review Pending Posts</h2>
6  
7        <form action="updateStatus.html" method="POST">
8  
9          <div id="message_line"
10              tal:repeat="post view/getPendingMessagesInfo">
11            <input type="checkbox" name="messages" value=""
12              tal:attributes="value post/path" />
13            <a href="" tal:attributes="href post/url"
14              tal:content="post/title">Message Title</a>
15            <div style="font-size: 70%">
16              (Posted by <b tal:content="post/creator">Creator</b>
17              on <b tal:replace="post/created">2003/01/01</b>)
18            </div>
19          </div>
20          <br />
21          <input type="submit" value="Publish"
22              i18n:attributes="value" />
23  
24        </form>
25  
26      </div>
27      <div id="actions" metal:fill-slot="actions">
28        <a href="posts.html" i18n:translate="">View Posts</a>
29      </div>
30    </body>
31  </html>
In the corresponding Python view class, we can simply reuse the code we developed for the management version:
在相应的 Python 视图类中,我们可以简单的在管理版本中复用我们开发的代码:

代码: 全选

1  from zope.app.import zapi
2  from zope.app.dublincore.interfaces import ICMFDublinCore
3  from zope.app.interfaces.workflow import IProcessInstanceContainer
4  
5  from book.messageboard.browser.messageboard import ReviewMessages
6  
7  class Review(ReviewMessages?):
8      """Review messages for publication."""
9  
10      def getPendingMessagesInfo(self):
11          """Get all the display info for pending  messages"""
12          msg_infos = []
13          for msg in self.getPendingMessages(self.context):
14              dc = ICMFDublinCore?(msg)
15              info = {}
16              info[path] = zapi.getPath(msg)
17              info[title] = msg.title
18              info[creator] = dc.creators[0]
19              formatter = self.request.locale.dates.getFormatter(
20                  dateTime, medium)
21              info[created] = formatter.format(dc.created)
22              info[url] = zapi.getView(
23                  msg, absolute_url, self.request)() + \
24                  /@@details.html
25              msg_infos.append(info)
26          return msg_infos
27  
28      def updateStatus(self, messages):
29          """Upgrade the stati from pending to published."""
30          if not isinstance(messages, (list, tuple)):
31              messages = [messages]
32  
33          for path in messages:
34              msg = zapi.traverse(self.context, path)
35  
36              adapter = IProcessInstanceContainer(msg)
37              adapter[publish-message].fireTransition(pending_published)
38  
39          return self.request.response.redirect(@@review.html)
* Line 5, 7, & 13: We are going to reuse some code from the other implementation.
* 第 5,7 & 13 行: 我们打算复用来自其他实现的部分代码
* Line 28-39: This is the interesting method, since it actually fires the transition from “pending” to “published” status.
* 第 28-39 行: 这是个有趣的方法,因为它实际上将状态从“待审”转换成“发布”。
o Line 33-34: Since we were clever, we passed the path as checkbox value, so that we can now simply traverse to it.
o 第 33-34 行: 由于我们的明智,我们把通过的路径作为了检查框的值,所以我们现在可以简单地穿过它。
o Line 36-37: Once we have the message, we get its process container and fire the transition.
o 第 36-37 行: 一旦我们有了消息,我们将得到它的处理容器和转换

The new review view can now be registered using the pages directive:
新的复审视图现在可以用页面语句注册了:

代码: 全选

1  <pages
2      for="book.messageboard.interfaces.IMessageBoard"
3      class=".views.Review"
4      permission="book.messageboard.PublishContent?"
5      layer="board">
6      <page name="review.html" template="board_review.pt"/>
7      <page name="updateStatus.html" attribute="updateStatus"/>
8  </pages>
Now restart Zope 3. Before we can enjoy publishing messages, we need to automate the transition from the “private” to “pending” status. To do that, go to the “default” Site-Management folder, and from there to your “publish-message” workflow definition. There you will find an option called “Manage Transitions”. Click on it and choose the transition named “private_pending”. In the following edit form, there is a “Trigger Mode” option that is set to “Manual”. Set it to “Automatic”. This will cause the messages to move automatically from “initial” to “pending”. If you now create a message, it will be automatically placed on the review list, from which you can then publish it as messageboard moderator. If you do not want any workflow at all, you can also set the “pending_published” “Trigger Mode” to “Automatic” and all newly created messages will automatically be published.
现在重启 zope 3,之前我们已经完成了消息发布,现在我们需要自动将状态从“私有”变成“待审”。为了做到这一点,到“缺省”的站点管理文件夹,从那到你的“消息发布”工作流定义。你可以发现有个选项叫“Manage Transitions”。点击它并选择名为 “private_pending” 的转换。在下面的编辑表单中有个 “Trigger Mode” 是被设为 “Manual(手动)”的。将其设为“Automatic(自动)”。这将引起消息自动从 “initial” 状态变为 “pending” 状态。如果你现在创建一个消息的话,它将自动出现在复审列表中,以便你可以作为消息栏版主来发布它。如果你根本不想用任何工作流的话,你也可以将 “pending_published” 的 “Trigger Mode” 设为 “Automatic” ,这样所有新建的消息就会被自动发布了。
图片
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#44

帖子 firehare » 2005-12-16 8:35

24.8 Step VIII: View Message Details
24.8 步骤 VIII: 查看消息细节

While the ZMI-based Message Details screen was very clear and detailed, it is not suitable for the end user. This view implements a better version of that screen and also adds the replies thread at the end. One of the available actions is to reply to the message. This is the screen we will look at next.
尽管基于 ZMI-based 的消息细节屏幕是非常清楚和详细的,但它并不适合最终用户。这个视图实现更好的屏幕版本并在其后添加了回复线索。一个可用的动作就是回复消息。我们将在下面看到该屏幕。

Since there is nothing new or very interesting going on in the code, I am going to skip over it and move directly to the next one. You will find the code in the accompanying package as usual.
因为在代码中没有什么新的或感兴趣的东西,所以我打算略过它到下一节。你一般可以在伴生包中找到该代码。
图片
我心无畏,源自于我心无知。
图片
头像
firehare
帖子: 2625
注册时间: 2005-04-10 16:54
来自: 温州大学
联系:

#45

帖子 firehare » 2005-12-16 18:11

24.9 Step IX: Replies to Messages
24.9 步骤 IX: 回复消息

Wow, another add form; can we do this? Yes, of course. The “Reply to Message” add form is a bit special though, since it should contain dynamic default values. For example, if the original message had a title called “My Title”, then the suggested title for the new reply message should be “Re: My Title”. Similarly, every text line of the original message body should be prefixed with “>”.
哦,另一个添加表单;我们能做吗?当然可以。 “Reply to Message” 添加表单有点特别,那是因为它包括动态缺省值。比如说,如果最初的消息有一个名为 “My Title” 的标题,那么新回复消息的推荐标题就应该是 “Re: My Title”。同样的,原始消息的每个文本行都在前面加上">"符号。

The best place I found to insert the dynamic default values is in the private _setUpWidgets() methods, which creates a data dictionary that is passed to the widget instantiater. The code for the entire add form view class looks like that:
我发现插入动态的缺省值的最佳方法就是在私有方法 _setUpWidgets() 中,它创建一个数据字典that is passed to the widget instantiater ,整个添加表单视图类的代码如下所示:

代码: 全选

1  from zope.app.form.interfaces import IInputWidget
2  from zope.app.form.utility import setUpWidgets
3  
4  class ReplyMessage:
5      """Add-Form supporting class."""
6  
7      def nextURL(self):
8          return ../@@details.html
9  
10      def _setUpWidgets(self):
11          """Alllow addforms to also have default values."""
12          parent = self.context.context
13          title = parent.title
14          if not title.startswith(Re:):
15              title = Re:</span><span class="cmtt-10">&nbsp; + parent.title
16  
17          dc = getAdapter(parent, ICMFDublinCore)
18          formatter = self.request.locale.getDateTimeFormatter(
19              medium)
20          body = %s</span><span class="cmtt-10">&nbsp;on</span><span class="cmtt-10">&nbsp;%s</span><span class="cmtt-10">&nbsp;wrote:\n %(dc.creators[0]?,
21                                      formatter.format(dc.created))
22          body += ></span><span class="cmtt-10">&nbsp; + parent.body.replace(\n, \n></span><span class="cmtt-10">&nbsp;)
23  
24          setUpWidgets(self, self.schema, IInputWidget
25                       initial={title: title, body: body},
26                       names=self.fieldNames)
* Line 24-26: This is pretty much the original content of the _setUpWidgets() method, except for the initial argument, which carries the default values of the widgets.
* 第 24-26 行: 这是几乎就是 _setUpWidgets() 方法的原始内容,除了带来缺省值的初始化参数外。

Just register the form as
只是注册该表单

代码: 全选

1  <addform
2      label="Reply to Message"
3      name="ReplyMessage?"
4      schema="book.messageboard.interfaces.IMessage"
5      content_factory="book.messageboard.message.Message"
6      permission="book.messageboard.Add"
7      class=".views.ReplyMessage?"
8      layer="board"/>
Once you restart Zope 3, you should be able to see the reply to Message screen.
一旦你重启 Zope 3,你应该就能看到消息回复屏界了。
图片
Figure 24.5: Reply to a message.
图 24.5: 回复一个消息

This is how far I want to go with creating a nice, enduser-friendly interface. There is much more that could be done to make the package attractive, but it would not contain much content and would be boring to read about. Instead, I encourage the reader to decide him/herself about making further improvements. The exercises below should stimulate the reader about some possible improvements.
这就是我想创建一个高的、最终用户用好接口。有很多可以让该包更有吸引力的方法,但它并不包括太多的内容也不会乏味。相反,我鼓励读者做进一步的改善。下面的练习将会刺激读者做一些可能的改善。
我心无畏,源自于我心无知。
图片
回复