个人工具

Zope3宝典/开发新皮肤

来自Ubuntu中文

跳转至: 导航, 搜索

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


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

原文作者:StephanRichter, zope.org

授权许可:创作共用协议

翻译人员:

  • FireHare <ubuntu.firehare@gmail.com>

校对人员:Leal

适用版本:Zope 3

文章状态:校正阶段



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 层的皮肤,然后所有新的样式定义就会生效。

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" />

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 和其他一些对我们也很有用的视图代码。除此之外,我们不会经常使用这个层。

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"> 
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 行: 额外的层属性对于指定层是足够的。

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 且在根文件夹中。

PIC Figure 24.1: Message Board introduction screen of the “board” skin.

== 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” 状态中的)

PIC Figure 24.2: Posted messages of the message board.

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 模块中的实现如下所示:

#!python
class AddMessage:
"""Add-Form supporting class."""

def nextURL(self):
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 之后,你现在可以为添加表单修正的性能感到惊讶。

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 视图类中,我们可以简单的在管理版本中复用我们开发的代码:

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

from book.messageboard.browser.messageboard import ReviewMessages

class Review(ReviewMessages):
"""Review messages for publication."""

def getPendingMessagesInfo(self):
"""Get all the display info for pending messages"""
msg_infos = []
for msg in self.getPendingMessages(self.context):
dc = ICMFDublinCore(msg)
info = {}
info['path'] = zapi.getPath(msg)
info['title'] = msg.title
info['creator'] = dc.creators[0]
formatter = self.request.locale.dates.getFormatter(
'dateTime', 'medium')
info['created'] = formatter.format(dc.created)
info['url'] = zapi.getView(
msg, 'absolute_url', self.request)() + \
'/@@details.html'
msg_infos.append(info)
return msg_infos

def updateStatus(self, messages):
"""Upgrade the stati from 'pending' to 'published'."""
if not isinstance(messages, (list, tuple)):
messages = [messages]

for path in messages:
msg = zapi.traverse(self.context, path)

adapter = IProcessInstanceContainer(msg)
adapter['publish-message'].fireTransition('pending_published')

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 行: 这是个有趣的方法,因为它实际上将状态从“待审”转换成“发布”。
    • Line 33-34: Since we were clever, we passed the path as checkbox value, so that we can now simply traverse to it.
      第 33-34 行: 由于我们的明智,我们把通过的路径作为了检查框的值,所以我们现在可以简单地穿过它。
    • Line 36-37: Once we have the message, we get its process container and fire the transition.
      第 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” ,这样所有新建的消息就会被自动发布了。

PIC Figure 24.3: Review of all pending messages.

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.
因为在代码中没有什么新的或感兴趣的东西,所以我打算略过它到下一节。你一般可以在伴生包中找到该代码。

PIC Figure 24.4: Display of all details of a message.

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 ,整个添加表单视图类的代码如下所示:

#!python
from zope.app.form.interfaces import IInputWidget
from zope.app.form.utility import setUpWidgets

class ReplyMessage:
"""Add-Form supporting class."""

def nextURL(self):
return '../@@details.html'

def _setUpWidgets(self):
"""Alllow addforms to also have default values."""
parent = self.context.context
title = parent.title
if not title.startswith('Re:'):
title = 'Re: ' + parent.title

dc = getAdapter(parent, ICMFDublinCore)
formatter = self.request.locale.getDateTimeFormatter(
'medium')
body = '%s on %s wrote:\n' %(dc.creators[0],
formatter.format(dc.created))
body += '> ' + parent.body.replace('\n', '\n> ')

setUpWidgets(self, self.schema, IInputWidget
initial={'title': title, 'body': body},
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,你应该就能看到消息回复屏界了。

PIC Figure 24.5: Reply to a message.
回复一个消息

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.
这就是我想创建一个高的、最终用户用好接口。有很多可以让该包更有吸引力的方法,但它并不包括太多的内容也不会乏味。相反,我鼓励读者做进一步的改善。下面的练习将会刺激读者做一些可能的改善。

Exercises(练习)

      • If you don’t like the generated message names, there is a way to change this. The names are generated by an adapter for `IWriteContainer` implementing `zope.app.container.interfaces.INameChooser`. Write you own adapter for IMessage and `IMessageBoard`
        如果你不喜欢通用消息名,那么有种方法可以改变它。通过 `IWriteContainer` 实现 `zope.app.container.interfaces.INameChooser` 来产生名字。编写你自己的消息和留言薄适配器。
      • Currently the actions on the bottom of the page are implemented using simple links and the user does not know whether s/he will be able to access the view or not. Implement the actions in a way that they depend on the user’s permissions. Note: Menus are extremely helpful for this, so I suggest using them.
        在页面底部当前的动作是使用简单的链接,用户并不知道他/她是否可以访问视图。实现一种可以根据用户的权限来变化的动作。注意:菜单是非常有用的。所以我建议使用它们。
      • The message board’s “Posts” screen currently shows all published messages, no matter what. If you have a couple hundred messages, it can take quiet a while to load the screen. Therefore you should implement some batching that only shows at most 20 messages at a time.
        留言薄的 “Posts” 屏幕显示所有的发布消息,没关系。如果你有几百个消息,它就需要有相当一段时间来引导该屏幕了。因此你应该实现分批显示,每次至多 20 个消息。
      • There exists currently no way to add attachments to posts. Add a screen that allows you to add attachments to a message. You might also want to customize the “Create a new Post” and “Reply to Post” add forms to be able to add attachments.
        当前还不存在发帖时添加附件的方式。增加一个屏幕以允许你给消息添加附件。你也许想自定义“Create a new Post” 和 “Reply to Post” 添加表单以便能添加附件。
      • The “Review Messages” for publication screen has really no option of declining messages. Therefore add some functionality that allows the refusal of messages. Once a message is refused, it should be either deleted or set to private state.
        发布屏幕的 “Review Messages” 实际上没有取消信息的选项。因此添加允许取消消息的功能。一旦一个消息被取消,它应该要么被删除或被设为私有状态。
      • There is currently no way for users to mail-subscribe to messages. Implement this screen.
        目前用户没办法用邮件订阅消息。实现该屏幕。
      • Utilize the Online Help and the existing screens to build a very user-friendly help for the message board.
        运行在线帮助和现有屏幕为留言薄构造一个非常用户友好的帮助。