{{{译者 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类定义的例子:
基类的顺序取决于你想要的优先级,大多数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是变化不定的非持久化子对象字典,你改变它时,需要让永久性对象知道。
此方法的最后一行把_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 参数。
- 代码如下:
最后,你应该把Item放在基类列表最后,因为Item提供如ObjectManager与PropertyManager类的默认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属性,例如
_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 提供属性或方法的名字,返回被选中的字符串列表,如:
- 在_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的例子:
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的例子:
- 此函数通过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中。
更多关于ProductRegistrar interface的信息,参见 API
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 相关连接正常工作。
一个FactoryDispatcher作为它的第一个参数传给add
function,第一个参数是产品添加的位置(通常是Folder)。Add function 也可以传入任何根据一般对象发布规则发布的form变量
- 下面是一个poll类的add function:
- Dispatcher 有三个方法:
Destination:product被加进的对象管理器(ObjectManager )的位置
DestinationURL:ObjectManager 的URL manage_main:指向 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:
- 下面是一个显示投票结果并允许你投票的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(类属性)中定义,下面是一个例子:
- 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。下面是一例子:
这个例子中包含标准的被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 方法,例如:
- 这样就在你的类里创建了一个DTML
Method,此方法在dtml/edit.dtml 文件中定义。注意你不需要包含.dtml 扩展文件,而且也不用担心作为路径分隔符会出问题,一般这样的转换在Windows上都可以运行。通过转换,DTML伟建就会放置到产品的dtml子目录里。
- DTMLFile 构造方法的globals()
参数作用是允许其定位product 目录。如果你在调试模式下运 行Zope,DTML 文件的变化立刻被反映出来,也就是说,你无须重起Zope,就可以看到这些改变。
DTML 类的方法和其他方法一样,可以通过web直接调用。因此用户可以通过调用poll 类的实例的editForm 方法来显示编辑表单。通常DTML方法是用来在实例中收集显示信息的。你可以用一般的方法包装你的DTML方法,这样你可以在调用它之前计算DTML所需要的信息,也保证用户总是通过你的包装对象来访问DTML,参见下面例子:
- 在创建管理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 文件中,下面是在你的类中如何定义此方法:
- 注意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
参数。例如:
- 注意此例中,icon是在product 的 www
子目录中已经存在了的
有关ProductRegistrar interface的registerClass
方法的更多信息,见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()
方法来注册帮助文件。如下所示:
此方法负责找到帮助文件,并为每个帮助文件创建帮助主题。可识别的帮助文件扩展名有:.html, .htm, .dtml, .txt, .stx, .gif, .jpg, .png.
如果你想更多的控制创建帮助主题,用registerHelpTopic() 方法,它有一个id 和 a 帮助 主题对象。例如:
帮助主题需要有(adhere)一个HelpTopic
interface。更多信息见API Reference。
把帮助主题和管理界面绑定,最主要的方法是在class 的manage_options 结构中包括关于帮助主题信息。例如:
- 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章。
通过从SimpleItem.Item 和 ObjectManager
继承的子类,无需做任何工作,就可以得到基本的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,例如:
- 方法二:用标准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管理和协作