个人工具

Zope3宝典/添加视图

来自Ubuntu中文

跳转至: 导航, 搜索

Chapter 14:Adding Views(第 14 章:添加视图)


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

原文作者:StephanRichter, zope.org

授权许可:创作共用协议

翻译人员:

校对人员:Leal, FireHare

适用版本:Zope 3

文章状态:校正阶段



Difficulty(难度)

Newcomer(新手)

Skills(技能)

  • Knowledge gained in the “Writing a new Content Object” chapter.
    已经掌握了“编写一个新的内容组件”一章的内容;
  • Some understanding of the presentation components. Optional.
    懂一些表示组件的概念(可选)。

Problem/Task(问题/任务)

Now that we have two fully-functional content objects, we have to make the functionality available to the user, since there are currently only three very simple views: add, edit and contents. In this chapter we will create a nice message details screen as well as a threaded sub-branch view for both messages and the message board.
既然我们已经有了两个功能完整的内容对象, 接下来要做的就是尽可能让这些功能对用户可用。不过,现在这儿只有三个非常简单的视图:增加(add),编辑(edit)和内容(contents)。这章我们将为消息和留言簿创建一个非常不错的详细留言界面和子线程视图。

Solution(解决方案)

This chapter revolves completely around browser-based view components for the `MessageBoard` and Message classes. Views, which will be mainly discussed here, are secondary adapters. They adapt IRequest and some context object to some output interface (often just zope.interface.Interface).
本章的内容将围绕browser-based视图组件展开,视图将是我们在这儿主要讨论的知识,其次就是适配器。They adapt IRequest and some context object to some output interface (often just zope.interface.Interface)

There are several ways to write a view. Some of the dominant ones include:
这儿列出了编写视图的几种主要方法,下面说说需要涉及的内容:

  • We already learned about using the browser:addform, browser: editform and browser:containerViews directive. These directives are high-level directives and hide a lot of the details about creating and registering appropriate view components.
    我们已经学习了browser:addform、browser: editform和browser:containerViews 指令的使用。

    Forms can be easily configured via ZCML, as you have done in the previous chapter. Forms are incredibly flexible and allow you any degree of customization.
    Forms真的令人难以置信,它允许您任意程度的定制;
  • There is a browser:page and a browser:pages directive, which are the most common directives for creating browser views and groups of views easily. We will use these two directives for our new views.
    browser:page和browser:pages指令是易于创建浏览器视图和成组视图最常用的指令。我们将使用这两个指令创建我们的视图;
  • The zope:view directive is very low-level and provides functionality for registering multi-views, which the other directives are not capable of doing. However, for the average application developer the need to use this directive might never arise.
    zope:view是一个低级别的指令,它为注册多个视图(multi-views)提供相关功能,其它指令没有能力来干这些工作。

14.1 Step I: Message Details View(14.1 步骤 I:详细消息视图)

Let’s now start with the creation of the two new browser views, which is the goal of this chapter. While we are able to edit a message already, we currently have no view for simply viewing the message, which is important, since not many people will have access to the edit screen.
现在我们开始以创建两个新的浏览器视图为本章的目标。我们现在已经能编辑消息,可当前我们并没有查看该消息的简单视图,这是非常重要的,因为不是很多人都有权利进入编辑页面的。

The view displaying the details of a message should contain the following data of the message: the title, the author, the creation date/time, the parent title (with a link to the message), and the body.
消息视图应显示的详细资料如下:标题(title)、作者(author), 创建日期/时间(creation date/time), 父标题(链接到消息)(parent title)、内容(body)。

Writing a view usually consists of writing a page template, some supporting Python view class and some ZCML to insert the view into the system. We are going to start by creating the page template.
编写视图通常包括编写页面模板、一些支持Python的视图类、一些ZCML。现在我们将开始创建页面模板。

14.1.1 (a) Create Page Template(14.1.1 (a) 创建页面模板)

Create a file called details.pt in the browser package of messageboard and fill it with the following content:
在messageboard的browser中创建名为details.pt的文件,在该文件中填充如下内容:

1  <html metal:use-macro="views/standard_macros/view">
2    <body>
3      <div metal:fill-slot="body">
4  
5        <h1>Message Details</h1>
6  
7          <div class="row">
8              <div class="label">Title</div>
9              <div class="field" tal:content="context/title" />
10          </div>
11  
12          <div class="row">
13              <div class="label">Author</div>
14              <div class="field" tal:content="view/author"/>
15          </div>
16  
17          <div class="row">
18              <div class="label">Date/Time</div>
19              <div class="field" tal:content="view/modified"/>
20          </div>
21  
22          <div class="row">
23              <div class="label">Parent</div>
24              <div class="field" tal:define="info view/parent_info">
25                <a href="../details.html"
26                    tal:condition="info"
27                    tal:content="info/title" />
28              </div>
29          </div>
30  
31          <div class="row">
32              <div class="label">Body</div>
33              <div class="field" tal:content="context/body"/>
34          </div>
35  
36      </div>
37    </body>
38  </html>
  • Line 1-3 & 36-38: This is some standard boilerplate for a Zope page template that will embed the displayed data inside the common Zope 3 UI. This will ensure that all of the pages have a consistent look and feel to them and it allows the developer to concentrate on the functional parts of the view.
    第1-3行& 36-38行:这是一些针对Zope页面模板的标准样板文件,已经被嵌入到了显示数据中。这样做的好处就在于所有的页面有一致的界面,并且允许开发者专注于视图的功能部分。

  • Line 9: The title can be directly retrieved from the content object (the Message instance), which is available as context.
    第9行:title能直接从内容对象中重新得到,作为context它是可用的。

  • Line 14 & 19: The author and the modification date/time are not directly available, since they are part of the object’s meta data (Dublin Core). Therefore we need to make them available via the Python-based view class, which is provided to the template under the name view. A Python-based view class’ sole purpose is to retrieve and prepare data to be in a displayable format.
    第14 &19行:作者(author)和修改日期(modification date/time )不是直接可用的,因为它们只是对象元数据(都柏林核心数据集)的一部分。因此我们需要通过基于Python的视图类让它们可用。基于Python的视图类的作用在于它能重新得到和准备数据。

  • Line 24-27: While we probably could get to the parent via a relatively simple TALES path expression, it is custom in Zope 3 to make this the responsibility of the view class, so that the template contains as little logic as possible. In the next step you will see how this information is collected.
    第24-27行:通过相对简单的TALES路径表达式,我们或许能达到父对象,在下一步中您将看到怎样收集这些信息。

14.1.2 (b) Create the Python-based View class(14.1.2 (b) 创建基于Python的视图类)

From part (a) we know that we need the following methods (or attributes/properties) in our view class: author(), modified(), and parent_info(). First, create a new file called message.py in the browser package. Note that we will place all browser-related Python code for IMessage in this module.
从 (a)部分我们可以知道, 在我们的视图类中我们需要以下方法(或属性):author()、 modified()、 parent_info().首先我们在browser包中创建名为message.py的新文件,注意在这个模块里我们将为IMessage安置所有与browser相关的Python 代码。

Here is the listing of my implementation:
下面是我们的执行清单:

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

from book.messageboard.interfaces import IMessage


class MessageDetails:


def author(self):
"""Get user who last modified the Message."""
creators = ICMFDublinCore(self.context).creators
if not creators:
return 'unknown'
return creators[0]


def modified(self):
"""Get last modification date."""
date = ICMFDublinCore(self.context).modified
if date is None:
date = ICMFDublinCore(self.context).created
if date is None:
return ''
return date.strftime('%d/%m/%Y %H:%M:%S')


def parent_info(self):
"""Get the parent of the message"""
parent = zapi.getParent(self.context)
if not IMessage.providedBy(parent):
return None
return {'name': zapi.name(parent), 'title': parent.title}
  • Line 1: Many of the fundamental utilties that you need, are available via the zapi module. The zapi module provides all crucial component architecture methods, such as getParent(). All the core servicenames are also available. Furthermore you can access traversal utilities as well. See ZOPE3/src/zope/app/interfaces/zapi.py for a complete list of available methods via the zapi module.
    第1行:通过zapi模块可以得到您所需要的大多数基础的程序,zapi模块提供了所有至关重要的组件构建方法,比如说getParent(),还有所有可利用的核心servicenames 。参见ZOPE3/src/zope/app/interfaces/zapi.py就可以得到通过zapi模块的一个完整的可利用的方法列表。

  • Line 2: The ICMFDublinCore interface is used to store the Dublin Core meta data. Using this interface we can get to the desired information.
    第2行:ICMFDublinCore接口被用于存储都柏林核心数据,我们可以使用这个接口得到我们需要得到的信息。

  • Line 7: Note that the view class has no base class or specifies any implementing interface. The reason for this is that the ZCML directive will take care of this later on, by adding the BrowserView class as a base class of the view.
    第7行:注意,这个视图类没有基础类或指定任何应用接口,理由是稍后可以通过ZCML指令为视图添加BrowserView基础类。

    In some parts of Zope 3 you might still see the view class to inherit from `BrowserView`.
    在Zope3的某些部分您仍然能够看到从`BrowserView` 继承的视图类。

  • Line 12-16: The code tries to get a list of creators (which I refer to as authors) from the Dublin Core meta data. If no creator is found, return the string “unknown”, otherwise the first creator in the list should be returned, which is the owner or the original author of the object. Note that we should usually have only one entry, since Messages are not edited (as of this stage of development).
    第12-16行:这段代码试着从都柏林核心集获得创建者(涉及到作者)列表。如果创建者没有发现,就返回字符串"unknown",否则列表中的第一个创建者将被返回,也就是这个对象的拥有者或原始作者。注意由于消息没有被编辑过(作为开发步骤),因此通常应该仅仅有一项。

  • Line 20-28: Finding the modification date is a bit more tricky, since during the creation only the created field is populated and not the modified field. Therefore we try first to grab the modified field and if this fails we get the created field. If the created date/time does not exist, we return an empty string.
    第20-28行:发现修改日期更难对付,由于我们在创建时只创建了字段而非修改字段。因此我们首先尝试获取被修改字段,如果失败我们将获得创建字段,如果创建日期都不存在,我们就返回空字符串。

    Finally, if a date object was found, then we convert it to a string and return it.
    如果最后发现了日期对象,我们就把它转化成字符串并返回它。

  • Line 30-33: Getting the parent is easy, just use the getParent() method. But then we need to make sure that the parent is also an IMessage object; if it is not, then we have a root message, and we return None. The name and the title of the parent are stored in an information dictionary, so that the data can be easily retrieved in a page template.
    第30-33行:使用getParent()方法我们可以比较轻易的得到父对象。但是我们需要确认父对象仍然是一个Imessage对象。如果不是并且还有一个根消息,我们就返回None。如果是的话这个父对象的名称和标题将被存放在字典中,以至于能够比较轻易的在页面模板中重新得到数据。

14.1.3 (c) Registering the View(14.1.3 (c) 注册视图)

The final task is to register the new view using ZCML. Open the configuration file in the browser sub-package and add the following lines:
最后的工作就是用ZCML注册这个新的视图。在browser目录中打开配置文件(configure.zcml)并添加如下各行:

1  <page
2      name="details.html"
3      for="book.messageboard.interfaces.IMessage"
4      class=".message.MessageDetails"
5      template="details.pt"
6      permission="zope.Public"
7      menu="zmi_views" title="Preview"/>
  • Line 1: The browser:page directive registers a single page view.
    第1行:browser:page指令注册一个单独的页面视图。

  • Line 2: The name attribute specifies the name as which the view will be accessible in the URL:
    第2行:name属性指定了这个视图,可通过如下地址访问:
http://localhost:8080/board/message1/@@details.html

The name attribute is required.
name属性是必需的。

  • Line 3: The for attribute tells the system that this view is for IMessage objects. If this attribute is not specified, the view will be registered for Interface, which means for all objects.
    第3行:For属性将告诉系统这个视图是针对Imessage对象。如果属性没有被指定,表明视图将注册的接口适合全部对象。

  • Line 4-5: Use the just created MessageDetails class and details.pt page template for the view; for this page details.pt will be rendered and uses an instance of MessageDetails as its view.
    第4-5行:为该视图创建MessageDetails类和details.pt页面模板;details.pt页将作为视图被提供和用做MessageDetails的实例。

    Note that not all views need a supporting view class; therefore the class attribute is optional.
    注意不是所有的视图都需要一个视图类来支持,因此该类属性是可选的。

    While you will usually specify a page template for regular pages, there are situations, where you would prefer a view on an attribute of the Python view class. In these cases you can specify the attribute attribute instead of template. The specified attribute/method should return a unicode string that is used as the final output.
    通常您为一个规则的页面指定页面模板时,您也许宁愿有视图基于Python视图类属性的情形存在。这样的话,您能指定代替模板的属性。指定的属性/方法应该以unicode字符串的形式返回并最终输出。

  • Line 6: The permission attribute specifies the permission that is required to see this page. At this stage we want to open up the details pages to any user of the site, so we assign the zope.Public permission, which is special, since every user, whether authenticated or not, has this permission.
    第6行:Permission属性指定了查看该页面被要求的权限。现在我们想设置任何该站点的用户都有打开详细页的权限,于是我们设定了zope.Public权限。

  • Line 7: In order to save ourselves from a separate menu entry directive, we can use the menu and title attribute to tell the system under which menu the page will be available. In this case, make it a tab ( zmi_views menu) which will be called “Preview”.
    第7行:为了避免使用另外单独的菜单条目指令,我们可以利用菜单和名称属性来告知系统,网页应当隶属于哪一个菜单。此处我们使用一个标签(zmi_views menu),并将其命名为Preview(预览)。

All you need to do now is to restart Zope, add a Message content object (if you have not done so yet) and click on it. The “Preview” tab should be available now. Note that you will have no “Parent” entry, since the message is not inside another one.
现在您必须重新启动Zope,如果您以前没有添加一个消息对象的话请单击添加它。"Preview" tab菜单条目现在应该有效。假如这个消息没有包含在另一个消息里面,请注意界面上将没有"Parent"条目显示。

To see a “Parent” entry, add another message inside the current message by using the “Contents” view. Once you added the new message, click on it and go to the Details view. You should now see a “Parent” entry with a link back to the parent message.
为了在界面上看到"Parent"条目,需要使用"Contents"视图在当前消息下面新增另一条消息。现在您应该能看到一个能够链接到父消息的"Parent"条目。

14.1.4 (d) Testing the View((d) 测试视图)

Before moving to the next item on the list, we should develop some functional tests to ensure that the view works correctly. Functional tests are usually straight forward, since they resemble the steps a user would take with the UI. The only possibly tricky part is to get all the form variables set correctly.
在继续下一项之前,我们应该开发一些功能测试来确保视图正确工作。功能测试通常是自动向前执行的,因为它们仿真用户通过UI所采取的步骤。唯一感到棘手的问题是如何正确设置所有的变量。

To run the functional tests the entire Zope 3 system is brought up, so that all side-effects and behavoir of an object inside its natural environment can be tested. Oftentimes very simple tests will suffice to determine bugs in the UI and also in ZCML, since all of it will be executed during the functional test startup.
整个Zope3 系统提出了进行功能测试,以便一个对象的所有副作用和行为在它处的环境里面都可能被测试出来。经常进行一些简单的测试也能在UI和ZCML里发现出致命的错误,这是由于功能测试起动后所有的过程都将被执行。

The following functional tests will ensure that messages can be properly added and that the all the message details information are displayed in the “Preview”. By convention all functional tests are stored in a sub-module called ftests. Since we plan to write many of these tests, let’s make this module a package by creating the directory and adding an <u>init</u>.py file.
在下面的功能测试中将确信消息能够增加并且所有的详细信息将被显示在"Preview"中。所有的功能测试都习惯被存放在名叫ftests的子模块中。当我们决定写这些测试时,让我们通过创建目录和增加<u>init</u>.py文件来制作测试模块。

Now create a file called test_message.py and add the following testing code:
现在我们创建一个名为test_message.py的文件并且加上如下测试代码:

#!python
import unittest
from zope.app.tests.functional import BrowserTestCase

class MessageTest(BrowserTestCase):

def testAddMessage(self):
response = self.publish(
'/+/AddMessageBoard.html=board',
basic='mgr:mgrpw',
form={'field.description': u'Message Board',
'UPDATE_SUBMIT': 'Add'})
self.assertEqual(response.getStatus(), 302)
self.assertEqual(response.getHeader('Location'),
'http://localhost/@@contents.html')
response = self.publish(
'/board/+/AddMessage.html=msg1',
basic='mgr:mgrpw',
form={'field.title': u'Message 1',
'field.body': u'Body',
'UPDATE_SUBMIT': 'Add'})
self.assertEqual(response.getStatus(), 302)
self.assertEqual(response.getHeader('Location'),
'http://localhost/board/@@contents.html')

def testMessageDetails(self):
self.testAddMessage()
response = self.publish('/board/msg1/@@details.html',
basic='mgr:mgrpw')
body = response.getBody()
self.checkForBrokenLinks(body, '/board/msg1/@@details.html',
basic='mgr:mgrpw')

self.assert_(body.find('Message Details') > 0)
self.assert_(body.find('Message 1') > 0)
self.assert_(body.find('Body') > 0)


def test_suite():
return unittest.TestSuite((
unittest.makeSuite(MessageTest),
))

if <u>name</u> == '<u>main</u>':
unittest.main(defaultTest='test_suite')
  • Line 2: In order to simplify writing browser-based functional tests, the BrowserTestCase can be used as a test case base class. The most important convenience methods are used in the code below.
    第2行:为了简化写基于浏览器的功能测试,BrowserTestCase被用于测试基类。

  • Line 6-23: Before we are able to test views on a message, we have to create one. While it is possible to create a message using a lower-level API, this is a perfect chance to write tests for the adding views as well.
    第6-23行:在我们准备在一个消息上面测试视图之前,我们必须创建一个消息。我们可以用一个底层API函数创建一个消息。
    • Line 7-11: The publish() method is used to publish a request with the publisher. The first arguments is the URL (excluding the server and port) to be published. Commonly we also include the basic argument, which specifies the username and password. The system knows only about the user zope.mgr with username “mgr” and password “mgrpw”. The role zope.Manager has been granted for this user, so that all possible screens should be availale.
      第7-11行:publish()方法被用于发布者发布一个请求。第一个参数是URL(排除服务器和端口)被发布。通常我们也包括指定用户名和密码的basic参数。系统只识别用户名为mgr和密码为mgrpw的zope用户。zope.Manager角色已经授权给该用户,所以该用户可以使用所有的界面。

      In the form you specify a dictionary of all variables that are submitted via the HTTP form mechanism. The values of the entries can be already formatted Python objects and do not have to be just raw unicode strings. Note that the adding view requires a field named UPDATE_SUBMIT for the object to be added. Otherwise it just thinks this is a form reload.
      在form中,您指定通过HTTP表单机制被提交的所有变量的字典。条目的值已经能被Python对象格式化,而且不必要是生硬的unicode字符串。注意添加的视图要求为这个对象添加一个名叫UPDATE_SUBMIT字段。否则它仅仅认为这是表单重引。

    • Line 12-14: The adding view always returns a redirect (HTTP code 302). We can also verify the destination by looking at the “Location” HTTP header.
      第12-14行:添加视图总是返回重定向(HTTP 代码 302)。我们也能够通过查看"Location"HTTP头来查证目的地。

    • Line 15-23: Here we repeat the same procedure; this time by adding a message named “msg1” to the message board.
      第15-23行:这里我们重复相同的过程;这次添加一个名叫"msg1"的消息到留言簿中。

  • Line 25-35: After creating the message object (line 26), the details view is simply requested and the HTML result stored in body (line 27-29).
    第25-35行: 在创建消息对象 (第 26 行) 之后,详细视图被请求并且HTML结果存放在body里(27-29行)。

    One of the nice features of the BrowserTestCase is a method called checkForBrokenLinks() that parses the HTML looking for local URLs and then tries to verify that they are good links. The second argument of the method is the URL of the page that generated the body. This is needed to determine the location correctly. We should also specify the same authentication parameters, as used during the publication process, since certain links are only available if the user has the permission to access the linked page.
    BrowserTestCase比较好的特点之一是checkForBrokenLinks()方法,方法解析HTML寻找本地的URL,然后尝试检查他们是好链接。方法的第二个参数是生成页面的本页网址。这需要正确地决定位置。我们也应该指定一个相同的验证参数,在发布期间,如果用户有访问这个链接页面的权限,链接才可利用。

    In the last the tests (line 33-35) we simply check that some of the expected information is somewhere in the HTML, which is usally efficient, since a faulty view usually causes a failure during the publishing process.
    在测试的最后(33-35行)我们只是在HTML中检查一些预期的信息,这通常是重要的,因为一个不完善的视图在发布过程当中会导致失败。

  • Line 38-44: As always, we have to have the usual boilerplate.
    第 38 行-44行: 按惯例,我们必须有这些通用的样板代码。

Now that the tests have been developed, we can run them like the unit tests, except that for using the -u option (unit tests only), we now specify the -f option (functional tests only).
现在测试已经开发完毕,我们象单元测试一样来运行它们,但我们不能使用-U选项(仅仅单元测试可以使用),我们现在指定-f选项(仅用于功能测试)。

1  python2.3 test.py -vpf --dir src/book/messageboard

Since you already looked at the pages before, all tests should pass easily, unless you have a typo in your test case. Once the tests pass, feel free to go on to the next task.
既然以前您已经看过本页,所有的测试应该很容易地通过, 除非您的测试范例有书写问题。一旦测试通过,您就可以继续下一个任务了。

14.2 Step II: Specifying the Default View(14.2 步骤 II:指定缺省视图)

If you try to view a message using http://localhost:8080/board/msg1 at this point, you will get the standard container index.html view. This is rather undesirable, since you default view should really show the contents of the message.
此时如果您试着用 http://localhost:8080/board/msg1" 查看一个消息,您将会获得一个标准的index.html视图。由于缺省视图只是显示出了消息的内容,因此界面就不是那么合乎用户的需求了。

There is a special directive for declaring a default view. All you need to add are the following lines to your browser package configuration file:
这儿有一个特殊指令用于声明一个缺省的视图,请在您的browser程序包配置文件中添加如下代码:

1  <defaultView
2      for="book.messageboard.interfaces.IMessage"
3      name="details.html"/>
  • Line 2: Here we tell the system that we are adding a default view for the components implementing IMessage.
    第2行:这里我们告诉系统,我们为实现Imessage的组件添加一个缺省视图。

  • Line 3: We make the “Preview” screen the default view. However, you can choose whatever view you like. Naturally, these views are usually views that display data instead of asking for input. It is also advisable to make the least restrictive and most general view the default, so that users with only a few permissions can see something about the object.
    第 3 行:我们用"Preview"界面来做缺省视图。不过您也可以选择任何您喜欢的视图。当然这些视图通常是用来数据显示,而不是用来数据输入的。同时也建议至少对视图做一些限制并且设为缺省,以便于仅拥有少量权限的用户才能查看到这个对象的某些信息。

14.3 Step III: Threaded Sub-Tree View(14.3 步骤 III:线程树型视图)

Creating a nice and extensible thread view is difficult, since the problem is recursive in nature. We would also like to have all HTML generation in Page Templates, since it allows us to enhance the functionality of the view later; however, Page Templates do not like recursion.
创建一个好的线程树型视图自然是比较难的,因为稍后它能让我们的视图功能得到增强。通过页面模板能够有效的避免重复同一操作。

14.3.1 (a) Main Thread Page Template(14.3.1 (a) 主线程页面模板)

So let’s tackle the problem by starting to create the main view template for thread.html, which we call thread.pt:
现在我们开始为thread.html创建一个主视图模板,这个模板名叫thread.pt:

1  <html metal:use-macro="views/standard_macros/view">
2    <body>
3      <div metal:fill-slot="body">
4  
5        <h1>Discussion Thread</h1>
6  
7        <div tal:replace="structure view/subthread" />
8  
9      </div>
10    </body>
11  </html>

Almost everything is boiler plate really, but there is enough opportunity here to add some more functionality later, if we desire to do so.
我们几乎可以把所有东西都做成模板文件,不过,如果我们需要这么做的话,稍后还有更多的机会在这里添加更多功能。

  • Line 7: Being blind about implementation, we simply assume that the Python-based view class will have a subthread() that can magically generate the desired sub-thread for this message or even the message board.
    第 7 行:现在我们随便做些应用, 我们简单地假定基于Python的视图类有一个能为这个消息或者留言簿产生我们需要的子线程的方法subthread()。

14.3.2 (b) Thread Python View Class(14.3.2 (b) 线程Python视图类)

Next we have to build our Python view class. We start by editing a file called thread.py and insert the following code:
下一步我们必须建立我们自己的Python视图类。我们开始编辑thread.py文件并且插入如下代码:

#!python
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from book.messageboard.interfaces import IMessage

class Thread:

def <u>init</u>(self, context, request, base_url=''):
self.context = context
self.request = request
self.base_url = base_url

def listContentInfo(self):
children = []
for name, child in self.context.items():
if IMessage.providedBy(child):
info = {}
info['title'] = child.title
url = self.base_url + name + '/'
info['url'] = url + '@@thread.html'
thread = Thread(child, self.request, url)
info['thread'] = thread.subthread()
children.append(info)
return children

subthread = ViewPageTemplateFile('subthread.pt')
  • Line 1: The ViewPageTemplateFile class is used to allow page templates to be attributes/methods of a Python class. Very handy.
    第 1 行: ViewPageTemplateFile类允许页面模板有Python类的属性和方法,非常便利。

  • Line 2: Import the IMessage interface, since we need it for object identification later.
    第 2 行: 导入IMessage 接口, 因为我们稍后为对象标识需要它。

  • Line 25: Here is our promised subthread() method, which is simply a page template that knows how to render the thread. Note: You might want to read part (c) first, before proceeding.
    第 25 行: 这里有我们承诺的subthread()方法,它只是一个简单知道怎样描绘线程的页面模板。注意:在进行之前,您可能想先阅读part (c)。

  • Line 12-23: This method provides all the necessary information to the subthread page template to do its work. For each child it generates an info dictionary. The interesting elements of the dictionary include the url and the thread values. The URL is built up in every iteration of the recursive process. We could also use the zope.app.traversing framework to generate the URL, but I think this is a much simpler this way.
    第12-13行:这一个方法为subthread页面模板提供了它工作所有的必需信息。对于每个child它产生一个信息字典。字典中包括url和thread值这些有趣的元素。URL在循环过程每个迭代中被建立。我们也可以用zope.app.traversing框架产生URL,但我想这种方法更为简单些。

    The second interesting component of the info, the thread value, should contain a string with the HTML describing the subthread. This is were the recursion comes in. First we create a Thread instance (view) for each child. Then we are asking the view to return the subthread of the child, which is certainly one level deeper, which in return creates deeper levels and so on. Therefore the thread value will contain a threaded HTML representation of the branch.
    首先我们为每个child创建一个Thread实例(view),然后我们要求视图返回到child的subthread,确定了下一层, 当返回后创建更深的层等等。因此thread值将包含一个分支线程的HTML表示。

14.3.3 (c) Sub-Thread Page Template((c) 子线程页面模板)

This template, named subthread.pt as required by the view class, is only responsible of creating an HTML presentation of the nested Message children using the information provided; therefore the template is very simple (since it contains no logic):
我们把view类需要的这个模板命名为subthread.pt,模板仅负责用提供的信息创建款套Message children,因此模板是很简单的(因为没包含逻辑):

1  <ul>
2    <li tal:repeat="item view/listContentInfo">
3      <a href=""
4          tal:attributes="href item/url"
5          tal:content="item/title">Message 1</a>
6      <div tal:replace="structure item/thread"/>
7    </li>
8  </ul>
  • Line 1 & 8: Unordered lists are always good to create threads or trees.
    第1行:UL总是有益于创建threads 或trees。

  • Line 2: Thanks to the Thread view class, we simply need to iterate over the children information.
    第2行:由于Thread view类,我们只是需要反复children信息。

  • Line 3-5: Make sure we show the title of the message and link it to the actual object.
    第3-5行:确定我们需要显示的消息标题和链接的实际对象。

  • Line 6: Insert the subthread for the message.
    第6行:为消息插入subthread。

14.3.4 (d) Register the Thread View(14.3.4 (d) 注册Thread View)

Registering the thread view works like before:
象以前一样注册thread视图。

1  <page
2      name="thread.html"
3      for="book.messageboard.interfaces.IMessage"
4      class=".thread.Thread"
5      template="thread.pt"
6      permission="zope.View"
7      menu="zmi_views" title="Thread"/>

You should be familiar with the page directive already, so the above code should be easy to understand.
您应该已经比较熟悉这页指令了,因此上述代码应该很容易理解。

You also have to register the same view for IMessageBoard, so that you can get the full thread of the entire messageboard as well.
您也必须为 IMessageBoard 注册相同的视图,以便于您能得到整个messageboard 的完整线程。

14.3.5 (e) Message Board Default View(14.3.5 (e) 留言簿缺省视图)

Since the message board does not have a default view yet, let’s make the thread view the default:
由于留言簿还没有一个缺省的视图,现在我们把这个线程视图设为默认:

1  <defaultView
2      for="book.messageboard.interfaces.IMessageBoard"
3      name="thread.html"/>

This is, of course, very similar to the default view we registered for IMessage before.
这当然和我们在之前为Imessage注册缺省视图有些相似。

14.4 Step IV: Adding Icons(14.4 步骤 IV:添加图标)

Now that we have some text-based views, let’s look into registering custom icons for the message board and message. Icons are also just views on objects, in this case our content components. However, to make life easier the browser namespace provides a convenience directive called icon to register icons.
既然我们有一些基于文本的视图,让我们研究为留言簿和消息定制图标,图标也就是在当前情况下我们内容组件上的视图。然而,browser命名空间提供一个更为方便的 icon 指令来注册图标。

Simply add the following directive for each content type in the browser package configuration file:
因此只需简单地在browser软件包配置文件中为每个内容类型添加如下指令:

1  <icon
2      name="zmi_icon"
3      for="book.messageboard.interfaces.IMessage"
4      file="message.png" />

The code should be self-explanatory at this point. Instead of a template, we are specifying a file here as the view, which is expected to be binary image data and not just ASCII text.
这段代码此时应该不需要说明。替代上面的模板,我们指定一个文件作为视图,这是图象数据而不是 ASCII文本文件。

Now you should be all set. Restart Zope 3 and see whether the new features are working as expected.
现在您已经做了所有的设置。重新启动Zope 3,然后看新功能是否按照您的预期在开展工作。

The code is available in the Zope SVN under http://svn.zope.org/book/trunk/messageboard/step02.
代码可在Zope SVN http://svn.zope.org/book/trunk/messageboard/step02 中得到。

Exercises(练习)

  • For the message details screen it might be also useful to display the author of the parent message. Expand the returned information dictionary of parent_info to include the author of the parent and display it properly using the template.
    在详细的消息界面中显示父消息的作者。扩展parent_info返回信息字典,包括父消息的作者并且用模板显示它。

  • It would be great if there was a Reply, Modify, and Delete link (maybe as an image) behind each message title and make the actions work. Note that you should be able to reuse a lot of existing code for this.
    如果在每条消息标题后面再有回复、修改以及删除链接(可能是图象)并且能让它们正常工作就更好了。注意您应该能够重用许多现在已经存在的代码。