译者 sunbaby,jzx++ 
如要转载请注明作者

不好意思,我们第一次翻译,如有翻译的不正确的地方,欢迎指正。来信请到 [email protected] 谢谢

::-- ZoomQuiet DateTime(2005-08-22T07:12:30Z)

Contents

3. Zope 产品

3.1 概论

Zope产品具有新的功能,扩展了Zope。它们通常提供新可增加对象,但也通过新DTML标签,基于类的新ZClass,以及其他服务扩展Zope。

在Zope里有两种方式创建产品:一是通过WEB,二是文件系统中的文件。本章将讨论如何在文件系统上创建产品。至于通过WEB创建产品和ZCLASS的有关信息,请参见Zope Book,第12章。

相对于WEB产品,文件系统产品开发费用更高,但功能及弹性更强,而且他们可以通过比较熟悉的工具,如Emacs和CVS开发。

本章所涉及的产品例子,很快我们将提供网上下载,到那时,目前还不能看的本章中的一些文件参考将可用。 3.2 开发过程

本章首先讨论如何开发产品,着重于在开发产品过程中所遇到的普通工程任务。

3.2.1 考虑其它方法

在开发产品前,你首先应该考虑一下你的问题是否可以用ZCLASS、外部方法或Python脚本来解决更好。产品在扩展Zope用对象的可增加新类具有优越性,如果这点恰与你的解决方案不匹配,你应当寻找别的方案。产品像外部方法那样,允许在文件系统中无约束的写Python代码。

3.2.2 从接口开始

创建PRODUCT的第一步是创建PRODUCT中所描述的一或多个接口,如何创建接口及相关详细信息请参见第一章。

应当在开始执行前创建接口,因为这有利于你的设计和更好的评估它满足你的需求的情况。

   1    from Interface import Base
   2    class Poll(Base):
   3        "A multiple choice poll"
   4 
   5        def castVote(self, index):
   6            "Votes for a choice"
   7 
   8        def getTotalVotes(self):
   9            "Returns total number of votes cast"
  10 
  11        def getVotesFor(self, index):
  12            "Returns number of votes cast for a given response"
  13 
  14        def getResponses(self):
  15            "Returns the sequence of responses"
  16 
  17        def getQuestion(self):
  18            "Returns the question"

可以随意命名接口,但是不要用如"I"及其它特殊的标识符。

3.2.3 实现接口

为你的PRODUCT定义了一个接口后,下一步是在Python中创建一个原型来实现它

  • 这是一个Poll实现类的原型,用来实现刚才的接口

   1    from Poll import Poll
   2 
   3    class PollImplementation:
   4        """
   5        A multiple choice poll, implements the Poll interface.
   6 
   7        The poll has a question and a sequence of responses. Votes
   8        are stored in a dictionary which maps response indexes to a
   9        number of votes.
  10        """
  11 
  12        __implements__=Poll
  13 
  14        def __init__(self, question, responses):
  15            self._question = question
  16            self._responses = responses
  17            self._votes = {}
  18            for i in range(len(responses)):
  19                self._votes[i] = 0
  20 
  21        def castVote(self, index):
  22            "Votes for a choice"
  23            self._votes[index] = self._votes[index] + 1
  24 
  25        def getTotalVotes(self):
  26            "Returns total number of votes cast"
  27            total = 0
  28            for v in self._votes.values():
  29                total = total + v
  30            return total
  31 
  32        def getVotesFor(self, index):
  33            "Returns number of votes cast for a given response"
  34            return self._votes[index]
  35 
  36        def getResponses(self):
  37            "Returns the sequence of responses"
  38            return tuple(self._responses)
  39 
  40        def getQuestion(self):
  41            "Returns the question"
  42            return self._question

可以交互使用并测试此类。下面是一个交互测试的例子。

   >>> from PollImplementation import PollImplementation
   >>> p=PollImplementation("What's your favorite color?", ["Red",
"Green", "Blue", "I forget"])
   >>> p.getQuestion()
   "What's your favorite color?"
   >>> p.getResponses()
   ('Red', 'Green', 'Blue', 'I forget')
   >>> p.getVotesFor(0)
   0
   >>> p.castVote(0)
   >>> p.getVotesFor(0)
   1
   >>> p.castVote(2)
   >>> p.getTotalVotes()
   2
   >>> p.castVote(4)
   Traceback (innermost last):
   File "<stdin>", line 1, in ?
   File "PollImplementation.py", line 23, in castVote
   self._votes[index] = self._votes[index] + 1
   KeyError: 4

交互测试提供了一种简单但强有力的测试代码的途径,是Python的一大优越性,通过不断的测试和提炼接口及执行它们的类。对于测试的有关详情,请参见第七章。

目前为止,我们已经知道如何创建Python类,并用接口及测试证实。下一步将检验Zope product构架,然后你将学会如何用构架来把你的Python类组成一个product框架。

3.3 构建Product类

把一个组件转换成产品需要满足很多规范,这些规范大部分在接口中定义,从基类子类化出符合规范的产品,这复杂化了产品的建立,这一点我们正努力完善和提高。

3.3.1 基类

  • 看以下product类定义的例子:

   1    from Acquisition import Implicit
   2    from Globals import Persistent
   3    from AccessControl.Role import RoleManager
   4    from OFS.SimpleItem import Item
   5 
   6    class PollProduct(Implicit, Persistent, RoleManager, Item):
   7        """
   8        Poll product class
   9        """
  10        ...

基类的顺序取决于你想要的优先级,大多数Zope类不会定义相似的名字,因此通常不用担心product中这些类的顺序。让我们逐一看一下这些基类。

3.3.1.1 Acquisition.Implicit

  • Acquisition.Implicit是acquisition的标准基类,详情参见API

Reference,ZOPE的许多服务,如对象Publishing、安全对象。

因此,Acquisition.Implicit类是products所需要的。事实上,你可以选择从Acquisition.Explicit继承,但它会组织从类的实例中动态绑定的Python Scripts 和 DTML Methods,通常你需要从Acquisition.Implicit中继承。

3.3.1.2 Globals.Persistent

  • 此基类让你可以创建持久化产品实例(makes

instances)。关于"持久化"及此类的信息,参见第4章。

为了使你的Poll类持久化,你需要做一点变化。既然_votes是变化不定的非持久化子对象字典,你改变它时,需要让永久性对象知道。

   1    def castVote(self, index):
   2        "Votes for a choice"
   3        self._votes[index] = self._votes[index] + 1
   4        self._p_changed = 1

此方法的最后一行把_p_changed的属性置1,通知持久对象已经改变并标记为脏,这意味着当前事务需要把新状态写进数据库。更详细的解释见Persistence 章节。

3.3.1.3 OFS.SimpleItem.Item

这个基类提供了使你的产品对象可以工作在ZMI中,通过从Item继承,你的产品增加了很多功能(剪切/粘贴/查看、WebDAV/FTP支持、undo支持、ownership支持、Traversal、各种标准ZMI视图、错误信息显示),具备getId()/title_or_id()/title_and_id()/this()等DTML方法,支持dtml-tree标签。 注意: Item需要你的产品类设置以下属性:meta_type、id or name、title

  • meta_type:
    • 此属性类型是一short

string,也是产品出现在产品添加列表中的名字。例如,poll product类的meta_type是Poll

  • id or name

    • 所有的Item实例都必须有一个string类型的id属性.

用来唯一的标识容器中的实例。你也可以用name来代替id属性

  • title :

所有的Item实例都必须有一个string类型的title属性. 如果你的实例没有title可以保持title为空字符串

  • 为了让你的poll

class就像一个Item一样正常工作,需要做些改动。必须增加meta_type

  • 属性, 也可以给构造函数添加一id 参数。
    • 代码如下:

   1    class PollProduct(..., Item):
   2        meta_type='Poll'
   3        ...
   4        def __init__(self, id, question, responses):
   5            self.id=id
   6            self._question = question
   7            self._responses = responses
   8            self._votes = {}
   9            for i in range(len(responses)):
  10                self._votes[i] = 0

最后,你应该把Item放在基类列表最后,因为Item提供如ObjectManagerPropertyManager类的默认override功能。你可以在Item中override 排在前面的类的方法

3.3.1.4 AccessControl.Role.RoleManager

这个类提供了是你的产品能够通过ZMI定义安全策略。关于安全策略的更多信息,参见第6章。

3.3.1.5 OFS.ObjectManager

这个基类提供了似你的产品可以包含其它的Item实例,换句话说,可以让你的产品就像Zope文件夹一样,这个基类是可选的,参考API参考可以获取更多信息。

这个基类提供了添加Zope对象,导入和导出Zope对象,WebDAV和FTP,它也提供了 objectids,objectValues,objectItems方法。

ObjectManager对于子类化它几乎没有什么要求,一般而言你不需要override它的方法

如果希望控制product的实例所包含对象的类型,可通过设置meta_types类属性实现,,meta_type属性一般用来创建专门的容器产品

3.3.1.6 OFS.PropertyManager

这个基类提供了使实例具有可以被用户管理的属性,查看API手册可以获得更多的信息,该基类是可选的。

你的类可以指定一个或多个预先定义的属性,通过设置_properties属性,例如

   1    _properties=({'id':'title', 'type': 'string', 'mode': 'w'},
   2             {'id':'color', 'type': 'string', 'mode': 'w'},
   3             )

_properties结构是一个Sequence字典,其中每个字典代表一预定义的属性。需注意的是如果在_properties structure中定义一个预定义的property ,你必须在类或者实例中提供一个相同名字的属性(可以包含默认值或者是预定义的)

_properties结构的每个实体至少需要一个id和一个type键。其中id是property的名字,type是对象的类型字符串,而且类型必须是以下内容之一:float, int, long, string, lines, text, date, tokens, selection, or multiple section.想了解更多的关于Zope properties的信息,参见Zope Book.

  • 对于selection 和 multiple selection

properties,需要在属性字典中包括一addition item。select_variable 提供属性或方法的名字,返回被选中的字符串列表,如:

   1    _properties=({'id' : 'favorite_color',
   2              'type' : 'selection',
   3              'select_variable' : 'getColors'
   4              })
  • 在_properties

structure的每个entry中可能存在一可选项mode key,此键标识多种多样的属性。如果mode string需要表示,必须是w,d,或wd。

  • 在mode string中,w 表示属性的值可以被用户更改,d

表示用户可以删除属性。一个空的empty mode表示此属性,并且此属性的值可以在属性列表中显示,但是其值为只读属性,不能被删除。

  • 在_properties structure的Entries中,如果没有mode

项目,默认为有一mode wd (可更改和删除)。

3.3.2 安全声明

把component转换成product,除了继承诸多标准基类外,还必须declare security information(声明有关安全信息)。关于安全信息及应用指导,详情见第6章。

  • 下面是一如何对poll 类声明安全的范例:

   1    from AccessControl import ClassSecurityInfo
   2 
   3    class PollProduct(...):
   4        ...
   5 
   6        security=ClassSecurityInfo()
   7 
   8        security.declareProtected('Use Poll', 'castVote')
   9        def castVote(self, index):
  10            ...
  11 
  12        security.declareProtected('View Poll results', 'getTotalVotes')
  13 
  14        def getTotalVotes(self):
  15            ...
  16 
  17        security.declareProtected('View Poll results', 'getVotesFor')
  18        def getVotesFor(self, index):
  19            ...
  20 
  21        security.declarePublic('getResponses')
  22        def getResponses(self):
  23            ...
  24 
  25        security.declarePublic('getQuestion')
  26        def getQuestion(self):
  27            ...

为了声明安全信息,需要到Zope中设置初始化一下产品类,下面是一如何初始化poll class的例子:

   1    from Globals import InitializeClass
   2 
   3    class PollProduct(...):
   4       ...
   5 
   6    InitializeClass(PollProduct)

3.3.3 总结

  • 祝贺你已经创建了一product类。全部代码如下

   1    from Poll import Poll
   2    from AccessControl import ClassSecurityInfo
   3    from Globals import InitializeClass
   4    from Acquisition import Implicit
   5    from Globals import Persistent
   6    from AccessControl.Role import RoleManager
   7    from OFS.SimpleItem import Item
   8 
   9    class PollProduct(Implicit, Persistent, RoleManager, Item):
  10        """
  11        Poll product class, implements Poll interface.
  12 
  13        The poll has a question and a sequence of responses. Votes
  14        are stored in a dictionary which maps response indexes to a
  15        number of votes.
  16        """
  17 
  18        __implements__=Poll
  19 
  20        meta_type='Poll'
  21 
  22        security=ClassSecurityInfo()
  23 
  24        def __init__(self, id, question, responses):
  25            self.id=id
  26            self._question = question
  27            self._responses = responses
  28            self._votes = {}
  29            for i in range(len(responses)):
  30                self._votes[i] = 0
  31 
  32        security.declareProtected('Use Poll', 'castVote')
  33        def castVote(self, index):
  34            "Votes for a choice"
  35            self._votes[index] = self._votes[index] + 1
  36            self._p_changed = 1
  37 
  38        security.declareProtected('View Poll results', 'getTotalVotes')
  39 
  40        def getTotalVotes(self):
  41            "Returns total number of votes cast"
  42            total = 0
  43            for v in self._votes.values():
  44                total = total + v
  45            return total
  46 
  47        security.declareProtected('View Poll results', 'getVotesFor')
  48        def getVotesFor(self, index):
  49            "Returns number of votes cast for a given response"
  50            return self._votes[index]
  51 
  52        security.declarePublic('getResponses')
  53        def getResponses(self):
  54            "Returns the sequence of responses"
  55            return tuple(self._responses)
  56 
  57        security.declarePublic('getQuestion')
  58        def getQuestion(self):
  59            "Returns the question"
  60            return self._question
  61 
  62    InitializeClass(Poll)
  • 现在该在Zope中测试product

class了,测试前须在Zope中注册product class。

3.4 注册Products

  • Products是lib/python/Products.下的Python

包(packages),在Zope启动时被载入,此过程称产品初始化。通过产品初始化,每个产品都会register它的功能。 3.4.1 Product初始化

  • 当Zope起动时,导入每个产品并调用产品的initialize

函数,并且传给它一个registrar object。此initialize 函数通过registrar告诉Zope它的功能。下面是一init.py file的例子:

   1    from PollProduct import PollProduct, addForm, addFunction
   2 
   3    def initialize(registrar):
   4        registrar.registerClass(
   5            PollProduct,
   6            constructors = (addForm, addFunction),
   7            )
  • 此函数通过registrar

object把类作为一个可增加对象来注册。registrar通过类的meta_type找出位于在product add list中的名字。Zope会根据类的meta-type自动找出一个权限名字,在这里例子中叫做 Add Polls(Zope会自动以产品的类名后加"s"),构造的参数是由两个函数组成的对象,一个是add form,另一个是add method。

  • Add form在用户从Produc add list中选择对象时调用. Add method是被Add form调用的方法。 这些函数收到constructor permission的保护

注意你无法限制containers包含类的实例的类型。换句话说,如果类已经注册的话,假如用户拥有constructor允许权限,此类将显示在product add list中。

Reference。

3.4.2 工厂和构造

Factories允许创建Zope对象,对象可加入到folders,也允许创建其他对象管理器。(关于Factories,参见Zope Book的第12章)。一个Factory的基本功能是把类名放到product add list中,并为此名字联一个permission和action。如果你有允许权限,将在product add list中看到此类名,当选中此类名时,action method被调用。

  • Products利用Zope factory可以在product add

list中创建Product类的实例。在上面的product初始化例子中,我们知道了product registrar如何创建一个factory,下一步我们将看一下如何创建add form和add list。

  • add form是一个允许用户为你的product

class创建instance的函数,返回值为HTML form。通常此HTML form收集instance的id,title及其他相关数据。下面是poll class的add form函数的简单例子:

   1    def addForm():
   2        """
   3        Returns an HTML form.
   4        """
   5        return """<html>
   6        <head><title>Add Poll</title></head>
   7        <body>
   8        <form action="addFunction">
   9        id <input type="type" name="id"><br>
  10        question <input type="type" name="question"><br>
  11        responses (one per line)
  12        <textarea name="responses:lines"></textarea>
  13        </form>
  14        </body>
  15        </html>"""
  • 注意form的action为何是addFunction,而且response

的lines被排成一sequence。关于argument marshalling和object publishing的更多信息,参见第2章。

  • 在add form中包括一个HTML head

tag是非常重要的,因为这样Zope可以设置一个基本的URL以确保与addFunction 相关连接正常工作。

function,第一个参数是产品添加的位置(通常是Folder)。Add function 也可以传入任何根据一般对象发布规则发布的form变量

  • 下面是一个poll类的add function:

   1    def addFunction(dispatcher, id, question, responses):
   2        """
   3        Create a new poll and add it to myself
   4        """
   5        p=PollProduct(id, question, responses)
   6        dispatcher.Destination()._setObject(id, p)
  • Dispatcher 有三个方法:

Destination:product被加进的对象管理器(ObjectManager )的位置

  • 注意为了把poll加到folder,它是如何调用ObjectManager

类的_setObject() 方法的的。关于ObjectManager 接口的更多信息,参见API Reference

  • add

function应当检查其输入的有效性。如果有问题问题或参数不是正确类型,add function应当提示。

  • 最后,应当意识到constructor函数不是product

class的方法,实际上,它们(constructor函数)在product class的任何instance被调用前就被调用;构造器功能需要有doc strings才能发布在web上,并在产品初始化是被权限设置所保护。

3.4.3 测试

  • 现在你可以在Zope中注册你的product。首先需要把add

form和add method加入到poll模块中,然后在lib/python/Products 目录下创建Poll 目录,并把Poll.py, PollProduct.py, 和 init.py 文件加入到该目录下,最后重启Zope。

以manager身份登录ZMI。在控制面板的Products目录下,你将看到一Poll product列表;如果Zope初始化product出错,这儿将显示traceback,如果product有错误,改正后重起Zope。如果重起Zope你嫌太麻烦,可以参考第7章的Refresh facility。

现在转到根目录下,从产品添加列表中选择Poll。注意添加到add form需要:提供一id, 一个question,一个responses列表,单击Add即可。出现了黑屏,是因为你的add method没有任何返回值。而且你发现,poll有一个broken icon(图标),且仅有management views,先不要管它,下一部分我们将讨论如何解决这些问题。

  • 现在你需要创建一些DTML Methods和Python

Scripts来测试你的poll instance。下面是计算投票百分比的Script:

   1        ## Script (Python) "getPercentFor"
   2        ##parameters=index
   3        ##
   4        """
   5        Returns the percentage of the vote given a response index.
   6 Note,
   7        this script should be bound a poll by acquisition context.
   8        """
   9        poll=context
  10        return float(poll.getVotesFor(index)) / poll.getTotalVotes()
  • 下面是一个显示投票结果并允许你投票的DTML

Method:

       <dtml-var standard_html_header>
       <h2>
         <dtml-var getQuestion>
       </h2>
       <form> <!-- calls this dtml method -->
       <dtml-in getResponses>
         <p>
           <input type="radio" name="index"
value="&dtml-sequence-index;">
           <dtml-var sequence-item>
         </p>
       </dtml-in>
       <input type="submit" value=" Vote ">
       </form>
       <!-- process form -->
       <dtml-if index>
         <dtml-call expr="castVote(index)">
       </dtml-if>
       <!-- display results -->
       <h2>Results</h2>
       <p><dtml-var getTotalVotes> votes cast</p>
       <dtml-in getResponses>
         <p>
           <dtml-var sequence-item> -
           <dtml-var expr="getPercentFor(_.get('sequence-index'))">%
         </p>
       </dtml-in>
       <dtml-var standard_html_footer>
  • 用此DTML Method,在poll

实例里调用它即可。注意DTML是如何调用poll实例和getPercentFor Python script。

在这一点,需要大量的测试的提炼工作。每改动一点product class,都必须重起Zope,这点非常讨厌。可参考第7章有关如何避免重起Zope的信息。如果你大量的改动你的class,可能破坏现存的poll instances,这样你需要删除这些instances,并创建新的instances。参见第7章有关的调试技巧信息。

3.5 创建管理界面

既然现在有了可以工作的product,让我们看一下如何如何创建用户界面和在线管理界面。

3.5.1 定义管理Views

  • 所有的Zope

products可通过web管理。Products具有管理的tabs或views,允许管理者来配置product。

  • Product的管理views在manage_options class

attribute(类属性)中定义,下面是一个例子:

   1    manage_options=(
   2        {'label' : 'Edit', 'action' : 'editMethod'},
   3        {'label' : 'View', 'action' : 'viewMethod'},
   4        )
  • manage_options

structure是一个含有字典的tuple,每个字典定义一个management view,view字典含有数个items。

  • label :是management view的名字。 action

:是被选view被调用时的URL,通常是显示management view的一个方法的名字。

  • target :显示action的一个可选的target

frame。一般很少会用到。

  • help

:可选项。有关view的帮助信息。以后的章节可以学到有关help的更多内容。

  • Management

views按定义的顺序显示,但是只有当前用户有允许权限的这些Management views才能显示出来。这就意味着不同的用户在管理product时,看到的management views不同。

  • 通常定义一些custom

views和重用一些已经存在的基类中已被定义的views。下面是一例子:

   1        class PollProduct(..., Item):
   2        ...
   3        manage_options=(
   4            {'label' : 'Edit', 'action' : 'editMethod'},
   5            {'label' : 'Options', 'action' : 'optionsMethod'},
   6            ) + RoleManager.manage_options + Item.manage_options
  • 这个例子中包含标准的被RoleManager 定义的management

view,包含被Item 定义的Security,Undo和Ownership,一般情况下你应该包含着这些标准管理view。如果你的类具有一个默认index_html,那么你应该包含一个action为空的View View,关于index_html 更多信息,参见第2章。

  • 注意:不应当把View

view作为类的第一个view,因为当单击Zope management interface的一个对象时,将显示第一个management view。如果View view被第一个显示,view tabs将不可见,这样用户就不能浏览其他的management views。

3.5.2 创建管理 Views

  • 通常用DTML创建管理view 方法,也可以用DTMLFile

类从文件中创建DTML 方法,例如:

   1        from Globals import DTMLFile
   2           class PollProduct(...):
   3               ...
   4           editForm=DTMLFile('dtml/edit', globals())
  • 这样就在你的类里创建了一个DTML

Method,此方法在dtml/edit.dtml 文件中定义。注意你不需要包含.dtml 扩展文件,而且也不用担心作为路径分隔符会出问题,一般这样的转换在Windows上都可以运行。通过转换,DTML伟建就会放置到产品的dtml子目录里。

  • DTMLFile 构造方法的globals()

参数作用是允许其定位product 目录。如果你在调试模式下运 行Zope,DTML 文件的变化立刻被反映出来,也就是说,你无须重起Zope,就可以看到这些改变。

DTML 类的方法和其他方法一样,可以通过web直接调用。因此用户可以通过调用poll 类的实例的editForm 方法来显示编辑表单。通常DTML方法是用来在实例中收集显示信息的。你可以用一般的方法包装你的DTML方法,这样你可以在调用它之前计算DTML所需要的信息,也保证用户总是通过你的包装对象来访问DTML,参见下面例子:

   1    from Globals import DTMLFile
   2        class PollProduct(...):
   3            ...
   4        _editForm=DTMLFile('dtml/edit', globals())
   5        def editForm(self, ...):
   6            ...
   7            return self._editForm(REQUEST, ...)
  • 在创建管理views时,应当包括DTML变量。 manage_page_header 和 manage_tabs 在顶部,manage_page_footer

在底部。Product需要这些变量构建一个标准的管理view header, tabs widgets, 和 footer.其中,管理 view header包括CSS信息,这样如果需要你可以方便的向management views中加入CSS类型信息。管理 CSS信息在lib/python/App/dtml/manage_page_style.css.dtml文件中定义,下面是在此文件中定义的CSS类和在使用时的所作的转换的例子:

  • form-help

:与forms相关的解释文本。以后,用户可能可以隐藏此文本。

std-text:也forms无关的所声称的文本。可能很少用到此项。

  • form-title:Form的Titles。 form-label:Form元素的显示labels form-optional:Form的可选元素的Form labels。 form-element:Form元素。注意:由于Netscape

bug,此类在textarea 元素中不可用。

  • form-text:Forms中的宣称文本。 form-mono:Forms中固定宽度的文本。很少用到此类。 下面是poll 类的一个管理view的例子,它允许编辑poll

问题和答案(见editPollForm.dtml):

       <dtml-var manage_page_header>
       <dtml-var manage_tabs>

       <p class="form-help">
       This form allows you to change the poll's question and
       responses. <b>Changing a poll's question and responses
       will reset the poll's vote tally.</b>.
       </p>
       <form action="editPoll">
       <table>
         <tr valign="top">
           <th class="form-label">Question</th>
           <td><input type="text" name="question" class="form-element"
           value="&dtml-getQuestion;"></td>
         </tr>
         <tr valign="top">
           <th class="form-label">Responses</th>
           <td><textarea name="responses:lines" cols="50" rows="10">
           <dtml-in getResponses>
           <dtml-var sequence-item html_quote>
           </dtml-in>
           </textarea>
           </td>
         </tr>
         <tr>
           <td></td>
           <td><input type="submit" value="Change"
class="form-element"></td>
         </tr>
       </table>
       </form>
       <dtml-var manage_page_header>
  • 这个DTML方法显示一个edit

form允许改变问题及答案,注意poll属性是个HTML引用,它的值为在dtml-var中使用的html_quoted标记或者是dtml-var实体类型

假定这个DTML保存在product的dtml目录下的editPollForm.dtml 文件中,下面是在你的类中如何定义此方法:

   1        class PollProduct(...):
   2            ...
   3            security.declareProtected('View management screens', 'editPollForm')
   4            editPollForm=DTML('dtml/editPollForm', globals())
  • 注意edit form受View management screens

允许权限保护,这确保了只有管理者才能调用此方法。

注意这个form的action也是editPoll.既然poll不包括任何编辑方法,需要定义一个来接受改变,下面是一个editPoll 方法。

   1        class PollProduct(...):
   2            ...
   3 
   4        def __init__(self, id, question, responses):
   5            self.id=id
   6            self.editPoll(question, response)
   7 
   8            ...
   9 
  10        security.declareProtected('Change Poll', 'editPoll')
  11        def editPoll(self, question, responses):
  12            """
  13            Changes the question and responses.
  14            """
  15            self._question = question
  16            self._responses = responses
  17            self._votes = {}
  18            for i in range(len(responses)):
  19                self._votes[i] = 0
  • 注意init

方法如何通过新的editPoll方法被反射,另外,注意editPoll方法改变Poll时是如何被一个新的权限保护的。

  • editPoll

方法仍然有一个问题:当从WEB调用editPollForm时并没有返回任何东西,这样的管理界面比较糟糕。

  • 当从WEB调用时,你希望返回一个HTML

响应,但从init调用时,不希望返回HTML响应。下面是解决方法:

   1    class Poll(...):
   2        ...
   3        def editPoll(self, question, responses, REQUEST=None):
   4            """
   5            Changes the question and responses.
   6            """
   7            self._question = question
   8            self._responses = responses
   9            self._votes = {}
  10            for i in range(len(responses)):
  11                self._votes[i] = 0
  12            if REQUEST is not None:
  13                return self.editPollForm(REQUEST,
  14                    manage_tabs_message='Poll question and responses
  15 changed.')
  • 如果此方法从WEB调用,Zope将自动提供一个REQUEST

参数,关于object publishing更多信息,参见第2章。这样通过REQUEST ,你可以判断此方法是否从WEB调用,如果从WEB调用,就再返回edit表单

  • 一个有关management

界面的约定,应当用manage_tab_message DTML变量,如果在调用管理view时设置此变量,它将在页面顶部显示一条状态信息,利用此状态信息提供给用户反馈信息,告诉用户他们的actions已经生效(这些actions改变不容易看出)。例如,如果不为editPoll方法返回状态信息,用户可能意识不到所做的改动。

有时在显示管理views时会把不该高亮度显示的tab页高亮度显示了,这是因为从URL中manage_tabs 不能判断哪个view应当被高亮度显示。解决此问题只须设定要被高亮度的view的 management_view 变量给label,下面是一个用editPoll 方法的实现例子:

   1        def editPoll(self, question, responses, REQUEST=None):
   2           """
   3           Changes the question and responses.
   4           """
   5           self._question = question
   6           self._responses = responses
   7           self._votes = {}
   8           for i in range(len(responses)):
   9               self._votes[i] = 0
  10           if REQUEST is not None:
  11               return self.editPollForm(REQUEST,
  12           management_view='Edit',
  13           manage_tabs_message='Poll question and responses
  14 changed.')
  • 接下来让我们看一下,如何给product定义图标

3.5.3 图标

  • Zope products在management

interface用Icon定义。Icon是16*16像素的GIF图片,其背景为透明。通常,icons文件存在product package的www 子目录下,如何把icon和product class联系起来?

  • 用product's constructor中的registerClass 方法 icon

参数。例如:

   1    def initialize(registrar):
   2        registrar.registerClass(
   3            PollProduct,
   4            constructors = (addForm, addFunction),
   5            icon = 'www/poll.gif'
   6            )
  • 注意此例中,icon是在product 的 www

子目录中已经存在了的

方法的更多信息,见API Reference。

3.5.4 在线帮助

Zope提供在线帮助系统,主要是上下文相关的帮助和API帮助,你也可以为你的product提供这两大部分帮助。

3.5.4.1 上下文相关的帮助

  • 创建上下文相关的帮助,需要在product的 help

目录下,为你的每个management view创建一个帮助文件,此文件的格式有以下几种选择:HTML, DTML, structured text, GIF, JPG, 和 PNG.

  • 在product初始化时,用registrar object的registerHelp()

方法来注册帮助文件。如下所示:

   1        def initialize(registrar):
   2           ...
   3           registrar.registerHelp()

此方法负责找到帮助文件,并为每个帮助文件创建帮助主题。可识别的帮助文件扩展名有:.html, .htm, .dtml, .txt, .stx, .gif, .jpg, .png.

如果你想更多的控制创建帮助主题,用registerHelpTopic() 方法,它有一个id 和 a 帮助 主题对象。例如:

   1    from mySpecialHelpTopics import MyTopic
   2    def initialize(context):
   3        ...
   4        context.registerHelpTopic('myTopic', MyTopic())
  • 帮助主题需要有(adhere)一个HelpTopic

interface。更多信息见API Reference。

把帮助主题和管理界面绑定,最主要的方法是在class 的manage_options 结构中包括关于帮助主题信息。例如:

   1        manage_options=(
   2            {'label':'Edit',
   3             'action':'editMethod',
   4             'help':('productId','topicId')},
   5            )
  • help 的值是包含product的 Python

package名字、帮助主题的文件名字(或其他的id )的tuple。提供此help 值,Zope将在管理界面上自动的为帮助主题生成一个Help按钮并链接到你的帮助主题

在管理界面上生成一个help按钮而不是一个view(像add form那样)的方法时使用HelpSys 对象的HelpButton。方法如下:

       <dtml-var "HelpSys.HelpButton('productId', 'topicId')">
  • 这样就为特定的 help topic生成了一个help

button。如果你想生成你自己的help button,可用helpURL方法,如下:

       <dtml-var "HelpSys.helpURL(
         topic='productId',
         product='topicId')">

这样就给帮助主题了一个URL。可以选择生成任何类型的按钮和连接。

3.5.5 其它用户界面

  • 你的product可能没有web

管理界面,也可能完全的通过其它的网络协议控制。除了通过WEB提供管理界面之外,products也支持很多其它的用户界面。Zope提供了支持FTP, WebDAV 和 XML-RPC的 接口,如果这还不够,还可以增加其它的协议。

3.5.5.1 FTP 和 WebDAV 接口

  • FTP 和

WebDAV对待Zope对象像文件和目录,更多信息见第2章。

继承的子类,无需做任何工作,就可以得到基本的FTP和WebDAV支持。你的对象将出现在FTP目录列表中,而且,如果你的类是ObjectManager ,可以通过FTP 和 WebDAV访问它的内容。更多有关FTP 和 WebDAV支持的信息,见第2章。

3.5.5.2 XML-RPC和网络服务

XML-RPC在第2章中介绍。所有product的方法,均可通过XML-RPC访问,然而,如果你需要实现网络服务功能,就要使用XML-RPC应当设计一个或多个方法。

既然XML-RPC允许简单字符串、列表和字典的数据编组(marshalling),它也只能接受和返回这些类型的数据,不能接受和返回Zope对象。XML-RPC也不支持None ,因此需要用零或其它方式代替None.

使用XML-RPC需要考虑的另一个方面是安全。许多XML-RPC客户端都不支持基本的HTTP授权。根据不同的XML-RPC客户,需要编写公共的XML-RPC方法并通过参数接收授权证书。

3.5.5.3 内容管理框架接口(CMF)

  • Content Management Framework

是Zope的一绩效内容管理扩展,它为内容对象提供数个接品和转换,如果你想支持CMF,应当查阅CMF用户接口guidelines和界面文档。

  • 如果你的product已经支持了ZMI,那么支持CMF

interfaces就变得很容易了。如果你的product 类处理可管理的内容,如文档、图片、商业表格,你需要考虑支持CMF。

3.6 Products打包

  • 通常,Zope

products被打包为tar压缩包,在Products目录下创建压缩包时应当允许解包。例如,cd 到Products目录下,执行 tar 命令如下:

这将创建一个含有你的product的tar压缩包,product档案的文件名里应当包括product名字和版本号。

  • 一个完全的Python product打包例子,见

http://www.zope.org/Documentation/Books/ZDG/current/examples/Poll-1.0.tgz 文件

3.6.1 Product信息文件

  • 在product根目录下,除了Python 和 DTML

文件,还应当包括Product的一些信息。

README.txt:提供产品的基本信息。Zope解析此文件为文本,在product控制面板里,可以用README view阅读。

VERSION.txt:在一行里显示product名字和版本号,如Mutiple Choice Poll 1.1.0. Zope将把此信息作为version 属性显示在你的product的控制面板里。

LICENSE.txt:包含product许可证,或是它的一个连接。

也许你还想提供一些附加信息。下面是你的产品包含的一些可选的文件:

INSTALL.txt:提供特殊product安装说明及它所需元件的说明。此文件可选仅当product在Zope安装时只有一个ungzip/untar。

TODO.txt:此文件阐明何处这个product发布需要的工作以及作者的意图。

  • CHANGES.txt and HISTORY.txt:CHANGES.txt 列举product

版本的变化信息和最近的变化信息;HISTORY.txt 是旧的变化信息。

  • DEPENDENCIES.txt:包括os platform, required Python version,

required Zope version, required Python packages, and required Zope products的列表。

3.6.2 产品目录规划

  • dtml:包含DTML文件。 www:包含icon文件。 help:包含帮助文件。 tests:包含单元测试。

如果在这些目录里,你没有任何东西要放,就没有必要创建它们。

3.7 Product Frameworks

  • 创建Zope

products是一件复杂的事。有很多frameworks可帮助你减轻复杂程度,不同的frameworks注重product构建的不同方面。

3.7.1 ZClass Base Classes

  • 你也可以选择创建Python base classes,而不是创建full

blown products,Python base classes可被ZClasses使用,这就允许你只注重逻辑应用,而由ZClasses实现管理界面

此方法的最大障碍是关于ZClass和Python基类的代码将分开,这样,更难于编辑和可视化。

  • 关于ZClasses的更多信息,见Zope Book。

3.7.2 TransWarp and ZPatterns

  • TransWarp 和 Zpatterns是Phillip Eby 和 Ty

Sarna开发的两个相关的Products架构

Page(http://www.zope.org/Members/pje/Wikis/ZPatterns/HomePage)

  • 关于Zpatterns的,见 ZPatterns Home

Page(http://www.zope.org/Members/pje/Wikis/ZPatterns/HomePage)

3.8 进化中的Products

  • 当你开发product

类时,通常需要发布一系列的product,当你事先不知道product将会如何改变时,当你确实需要改变product时,有一些措施可以最小化问题。

3.8.1进化中的Classes

  • 当你改变 product

的类时,可能会出现问题,因为这些类的实例一般都是持久化的。因为改变product 类,意味着旧类中创建的实例将在新类中应用;如果你大幅度的改变类的内容,可能破坏现存的实例。目前由三种方式可以帮你解决这个问题

方法一:最简单的方法是为新加属性提供默认值。例如,如果你的类的最近的版本需一个improved_spam 实例属性,而早点的版本仅有spam属性,你可能希望在新类中定义一个improved_spam 类属性,这样你的旧的对象仍可用。你可能把improved_spam 设置为None,在用此属性的方法中,你可能不得不考虑它可能是None,例如:

   1     class Sandwich(...):
   2        improved_spam=None
   3        ...
   4        def assembleSandwichMeats(self):
   5            ...
   6            # test for old sandwich instances
   7            if self.improved_spam is None:
   8                self.updateToNewSpam()
   9            ...
  • 方法二:用标准Python pickling hook

setstate,但是,这是最复杂和最容易出错的。

方法三:创建一方法来更新旧的实例,子后可以手动调用实例的方法来刷新它们。注意,这需要实例函数正常工作且能用ZMI来访问。

当你在开发product时,不需太关心这些细节,因为你总是可以删除旧的实例并引进新的类。然而一旦你发布了product,其它的人开始使用它,你需要永久性的更新一下。

另一个比较麻烦的问题可能是由重命名product类引起的。重命名类会破坏所有存在的实例,所以应尽量避免,如果你真的需要改变名字,用别名。当然,改变你的类的基类则不会引起这些问题。

3.8.2 进化中的Interfaces

尽量不要修改接口。当你自己使用时你可以随便改接口。但一旦你把接口设置为公共的,那么就不应该再改变它了。因为让用户修改接口来适公共接口的变化这是不公平的。一个接口是一个约定,它标明了如何使用组件和如何执行不同类型的组件,当接口在使用或执行时,不管是用户还是开发者,改变它都会出现问题。

通常的解决方法是,首先是创建一些简单的接口,当需要改变现存的interface时也要创建新接口,如果新接口与现存的接口兼容,你可以用新接口扩展旧接口,如果新接口代替旧interfaces而不是扩展它,你需要给新接口一个新的名字,如WidgetWithBellsOn.除了新接口,你的components应当继续支持旧接口。

3.9 结论

  • 把你的组件移值到Zope

products中,此过程需要很多步骤,也有很多细节需要注意。不过,如果你按照本章的说法,可以成功达到目的。

随着Zope的发展,我们将简化Zope开发模型,我们希望从product开发中除去大量的管理界面的细节,也希望能有一个更全面的组件架构来更好的利用接口。

  • Zope

products,对于创建web应用,是一个强有力的框架。通过创建products,可以利用Zope的特性,包括安全、可测量、通过Web管理和协作