Zope3宝典/编写内容对象

来自Ubuntu中文
Oneleaf留言 | 贡献2007年5月31日 (四) 09:26的版本 (新页面: == Chapter 13:Writing a new Content Object(第 13 章:编写新的内容对象) == ---- 原文出处:[http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/FrontPage/...)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航跳到搜索

Chapter 13:Writing a new Content Object(第 13 章:编写新的内容对象)


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

原文作者:StephanRichter, zope.org

授权许可:创作共用协议

翻译人员:

校对人员:Leal, FireHare

适用版本:Zope 3

文章状态:校正阶段



Difficulty(难度)

Newcomer(新手)

Skills(技能)

  • The developer should be familiar with Python and some object-oriented concepts. Component-based programming concepts are a plus.
    开发人员应该具备Python的编程经验,需要了解一些面向对象的基本概念以及基于组件编程的基本原理;
  • Some understanding of the schema and interface packages. Optional.
    对模型(schema)和接口程序包有一些了解。(可选)

Problem/Task(问题/任务)

Of course it is essential for any serious Zope 3 developer to know how to implement new content objects. Using the example of a message board, this chapter will outline the main steps required to implement and register a new content component in Zope 3.
在Zope 3中实现新的内容对象对Zope3开发人员来讲其重要性不言而喻。为了让讲解更加具有可操作性,本章将使用一个留言簿范例来帮助大家理解。下面一章,我们将列出在Zope3中实现和注册一个新内容组件所需的主要步骤。

Solution(解决方案)

This chapter is the beginning of the development of MessageBoard content type and everything that is to it. It serves very well as the first hands-on task, since it will not assume anything other than that you have Zope 3 installed, know some Python and are willing to invest some time in learning the framework.
本章将是我们开发一个 MessageBoard 类型的一个起点。 在任务开始前,我们认为您已经安装好了 Zope 3 , 还有您也已经具备了 Python 的编程技能,当然更重要的一点就在于您非常乐意花一些时间学习 Zope 框架。


13.1 Step I: Preparation(13.1 步骤 I:准备工作)

Before you start, you should have installed Zope 3, created a principal.zcml file and have successfully started Zope. You have already done that? Okay, then let’s start.
在开始之前,你应该已经安装了 Zope 3,创建了 principal.zcml 文件并可以成功启动 Zope。你都做到了吗? 好的,那么,让我们开始。

Other than in Zope 2, Zope 3 does not require you to place add-on packages in a special directory and you are free to choose where to put it. The most convenient place is inside ZOPE3/src ( ZOPE3 is your Zope 3 directory root), since it does not require us to mess with the PYTHONPATH. To clearly signalize that this application is demo from the book, we place all of the code in a package called book. To create that package, add a directory using

1  mkdir ZOPE3/src/book

on Unix.
相对于Zope2, Zope3没有要求您必须在指定目录中添加程序包,您可以选择您认为方便的地方来添加它。最好的地方就是把它放到ZOPE3/src (ZOPE3 是您的Zope3安装目录)目录中, 因为这样的话,我们就不用担心和PYTHONPATH相混淆了。另外,为了让书中所演示的内容显得更加整洁清晰,我们把所有的程序代码都放进一个名为book的程序包。在Unix中,用下面的命令创建book 目录:

1  mkdir ZOPE3/src/book

To make this directory a package, place an empty <u>init</u>.py file in the new directory. In Unix you can do something like
把一个空的 <u>init</u>.py 文件放到新建的目录里,在Unix中,您可以使用如下命令来完成该工作:

1  echo "# Make it a Python package" >> ZOPE3/src/book/<u>init</u>.py

but you can of course also just use a text editor and save a file of this name. Just make sure that there is valid Python code in the file. The file should at least contain some whitespace, since empty files confuse some archive programs.
不过,您当然也能使用文本编辑器来创建该文件,但是您需要确信该文件中的Python 程序代码是正确的。通常,文件中应该至少包含一些空格,因为空文件常常搞乱一些存档文件。

Now we create another package inside book called messageboard, in a similar manner (do not forget to create the <u>init</u>.py file). From now on we are only going to work inside this messageboard package, which should be located at ZOPE3/src/book/messageboard.
现在我们又以类似方式在book里面创建另一个messageboard的程序包(不要忘记创建<u>init</u>.py 文件)。从现在开始,我们下面需要进行的所有工作都只需在messageboard包里进行,这个包位于ZOPE3/src/book/messageboard。

Note: While the source code, that you can download for every step at http://svn.zope.org/book/trunk/messageboard, contains a license header, we omit these throughout the book to save typing and space. However, the copyright as stated in the source files still applies.
注意:您可以通过如下地址下载我们所做的每一步的源码: http://svn.zope.org/book/trunk/messageboard 其中包含许可,为了减少输入,我们在整个源程序中都省略了与许可有关的信息,但这并不代表这些源程序没有版权,实际上,版权仍然存在于这些源文件之中。

13.2 Step II: The Initial Design(13.2 步骤 II:初步设计)

As stated above, our goal is to develop a fully functional, though not great-looking, Web-based message board application. The root object will be the MessageBoard, which can contain postings or Message objects from various users. Since we want to allow people to respond to various messages, we need to allow messages to contain replies, which are in turn just other Message objects.
就像前面我们所提到的,我们的初衷就是需要开发一个基于Web的留言簿应用程序,程序的界面并不要求十分漂亮,但功能则要求相当完备。具体设计是,根对象 MessageBoard 可以容纳来自不同用户的记录或消息(Message)对象。另外,由于我们想让用户对不同的消息回复,因此我们就需要允许消息包含回复。

That means we have two container-based components: The MessageBoard contains only messages and can be added to any Folder or container that wishes to be able to contain it. To make the message board more interesting, it also has a description, which briefly introduces the subject/theme of the discussions hosted. Messages, on the other hand should be only contained by message boards and other messages. They will each have a title and a body.
那就意味着我们有两个基于容器的组件:MessageBoard只包含能被添加到任何文件夹的消息或那些希望能包含消息的容器。MessageBoard应该有一个对主题讨论的描述(description)。另一方面,Messages(消息)只被留言簿或其他消息包含。每一个消息都将有标题(title)和正文(body)。

This setup should contain all the essential things that we need to make the object usable. Later on we will associate a lot of other meta-data with these components to integrate them even better into Zope 3 and add additional functionality.
设置应该包含我们制作这个可用对象所必需的东西。稍后我们还将联合许多其它与这些组件有关的元数据,并把他们更好的集成到zope3中,而且还将增加相关的辅助功能。

13.3 Step III: Writing the interfaces(13.3 步骤 III:编写接口)

The very first step of the coding process is always to define your interfaces, which represent your external API. You should be aware that software that is built on top of your packages expect the interfaces to behave exactly the way you specify them. This is often less of an issue for attributes and arguments of a method, but often enough developers forget to specify what the expected return value of a method or function is or which exceptions it can raise or catch.
编码阶段的第一步就是定义那些能代表您外部API函数的接口程序。您应该有这样的意识,那些建立在您程序包上的软件接口程序期望能够按照您指定的方式运行。尽管对于方法属性和参数这显得无足轻重,但是太多的开发人员常常忘记指定方法的预期返回值,或者忘记编写捕捉异常的功能模块。

Interfaces are commonly stored in an interfaces module or package. Since our package is not that big, we are going to use a file-based module; therefore start editing a file called interfaces.py in your favorite editor.
在ZOPE3中,接口被一起存放在接口模块或程序包中。由于我们的程序包不是很大,我们准备使用一个基于文件的模块;因此在文本编辑器中编辑一个名叫interfaces.py的文件。

In this initial step of our application, we are only interested in defining one interface for the message board itself and one for a single message, which are listed below (add these to the file interfaces.py):
在程序开始,我们只是为留言簿本身(message board)定义一个接口,为单个消息(message)定义一个接口,代码如下(在interfaces.py文件中添加这些代码):

#!python
from zope.interface import Interface
from zope.schema import Text, TextLine, Field

from zope.app.container.constraints import ContainerTypesConstraint
from zope.app.container.constraints import ItemTypePrecondition
from zope.app.container.interfaces import IContained, IContainer
from zope.app.file.interfaces import IFile


class IMessage(Interface):
"""A message object. It can contain its own responses."""

def <u>setitem</u>(name, object):
"""Add a IMessage object."""

title = TextLine(
title=u"Title/Subject",
description=u"Title and/or subject of the message.",
default=u"",
required=True)

body = Text(
title=u"Message Body",
description=u"This is the actual message. Type whatever you wish.",
default=u"",
required=False)


class IMessageBoard(IContainer):
"""The message board is the base object for our package. It can only
contain IMessage objects."""

def <u>setitem</u>(name, object):
"""Add a IMessage object."""

<u>setitem</u>.precondition = ItemTypePrecondition(IMessage)

description = Text(
title=u"Description",
description=u"A detailed description of the content of the board.",
default=u"",
required=False)


class IMessageContained(IContained):
"""Interface that specifies the type of objects that can contain
messages."""
<u>parent</u> = Field(
constraint = ContainerTypesConstraint(IMessageBoard, IMessage))


class IMessageContainer(IContainer):
"""We also want to make the message object a container that can contain
responses (other messages) and attachments (files and images)."""

def <u>setitem</u>(name, object):
"""Add a IMessage object."""

<u>setitem</u>.precondition = ItemTypePrecondition(IMessage, IFile)
  • Line 1: Import the base Interface class. Any object that has this meta-class in its inheritance path is an interface and not a regular class.
    第1行:导入Interface类。任何在其继承路径上包含该类型的对象都是一个接口,而非常规类。
  • Line 2: The attributes and properties of an object are described by fields. Fields hold the meta-data about an attribute and are used, among other things, to validate values and create auto-generated input forms. Most fields are defined in the zope.schema package. For more details and a complete list of fields see “Zope Schemas and Widgets (Forms)”.
    第2行:一个对象的属性(attributes)和性质(properties)通过fields来描述。Fields包含关于被使用属性的元数据,它和其他东西一起被用来进行值的校验、创建自动生成的输入表单。绝大部分的fields在zope.schema包中定义。更详细的信息和fields的完整列表参见"Zope Schemas and Widgets (Zope模型和部件)"。
  • Line 4: ContainerTypesConstraint conditions allow us to tell the system to which type of containers an object can be added. For example, a message only wants to be contained by the message board and another message (when it is a reply to the parent message). See below how it is used.
    第4行:ContainerTypesConstraint条件让我们告诉系统,对象可以被添加到哪种容器中。例如,message只能被message board和被另一个message包含(当它作为父message的回复信息时)。用法如下文所示。
  • Line 5: The ItemTypePrecondition is the opposite of the container types constraint in that it specifies the object types that can be contained by a container. See below for its usage.
    第5行:ItemTypePrecondition是容器类型限制条件相对立的限制条件,它可指定能够被某个容器包含的对象类型。用法如下文所述。
  • Line 6: Objects providing the IContained interface can be included in the Zope object tree. We also import IContainer here, which is used as a base interface in Line 29 and 52. IContainer defines all necessary methods for this object to be recognized as a container by Zope 3.
    第6行:提供IContained接口的对象能被包括在Zope对象树中。我们在这里也导入了IContainer接口。该接口在第29行和第52行中被用作基接口。IContainer接口定义了让对象被Zope3识别为容器所有需要的方法。

    Note that we do not need to inherit Interface directly, since IContainer already inherits it, which automatically makes IMessageBoard also an interface.
    注意:我们并没有直接继承Interface,因为 IContainer已经继承了,它会自动让IMessageBoard成为一个对象。
  • Line 10: You might have already noticed the “I” in front of all the interfaces, which simply stands for “Interface” as you might have guessed already. It is a convention in Zope, so that we do not confuse interfaces and classes, since these two different object types cannot be used in the same way.
    第10行:可能您已经注意到所有接口前面的"I",它是"Interface"的简写。这是Zope的约定,这样我们就不会将接口和类混淆起来,因为这两种不同的对象类型是不能够以同样的方式来使用的。

    In general messages simply are objects that have a title and a body. Nothing more. We later declare more semantics through additional interfaces and meta-data.
    总的来说,message是具有标题(title)和正文(body)的简单对象,仅此而已。我们稍后会通过另外的接口和元数据来声明更多的语义。
  • Line 16-20: A simple title/subject headline for the message. Note that we made this a TextLine instead of a Text field, so that no newline characters can be inserted. This way the title will be relatively short and will be perfect for the title display where desired.
    第16-20行:是message的简单标题/主题的标题行。注意我们使用 TextLine 字段而不是用Text字段,这样就不能在其中插入换行符。因此标题相对较短,而且也能按照标题所需要的方式来进行显示。
  • Line 22-26: The body is the actual message content. Note that we made no restriction to its size, which you might want to do, in case you are afraid of spam filling your message board.
    第22-26行:正文(body)是message的实际内容。注意我们没有限制其长度,如果您担心您的留言簿被垃圾信息填满,您可能就需要设置其长度。
  • Line 33-36: We do not want to allow any content type to be added to a message board. In fact, we just want to be able to add IMessage objects. Therefore we declare a precondition on the <u>setitem</u>() method of the message board interface. Simply list all allowed interfaces as arguments of the ItemTypePrecondition constructor.
    第33 -36行:我们不允许在message board中添加任何其他内容类型。实际上,我们只需要能够加入IMessage对象就好了。因此,我们为messageboard接口的 `setitem()`方法声明一个先决限制条件。只需要简单地把所有允许的接口的列表作为ItemTypePrecondition构造器的参数就可以了。

    Note: Even though IContainer already defined <u>setitem</u>(), we have to declare it here again, so that it is in the scope of the interface and specific to the IMessageBoard; otherwise all IContainer objects will have this precondition.
    注意:尽管IContainer已经定义了`setitem()`方法,我们在这里还必须声明它,这样它就被限定在这个接口的作用范围之内,只针对IMessageBoard;否则,所有ICOntainer对象都会有这个限制条件。
  • Line 38-42: Declare a property on the interface that will contain the description of the message board. It is a typical Text field with the usual options (see “Zope Schemas and Widgets (Forms)” for details). One note though: Notice that we always use unicode strings for human-readable text - this is a required convention throughout Zope 3. One of the major focus points of Zope 3 is internationalization and unicode strings are the first requirement to support multi-lingual applications.
    第38-42行:对包含message board的描述信息(description)的接口声明一个属性。这个属性是一个典型的Text字段。值得一提的是:请注意,我们总是用unicode字符串(这是贯穿Zope3要求的一项约定)来表示易理解的文本,Zope3需要解决的主要问题之一就是国际化,unicdoe字符串是支持多语言应用的首要条件。
  • Line 45-49: This interface describes via field constraint which other content types can contain a message. Clearly message boards can contain messages, but also messages can contain other messages - known as responses. We usually specify this constraint on the parent on the main content type interface (i.e. IMessage) directly, but since this constraint refers explicitely to IMessage we have to wait till the interface is defined.
    第45-49行:接口通过字段限制条件来描述哪种内容类型可用于包含message。显然,message board可以包含messages, 但是message也要能够包含其他messages(为回复信息),我们通常直接在主内容类型接口的父接口上指定该项限制(例如:IMessage), 由于该限制显示地引用IMessage,所以我们要等到该接口被定义完毕后才进行此项定义。
  • Line 52-59: We also want the message to be container, so it can contain responses and attachments. However, we do not want any object to be able to be added to messages, so we add a precondition as we did for the `IMessageBoard` interface. Again, we have to do this in a separate interface here, since we reference IMessage in the condition.
    第52-59行:我们还想让message作为容器,这样它才能包含回复消息和附件。但是,我们不要其他任何对象被加入message,因此我们像 `IMessageBoard` 接口哪样添加一个先决限制。同样地,我们必须在一个独立的接口中做此项工作,因为我们在该条件里引用到了IMessage。

13.4 Step IV: Writing Unit tests(13.4 步骤 IV:编写单元测试)

There are two ways unit tests can be written in Zope 3. The first one is through special TestCase classes using the unittest package, which was modeled after JUnit. The second technique is writing tests inside doc strings, which are commonly known as doc tests.
Zope 3中有两种方式来编写单元测试。第一种是通过使用专门unittest包的TestCase类,这是借鉴JUnit构建的测试模型。第二种技术是在doc strings中编写单元测试,这种方式常被称为doc tests。

Late in the development of Zope 3 doc tests became the standard way of writing tests. For philosophical and technical differences between the two approaches, see the section on “Writing Tests”, especially the “Writing Basic Unit Tests” and “Doctests: Example-driven Unit Tests” chapters.
在今后的Zope3 的开发中,文档测试(doc tests)将成为编写单元测试的编写方法。要了解这两种技术的详细信息,请参见"编写测试"章节,特别是"编写基础单元测试"和"Doctests:案例驱动的单元测试"章节。

Common unit tests, however, are of advantage when it is desirable to reuse abstract tests, as it is the case for various container tests. Therefore, we will use unit tests for the container tests and doc tests for everything else.
通用的单元测试在需要重用抽象测试时具有很大的优点,我们把它作为各种容器测试的范例。因此,我们将采用单元测试进行容器测试,而对其他情况采用文档测试(doc tests)。

First, create a package called tests inside the messageboard package. Note that calling the test module tests (file-based test modules would be called tests.py) is a convention throughout Zope 3 and will allow the automated test runner to pick up the tests.
首先,在messageboard包中创建一个tests的包。注意:在Zope3中,按约定将此测试模块称为tests(基于文件的测试模块应当命名为tests.py),这样自动测试运行器就会运行该测试。

Next, start to edit a file called test_messageboard.py and insert:
下一步,开始编辑test_messageboard.py文件,并插入以下代码:

#!python
import unittest
from zope.testing.doctestunit import DocTestSuite

from zope.app.container.tests.test_icontainer import TestSampleContainer

from book.messageboard.messageboard import MessageBoard


class Test(TestSampleContainer):

def makeTestObject(self):
return MessageBoard()

def test_suite():
return unittest.TestSuite((
DocTestSuite('book.messageboard.messageboard'),
unittest.makeSuite(Test),
))

if <u>name</u> == '<u>main</u>':
unittest.main(defaultTest='test_suite')

A lot of cool stuff just happened here. You just got your first 12 unit tests. Let’s have a closer look:
这里我们会遇见一些非常酷的东西。让我们仔细看看这些代码:

  • Line 1: The unittest module comes with stock Python and is used to create the test suite.
    第1行:Python自带的unittest模块用来创建测试套件。
  • Line 2: Zope provides a specialized DocTestSuite that integrates doc tests into the common unittest framework and allows the doc tests to be run via the test runner.
    第2行:Zope提供了一个专用的 DocTestSuite ,该测试套件把文档测试(doc tests)集成到通用的单元测试框架中,并且使得文档测试可以通过测试运行器来运行。
  • Line 4: There are some basic tests for containers, so we should import them. Freebie tests are always good.
    第4行:免费的测试件总是好的,我们为容器导入一些基本测试。
  • Line 9-13: Define the Container tests. We only have to provide an instance of the container we would like to be tested as the return value of the makeTestObject() method.
    第9-13行:定义容器测试。我们只需要提供要测试容器的实例作为makeTestObject()方法的返回值就可以了。
  • Line 15-19: The test_suite() method collects all the defined test cases and compiles them into one test suite. This method must always be named that way, so that the test runner picks up the suite.
    第15-19行:test_suite()方法收集所有已经定义的测试案例,并将他们编译进一个测试套件中。该方法必须按这样进行命名,这样测试运行器才能够识别到该套件。

    Besides the container test, we also already register the doc tests.
    除了这个容器测试件以外,我们同时还注册了文档测试件(doc tests)。
  • Line 21-23: We also want to allow any test module to be executable by itself. Here we just tell the test runner to execute all tests of the test suite returned by test_suite(). These lines are common boilerplate for any test module in Zope 3.
    第21-23行:我们还要让所有的测试模块能够自己执行。在此,我们只要告诉测试运行器执行所有通过test_suite()返回的测试套件的所有测试。在Zope3的任何测试模块中,这几行是通用的模板。

Now it is time to do the second test module for the IMessage component. To start, we simply copied the test_messageboard.py to test_message.py and modified the new file to become:
现在,我们要为IMessage组件编写第二个测试模块。我们把test_messageboard.py拷贝成test_message.py,然后把新文件改为:

#!python
import unittest
from zope.testing.doctestunit import DocTestSuite

from zope.app.container.tests.test_icontainer import TestSampleContainer

from book.messageboard.message import Message


class Test(TestSampleContainer):

def makeTestObject(self):
return Message()

def test_suite():
return unittest.TestSuite((
DocTestSuite('book.messageboard.message'),
unittest.makeSuite(Test),
))

if <u>name</u> == '<u>main</u>':
unittest.main(defaultTest='test_suite')

There is not really any difference between the two testing modules, so that I am not going to point out the same facts again.
这两个测试模块没有任何本质的区别,因此我就不再逐点列出同样的代码说明了。

Note that none of the tests deal with implementation details yet, simply because we do not know what the implementation details will be. These test could be used by other packages, just as we used the SampleContainer base tests, since these tests only depend on the API. In general, however, tests should cover implementation-specific behavior.
注意这些测试都还没有处理具体的实现细节,因为我们还不知道具体的实现细节会是什么样子呢。就像我们使用 SampleContainer 基础测试件一样,这些测试件还可以被其他包使用,原因在于这些测试仅仅依赖于API。当然,通常来说,测试应当涵盖具体实现的行为。

13.5 Step V: Implementing Content Components(13.5 步骤 V: 内容组件的具体实现)

Now we are finally ready to implement the content components of the package. This is the heart of this chapter. But how do we know which methods and properties we have to implement? There is a neat tool called pyskel.py in ZOPE3/utiltities that generates a skeleton. Go to ZOPE3/src and type:
现在,我们终于准备好要实现这个软件包的内容组件了。这是本章节的核心部分。但是我们如何知道我们究竟应当诠释哪些方法和属性呢? 在ZOPE3/utilites中有一个名为pyskel.py的好工具,它能够生成一个框架。进入ZOPE3/src,然后键入:

1  python2.3 ../utilities/pyskel.py \
2            book.messageboard.interfaces.IMessageBoard

The expected result is shown below. The tool inspects the given interface and creates the skeleton of an implementing class. It also recurses into all base interfaces to get their methods. Here the generated code:
预计的输出结果显示如下。该工具检查给定的接口并创建该接口的实现类的框架。它还会递归进入所有基础接口,并获取他们的方法。以下是生成的代码:

#!python
from zope.interface import implements
from book.messageboard.interfaces import IMessageBoard

class MessageBoard:
<u>doc</u> = IMessageBoard.<u>doc</u>

implements(IMessageBoard)


def <u>setitem</u>(self, name, object):
"See book.messageboard.interfaces.IMessageBoard"

# See book.messageboard.interfaces.IMessageBoard
description = None

def <u>getitem</u>(self, key):
"See zope.interface.common.mapping.IItemMapping"

def get(self, key, default=None):
"See zope.interface.common.mapping.IReadMapping"

def <u>contains</u>(self, key):
"See zope.interface.common.mapping.IReadMapping"

def <u>getitem</u>(self, key):
"See zope.interface.common.mapping.IItemMapping"

def keys(self):
"See zope.interface.common.mapping.IEnumerableMapping"

def <u>iter</u>(self):
"See zope.interface.common.mapping.IEnumerableMapping"

def values(self):
"See zope.interface.common.mapping.IEnumerableMapping"

def items(self):
"See zope.interface.common.mapping.IEnumerableMapping"

def <u>len</u>(self):
"See zope.interface.common.mapping.IEnumerableMapping"

def get(self, key, default=None):
"See zope.interface.common.mapping.IReadMapping"

def <u>contains</u>(self, key):
"See zope.interface.common.mapping.IReadMapping"

def <u>getitem</u>(self, key):
"See zope.interface.common.mapping.IItemMapping"

def <u>setitem</u>(self, name, object):
"See zope.app.container.interfaces.IWriteContainer"

def <u>delitem</u>(self, name):
"See zope.app.container.interfaces.IWriteContainer"

This result is good but some parts are unnecessary; we will for example simply inherit the BTreeContainer base component, so that we do not have to implement the methods from the IReadMapping, IEnumerableMapping, IReadMapping, IItemMapping and IWriteContainer interfaces.
输出的结果已经非常好了,不过还需要另外一些内容;例如我们需要继承 BTreeContainer 基础组件,这样我们就不需要实现来自IReadMapping、IEnumberableMapping、IReadMapping、IItemMapping和 IWriteContainer接口中的方法。

Open a new file called messageboard.py for editing. The implementation of the message board including doc tests looks like this:
打开一个名为messageboard.py的新文件进行编辑。包含文档测试(doc tests)的message board的实现如下所示:

#!python
from zope.interface import implements
from zope.app.container.btree import BTreeContainer

from book.messageboard.interfaces import IMessageBoard

class MessageBoard(BTreeContainer):
"""A very simple implementation of a message board using B-Tree Containers

Make sure that the ``MessageBoard`` implements the ``IMessageBoard``
interface:

>>> from zope.interface.verify import verifyClass
>>> verifyClass(IMessageBoard, MessageBoard)
True

Here is an example of changing the description of the board:

>>> board = MessageBoard()
>>> board.description
u''
>>> board.description = u'Message Board Description'
>>> board.description
u'Message Board Description'
"""
implements(IMessageBoard)

# See book.messageboard.interfaces.IMessageBoard
description = u''
  • Line 1: The implements() method is used to declare that a class implements one or more interfaces. See “An Introduction to Interfaces” for more details.
    第1行:implements()方法用来声明某个类实现了一个或多个接口,详见"接口介绍"。
  • Line 2: Everything that has to do with containers is located in zope. app.container. BTreeContainers are a very efficient implementation of the IContainer interface and are commonly used as base classes for other containerish objects, such as the message board.
    第2行:与容器相关的所有东西都在zope.app.container中,BTreeContainers是IContainer接口的一个非常高效的实现,通常被用作其他容器对象的基类,例如message board的基类。
  • Line 7-24: The class docstring’s purpose is to document the class. To follow Python documentation standards, all docstrings should be using the re-structured text format. And doc tests are considered documentation, so it should be written in a narrative style.
    第7-24行:该类docstring的目的就是对为该类编写文档。按照Python文档的标准,所有的docstrings应当使用re-structured text格式。文档测试(doc tests)被认为是编写文档,因此应当按照某种叙述性的风格进行编写。

    On line 12-14 we verify that the MessageBoard component really implements IMessageBoard. The verifyClass function actually checks the object for the existence of the specified attributes and methods.
    第12-14行,我们验证该MessageBoard组件实现的确实是IMessageBoard,这个验证函数会对该对象存在的指定属性和方法进行实际的检查。

    Lines 18 through 23 just give a demonstration about the default description value and how it can be set. The test seems trivial, but at some point you might change the implementation of the description attribute to using properties and the test should still pass.
    第18-23行只是给出关于缺省描述值以及如何进行设置的演示。这个测试看起来微不足道,但是有时您可能需要使用property来修改这个描述(description)属性的实现并且这个测试应当仍然能够通过。
  • Line 25: Here we tell the class that it implements IMessage. This function call might seem like magic, since one might wonder how the function knows as to which class to assign the interface. For the ones interested, it uses sys.getframe().
    第25行:在此,我们告诉该类,它实现的是IMessage。该函数调用看起来有点神秘,因为可能有人会困惑这个函数是如何知道哪个类要指派给这个接口。告诉有兴趣的读者,它使用的是sys.getframe()。
  • Line 27-28: Make the description a simple attribute.
    第27 -28行:将描述(description)作为一个简单的属性。

Note: Python is very unique in this way. In almost any other object-oriented language (for example Java) one would have written an accessor ( getDescription()) and a mutator ( setDescription(desc)) method. However, Python’s attribute and property support makes this unnecessary, which in turn makes the code cleaner.
注意:Python在这种方式下非常独特。在几乎所有的面向对象语言(例如Java)中,有人可能已经编写了一个 accessor(getDescription())和一个mutator(setDescription(desc))方法。但是,Python的属性和特征能够使这些可有可无,这样也使得代码更加简洁。

The next task is to write the Message object, which is pretty much the same code. Therefore we will not list it here and refer you to the code at http://svn.zope.org/book/trunk/messageboard/step01/message.py. The only difference is that in this case the Message component must implement IMessage, IMessageContained, and IMessageContainer.
下一个任务是写消息对象,差不多是相同的代码。 因此我们将不在这里列出它,您可在 http://svn.zope.org/book/trunk/messageboard/step01/message.py 中去找到相关代码。 唯一不同的是,在该情况下消息组件一定要实现 IMessage, IMessageContained 和 IMessageContainer

13.6 Step VI: Running Unit Tests against Implementation(13.6 步骤 VI: 对实现进行单元测试)

After having finished the implementation, we need to make sure that all the tests pass. There is a script called test.py that will run all or only specified tests for you. To run the test against your implementation, execute the following line from the Zope 3 root directory:
当功能实现完成后,我们需要确保所有的测试通过。这儿有一个名叫test.py的代码文件将运行所有的或您唯一指定的测试。为了运行测试检测您的实现,在Zope3根目录下面执行下面这行命令:

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

The -v option cases the currently running test to be displayed, the -p allows us to see the progress of the tests being run and -u tells the test runner to just run the unit tests. For a list of all available options run the script with the -h (help) option.
-v选项:现在正在运行中的测试被显示;-p选项:允许我们观看测试运行的进度;-u选项:告诉测试者仅进行单元测试。对于所有可使用的运行脚本选项列表可以通过那- h参数获得相关帮助信息。

You should see 26 tests pass. The output at the of the test run should look like this:
您应该看到26个测试通过。测试结束时的输出结果应该象这样:

1  Configuration file found.
2  Running UNIT tests at level 1
3  Running UNIT tests from /opt/zope/Zope3/Zope3-Cookbook
4    26/26 (100.0%): test_values (....messageboard.tests.test_messageboard.Test)
5  ----------------------------------------------------------------------
6  Ran 26 tests in 0.346s
7
8  OK

It is very likely that some tests are failing or the test suite does not even run due to syntax errors. This is totally normal and exactly the reason we write tests in the first place. In these cases keep fixing the problems until all tests are passing.
一些测试很可能失败或者测试套件由于语法错误甚至不运行。这完全是正常的,同时这也是我们写测试的理由,继续修改存在的问题直到所有的测试都通过为止。

13.7 Step VII: Registering the Content Components(13.7 步骤 VII:注册开发好的内容组件)

Now that we have developed our components, it is necessary to tell Zope 3 how to interact with them. This is commonly done using Zope’s own configuration language called ZCML. The configuration is stored in a file called configure.zcml by convention. Start to edit this file and add the following ZCML code:
现在我们已经开发了我们自己的组件,告诉Zope3该如何与他们互动是必需的。通用的方法就是用Zope自己的配置语言ZCML。这个配置文件按约定被存放在configure.zcml文件中。我们现在开始编辑这个文件而且增加如下的ZCML代码:

1  <configure
2      xmlns="http://namespaces.zope.org/zope">
3
4    <interface
5        interface=".interfaces.IMessageBoard"
6        type="zope.app.content.interfaces.IContentType"
7        />
8
9    <content class=".messageboard.MessageBoard">
10      <implements
11          interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
12          />
13      <implements
14          interface="zope.app.container.interfaces.IContentContainer"
15          />
16      <factory
17          id="book.messageboard.MessageBoard"
18          description="Message Board"
19          />
20      <require
21          permission="zope.ManageContent"
22          interface=".interfaces.IMessageBoard"
23          />
24      <require
25          permission="zope.ManageContent"
26          set_schema=".interfaces.IMessageBoard"
27          />
28    </content>
29
30    <interface
31        interface=".interfaces.IMessage"
32        type="zope.app.content.interfaces.IContentType"
33        />
34
35    <content class=".message.Message">
36      <implements
37          interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
38          />
39      <implements
40          interface="zope.app.container.interfaces.IContentContainer"
41          />
42      <require
43          permission="zope.ManageContent"
44          interface=".interfaces.IMessage"
45          />
46      <require
47          permission="zope.ManageContent"
48          interface=".interfaces.IMessageContainer"
49          />
50      <require
51          permission="zope.ManageContent"
52          set_schema=".interfaces.IMessage"
53          />
54    </content>
55
56  </configure>
  • Line 1-2, 65: As the file extension promises, configuration is done using XML. All configuration in a ZCML file must be surrounded by the configure element. At the beginning of the configure element, we list all the ZCML namespaces that we are going use and define the default one. In this case we only need the generic zope namespace. You will get to know many more namespaces as we develop new functionality in the following chapters.
    第1-2行:配置文件使用XML,在ZCML文件中所有的配置必须有配置元素组成。在配置元素的最开始,我们列出所有我们将要使用和定义的缺省ZCML命名空间。当前情况下我们仅需要一个普通的zope命名空间。在接下来各章中,随着我们开发新功能的日益增多,您将接触到更多的命名空间。
  • Line 4-7: It is sometimes necessary to categorize interfaces. One type of category is to specify which interface provides a content type for Zope 3. The zope:interface directive is used to assign these types on interfaces. Another way to think about it is that interfaces are just components, and components can provide other interfaces.
    第4-7行:对接口分类有时候是必要的。其中type将指定哪个接口为Zope3提供内容类型。zope:interface指令用来指派接口上的这些类型。另外需要考虑的是接口就是组件,并且组件能够提供其它接口。
  • Line 9-28: The zope:content directive registers the MessageBoard class as a content component. The element always has only one attribute, class, that points to the component’s class using a dotted Python path.
    第9-28行:zope:content指令以内容组件注册了一个MessageBoard类。这个元素总是只有一个attribute 、class、使用虚Python路径指向组件的类。
    • Line 10-12: In order for the object to have a creation and modification date as well as other meta-data (for example the Dublin Core), we need to tell the system that this object can have annotations associated with itself. This is not necessarily required, but is a good habit. See the chapter on “Using Annotations to Store Meta-Data” for details.


      Annotations store add-on data which is also commonly known as meta-data, since it is data that is not necessary for the correct functioning of the object itself. However, meta-data allows an object to be better integrated in the system. Annotations are heavily used in Zope 3.
      第10-12行:为了让对象有一个创建和修改日期以及其它元数据(例如都柏林核心集),我们需要告诉系统这个对象能和它本身联合注解。这虽不是必需要求,但确是一个良好的习惯。请参看"使用注解存储元数据"获得更多的信息。注解存储被被当作元数据的附加数据,因为它对于对象本身的正确功能是不必需的数据。然而,元数据能让一个对象能够更好的与系统集成。同样,注解也被大量的用在了Zope 3中.

      In general, the zope:implements sub-directive allows you to assert new implemented interfaces on a class. It is totally equivalent to classImplements(Class,`ISomeInterface`) in Python. So why would we want to declare interfaces in ZCML instead of Python? For one, it clutters the Python code and distracts from the actual functionality of the component. Also, when dealing with 3rd party Python packages, we do not want to touch this code, but still be able to make assertions about objects, so that they can be used inside Zope 3 without modification.
      通常,zope:implements子指令允许您在一个类上声明新的应用接口。这完全同于Python中的classImplements(Class,`ISomeInterface`)。但我们为什么想使用ZCML代替Python声明接口呢?原因之一,使用Python方式会弄乱Python代码,并且使组件的实际功能发生了转移。同时,当处理第三方Python软件包时候,我们并不想接触这些代码,但仍想对这些对象做声明,使用ZCML方式就能够使他们不需要对代码做任何修改就能在Zope 3里使用。

      Note that usually only “marker interfaces”, interfaces that have no methods and/or properties, are declared via ZCML, since no additional Python code for the implementation of the interface is required.
      注意:通常通过ZCML声明的只有"marker interfaces(标记接口)"没有方法或属性,因此,接口实现不需要附加的Python代码。
    • Line 13-15: The IContentContainer interface is another example of a marker interface. All that it declares is that this container contains ordinary content in content space, which is clearly the case for our message board.
      第13-15行: IContentContainer 接口是另一个标记接口的实例。它声明的全部内容是:容器在内容空间包含普通内容,这在我们的留言簿中得到了清晰的体现。
    • Line 16-19: The zope:factory sub-directive allows us to register a factory named `book.messageboard.MessageBoard` for the `MessageBoard` component.
      第16-19行:zope:factory子指令允许我们为 `MessageBoard` 组件注册一个名叫`book.messageboard.MessageBoard` 的工厂。

      Every factory needs an id (first directive argument) through which the factory can be accessed and executed. However, you are not required to specify an id; if you don’t, the literal string value of the zope:content’s class attribute is used, which would be `.messageboard.MessageBoard` in this case.
      每个工厂需要一个id(第一个指令参数)来标明哪个工厂能被接入和执行。然而,您没被硬性要求一定要指定一个id;如果没有指定,zope:content类属性文字字符串值将被使用,本例中将是 `messageboard.MessageBoard`。

      The zope:factory directive also supports two human-readable information strings, title and description, that can be used for user interfaces.
      zope:factory指令也支持两个易读的信息串,标题(title)和描述(description),它们被用于用户界面
    • Line 20-27: In Zope 3 we differentiate between trusted and untrusted environments. Trusted environments have no security or can easily circumvent security. And example is file-based Python code, which is always trusted. The opposite is true for untrusted environments; here security should apply everywhere and should not be avoidable. All Web and FTP transactions are considered untrusted.
      第20-27行:在Zope 3 中分为信任和不信任环境。信任环境没有安全或者能轻易绕过安全。此例是就是基于文件的Python代码,属于总是被信任。而不信任环境恰恰相反,在不信任环境中,安全每时每刻都存在于系统中。所有的Web和FTP事务都被认为是不可信任的。

      Of course, we want to use our message board via the Web, since it is the default user interface of Zope 3. To make it usable, we have to declare the minimal security assertions for the properties and methods of our component. Security assertions are done using the zope:require and zope:allow directive.
      当然,我们想通过Web使用我们的留言簿,因为这是Zope 3缺省的的用户接口。为了让它可用,我们必须为我们的组件指派最小的安全声明。做安全声明使用zope:require和zope:allow指令。

      The require directive usually starts out with specifying a permission. Then we have to decide what we want to protect with this declaration. Here are your choices:
      require指令通常表示开始指定一个权限。然后我们必须决定我们想保护这些声明。下面是您的选择:
      • The attributes attribute allows us to specify attributes and methods (note that methods are just callable attributes) that can be accessed if the user has the specified permission.
        如果用户有指定的权限,Attributes允许我们指定能被访问的属性和方法(注意方法就是被调用的属性)。
      • set_attributes allows you to specify individual attributes that can be modified or mutated. Note that you should not list any methods here, since otherwise someone could override a method inserting malicious code.
        set_attributes允许您指定能被修改或变化的个别属性。注意您不应该在这儿列出任何的方法,否则别人可以插人恶意代码覆盖其中的方法。
      • If you specify one or more interfaces using the interface attribute, the directive will automatically extract all declared methods and properties of the interfaces and grant access rights to them.
        如果您使用interface属性指定一个或多个接口,指令将自动选取所有的这些接口声明的方法和属性并且授访问权限给它们。
      • When you specify a set of schemas using the set_schema attribute, then all the defined properties in it are granted modification rights. Methods listed in the schema will be ignored.
        当您使用set_schema属性指定一组模式(schemas)时,所有在它里面定义的属性都被授予了修改权限。在模式(schema)中列出的方法将被忽略。

Note: In ZCML tokens of a list are separated by simple whitespace and not by comma, as you might have expected.


A somewhat different option to the above choices is the like_class attribute, which must be specified without any permission. If used, it simply transfers all the security assertions from the specified class to the class specified in the zope:content directive that encloses the security assertions. In our case this is our MessageBoard component. The usage of the directive looks like this:
注意:正如您所期待的,ZCML中列表记号是以空格分开而不是用逗号。like_class属性略微不同上述选择,它必须是不需要任何的许可就被指定。如果用, 它只是把所有的安全声明从指定的类传递到装入安全声明zope:content指令里指定的类。当前我们的MessageBoard组件,指令的用法如下:

1      <require like_class=".message.Message" />


Here the MessageBoard would simply “inherit” the security assertions made for the Message component.
这儿的MessageBoard将只是继承(inherit)有益于消息组件的安全声明。

The second security directive, zope:allow, either takes a set of attributes or interfaces. All attributes specified will be publicly available for everyone to access. This is equivalent to requiring someone to have the zope.Public permission, which every principal accessing the system automatically possesses.
第二个指令zope:allow也可以操作任何一组属性或接口。每个人都可以访问所有经过指定的属性。这与要求某些人有zope.Public权限是等价的,每个主体(principal)访问该系统会自动拥有。

So now it is easy to decipher the meaning of our two security assertions. We basically gave read and write access to the `IMessageBoard` interface (which includes all IContainer methods and the description attribute), if the user has the zope.ManageContent permission.
现在很容易就可以解释这个两个安全声明的意思。如果用户有zope.ManageContent权限,我们基本上给予了`ImessageBoard`接口了读和写入权限(包括了所有的Icontainer方法和description属性)。

  • Line 30-54: This is the same as above for the Message content component.
    第 30 行-54行: 这与上面的消息内容组件相同。

13.8 Step VIII: Configure some Basic Views(13.8 步骤 VIII:配置一些基本视图)

Even though the content components are registered now, nothing interesting will happen, because there exists only a programmatic way of adding and editing the new components. Thus we are going to define some very basic browser views to make the content components accessible via the browser-based user interface.
尽管内容组件现在已经被注册了, 可程序看上去并不是那么吸引人,原因在于这里只存在通过程序方式增加和编辑新组件,而普通用户则需要通过用户界面来操作。因此,我们将着手定义通过以浏览器界面方式接入的基本视图。

First create a package called browser (do not forget the init.py file) inside the messageboard package. Add a new configuration file, configure.zcml, inside browser and insert the following content:
首先在messageboard包里创建一个browser的包(不要忘记了init.py文件),在该包下面新增一个配置文件configure.zcml,在该文件中插入如下内容:

1  <configure
2      xmlns="http://namespaces.zope.org/browser">
3
4    <addform
5        label="Add Message Board"
6        name="AddMessageBoard.html"
7        schema="book.messageboard.interfaces.IMessageBoard"
8        content_factory="book.messageboard.messageboard.MessageBoard"
9        fields="description"
10        permission="zope.ManageContent"
11        />
12
13    <addMenuItem
14        class="book.messageboard.messageboard.MessageBoard"
15        title="Message Board"
16        description="A Message Board"
17        permission="zope.ManageContent"
18        view="AddMessageBoard.html"
19        />
20
21    <editform
22        schema="book.messageboard.interfaces.IMessageBoard"
23        for="book.messageboard.interfaces.IMessageBoard"
24        label="Change Message Board"
25        name="edit.html"
26        permission="zope.ManageContent"
27        menu="zmi_views" title="Edit"
28        />
29
30    <containerViews
31        for="book.messageboard.interfaces.IMessageBoard"
32        index="zope.View"
33        contents="zope.View"
34        add="zope.ManageContent"
35        />
36
37    <addform
38        label="Add Message"
39        name="AddMessage.html"
40        schema="book.messageboard.interfaces.IMessage"
41        content_factory="book.messageboard.message.Message"
42        fields="title body"
43        permission="zope.ManageContent"
44        />
45
46    <addMenuItem
47        class="book.messageboard.message.Message"
48        title="Message"
49        description="A Message"
50        permission="zope.ManageContent"
51        view="AddMessage.html"
52        />
53
54    <editform
55        schema="book.messageboard.interfaces.IMessage"
56        for="book.messageboard.interfaces.IMessage"
57        label="Change Message"
58        fields="title body"
59        name="edit.html"
60        permission="zope.ManageContent"
61        menu="zmi_views" title="Edit"
62        />
63
64    <containerViews
65        for="book.messageboard.interfaces.IMessage"
66        index="zope.View"
67        contents="zope.View"
68        add="zope.ManageContent"
69        />
70
71  </configure>
  • Line 2: In this configuration file we do not use the zope, but the browser namespace, since we want to configure browser-specific functionality. Also note that browser is the default namespace, so that our directives do not need the namespace prefix.
    第2行:在这个配置文件我们不使用zope, 因为我们想要配置browser包的具体功能。并且注意browser包是缺省的命名空间, 我们的指令不需要命名空间前缀。

    Namespaces for ZCML start commonly with http://namespaces.zope.org/ followed by the short name of the namespace, which is commonly used in this book to refer to namespaces.
    在这本书里常提到命名空间是 http://namespaces.zope.org/。
  • Line 4-11: Register an auto-generated “Add” form for the Message Board.
    第4-11行:为Message Board注册一个自动生成"Add form"。
    • Line 5: The label is a small text that is shown on top of the screen.
      第5行:这个标签是显示在屏幕顶部的小文本。
    • Line 6: The name of the view. The name is the string that is actually part of the URL.
      第6行:这个视图的名称,名称字符串实际上是URL的一部分。
    • Line 7: This defines the schema that will be used to generate the form. The fields of the schema will be used to provide all the necessary meta data to create meaningful form elements.
      第7行:Schema定义将被用于产生表单,Schema中的字段将为创建有实际意义的表单元素提供所必需的元数据。
    • Line 8: The content factory is the class/factory used to create the new content component.
      第8行:内容工厂(The content factory)用于创建新的内容组件。
    • Line 9: The fields are a list of field names that are displayed in the form. This allows you to create forms for a subset of fields in the schema and to change the order of the fields in the form.
      第9行:fields是被显示在表单里的字段名称列表,它允许您创建基于schema的字段子集的表单并且可以改变字段在表单的顺序。
    • Line 10: Specifies the permission required to be able to create and add the new content component.
      第10行:指定能创建和新增内容组件所需要的权限。
  • Line 13-19: After creating a view for adding the message board, we now have to register it with the add menu, which is done with the browser:addMenuItem directive. The title is used to display the item in the menu. The important attribute is the view, which must match the name of the add form.
    第13-19行:当创建了一个新增视图后,我们现在必须以browse:addMenuItem指令注册新增菜单。标题(title)显示在菜单的条目上,重要的是视图必须与新增表单的名称相匹配。
  • Line 21-28: Declaring an edit form is very similar to defining an add form and several of the options/attributes are the same. The main difference is that we do not need to specify a content factory, since the content component already exists.
    第21-28行:声明一个编辑表单(edit form)的工作与定义一个新增表单(add form)非常相似,最主要的不同之处在于我们不需要指定专门的内容工厂(content factory),因为相关的内容组件已经存在。

    The for attribute specifies the interface for which type of component the edit form is for. All view directives (except the browser:addform) require the for attribute. If you would like to register a view for a specific implementation, you can also specify the class in the for attribute.
    For属性为组件的编辑表单(edit form)指定接口。所有的视图指令(除开browser:addform)都要求for属性。如果您愿意为一个具体的实现注册一个视图,您也能为for属性指定类。

    We also commonly specify the menu for edit views directly in the directive using the menu and title attribute as seen on line 27. The zmi_views menu is the menu that creates the tabs on the default Web UI. It contains all views that are specific to the object.
    在27行我们可以看见我们用菜单和标题属性直接为编辑视图(edit views)指定菜单,zmi_views菜单是在缺省的web界面创建tabs。它包含指定对象的所有视图。
  • Line 30-35: The message board is a container and a quick way to register all necessary container-specific views is to use the browser:containerViews directive. Note though that this directive is not very flexible and you should later replace it by writing regular views.
    第30-35行:Message board是容器并且将使用browser:containerViews指令快速注册所有必要的与容器相关的视图,这个指令非常灵活并且稍后您可以编写规则视图代替它。
  • Line 37-69: These are exactly the same directives over again, this time just for the IMessage interface.
    第37-69行:这些确切地说是相同的指令, 不过是针对IMessage 接口。

    In order for the system to know about the view configuration, we need to reference the configuration file in messageboard/configure.zcml. To include the view configuration, add the following line:
    为了能让系统知道视图的配置, 我们需要参考配置文件messageboard/configure.zcml。 也包括视图配置, 增加如下信息:
1  <include package=".browser" />

13.9 Step IX: Registering the Message Board with Zope(13.9 步骤 IX:在 Zope 中注册留言薄)

At this stage we have a complete package. However, other than in Zope 2, you have to register a new package explicitly. That means you have to hook up the components to Zope 3. This is done by creating a new file in ZOPE3/package-includes called messageboard-configure.zcml. The name of the file is not arbitrary and must be of the form *-configure.zcml. The file should just contain one directive:
我们现在已经有了一个完整的包,然而比起Zope2,您必须明确地注册一个新包,那意味着您必须集成组件到Zope3,接下来您需要在ZOPE3/package-includes中创建名为messageboard-configure.zcml的新文件。这个文件名称不是任意的,必须是形式为*-configure.zcml的文件,这个文件应该包含如下指令:

1  <include package="book.messageboard" />

When Zope 3 boots, it will walk through each file of this directory and execute the ZCML directives inside each file. Usually the files just point to the configuration of a package.
当Zope3 启动时,它将遍历在这个目录中的每个文件并且在每个文件中执行ZCML指令,通常这些文件就指向包的配置中。

13.10 Step X: Testing the Content Component(13.10 步骤 X:测试内容组件)

Congratulations! You have just finished your first little Zope 3 application, which is quiet a bit more than what would minimally be required as you will see in a moment. It is time now to harvest the fruits of your hard work. Start your Zope 3 server now, best using makerun from the Zope 3 root. If you get complains about the Python version being used, edit the Makefile and enter the correct path to Python’s executable. Other errors that might occur are due to typos or mis-configurations. The ZCML interpreter will give you the line and column number of the failing directive in Emacs-friendly format. Try to start Zope 3 again and again until you have fixed all the errors and Zope 3 starts up ending with this output:
恭喜恭喜! 您已经完成Zope3 的一个小应用, 尽管小但您仍需要花些时间在这上面。现在是您付出辛勤劳动后收获成功果实的时候。请启动您的Zope3服务器,最好从Zope3根目录运行makerun.在使用时,如果由于Python版本而引起一些问题,请编辑Makefile并且进入Python正确的路径执行。其它错误有可能是由错别字或错误的配置而引起的。ZCML翻译器将以Emacs-friendly格式显示错误指令所在的行号和列号。请试着反复启动Zope3直到您已经修正了所有的错误并且Zope3能正确运行您的成果:

1  ------
2  2003-12-12T23:14:58 INFO PublisherHTTPServer zope.server.http (HTTP) started.
3          Hostname: localhost
4          Port: 8080
5  ------
6  2003-12-12T23:14:58 INFO PublisherFTPServer zope.server.ftp started.
7          Hostname: localhost
8          Port: 8021
9  ------
10  2003-12-12T23:14:58 INFO root Startup time: 11.259 sec real, 5.150 sec CPU

Note that you also get some internationalization warnings, which you can safely ignore for now.
注意您可能会得到一些国际化警告,不过暂时您可以忽略它。

Once the server is up and running, go to your favorite browser and display the following URL:
一旦服务器已经运行,请使用您的浏览器运行如下地址:

http://localhost:8080/@@contents.html

At this point an authentication box should pop up and ask you for your username and password - users are listed in the principals.zcml. If you have not added any special user, use “gandalf” as the login name and “123” as password. After the authentication is complete you should be taken to the Zope 3 Web user interface. Under Add: you can now see a new entry “Message Board”.
这时将会弹出一个要您输入用户名和密码的认证框,用户将在principals.zcml文件中列出。如果您未增加任何特别用户, 请使用gandalf作为登陆名、123作为密码。认证结束后您将进入到Zope3的web用户界面。现在您可以看到"Message Board"条目。

Feel free to add and edit a message board object.
您可以自由增加和编辑message board对象

Once you created a message board, you can click on it and enter it. You will now notice that you are only allowed to add “Message” objects here. The choice is limited due to the conditions we specified in the interfaces. The default view will be the “Edit” form that allows you to change the description of the board. The second view is the “Contents” with which you can manage the messages of the message board.
一旦您创建了一个message board,您能单击它以及进入它。现在注意了, 系统只允许您在这儿添加Message对象。之所以选择有限是由于我们在接口中的指定了条件。另外,缺省视图是编辑表单(Edit form),该表单允许您改变留言簿描述(description)。第二个视图允许您管理留言簿中的消息内容。

Add a “Message” now. Once you added a message, it will appear in the “Contents” view. You can now click on the message. This will allow you to modify the data about the message and add new messages (replies) to it. With the code we wrote so far, you are now able to create a complete message board tree and access it via the Web UI.
先增加一个Message,一旦您增加了一则消息, 它将出现在Contents视图中。 然后您能单击查看您添加的消息。系统允许您修改消息数据以及增加新消息(回复)。好了,我们就此打住,您现在能创建一棵完整的留言簿树并且也能通过Web界面来访问该留言簿。

Note that you still might get errors, in which case you need to fix them. Most often you have security problems, which narrows the range of possible issues tremendously. Unfortunately, NotFoundError is usually converted to ForbiddenAttributeError, so be careful, if you see this problem.
不过,这儿仍然存在着一些需要您来修正的错误。经常会是安全问题,这也许能使查找的范围变得小些。不幸地是 NotFoundError 通常被转换成ForbiddenAttributeError, 因此如果您看到这种问题后请小心行事。

Another common trap is that standard error screens do not show the traceback. However, for these situations the Debug skin comes in handy - instead of http://localhost:8080/@@contents.html use http://localhost:8080/++skin++Debug/@@contents.html and the traceback will be shown.
其它缺陷是标准的错误界面不显示traceback 。 然而,为了让界面调试变得更为得心应手, 可以用 `<ulink url="http://localhost:8080/++skin++Debug/@@contents.html"/>` 代替 `<ulink url="http://localhost:8080/@@contents.html"/>` ,代替后HTML 和traceback 将被显示。

Note: If you make data-structural changes in your package, it might become necessary to delete old instances of the objects/components. Sometimes even this is not enough, so that you have to either delete the parent Folder or best delete the Data.fs (ZODB) file. There are ways to upgrade gracefully to new versions of objects, but during development the listed methods are simpler and faster.
注意: 如果您在您的包内做了数据结构方面的变动, 在objects/components中删除旧的实例就变得很有必要了,不过,有时光删除实例还不够,甚至您还必须删除父文件夹或者最好是删除数据库文件Data.fs (ZODB)。这些都是更新一个新版本对象可以采取的一些有效的方法。不过最好是在开发期间列出所有的方法,这样就会使得开发过程更加简单和快速。

The code is available in the Zope SVN under http://svn.zope.org/book/trunk/messageboard/step01.
在 Zope SVN 中提供了该示例的代码: http://svn.zope.org/book/trunk/messageboard/step01