{{{译者 sunbaby,jzx++ 如要转载请注明作者 }}}不好意思,我们第一次翻译,如有翻译的不正确的地方,欢迎指正。来信请到 [email protected] 谢谢
::-- ZoomQuiet [DateTime(2005-08-22T07:12:30Z)] TableOfContents
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中所描述的一或多个接口,如何创建接口及相关详细信息请参见第一章。
应当在开始执行前创建接口,因为这有利于你的设计和更好的评估它满足你的需求的情况。
- from Interface import Base class Poll(Base):
- "A multiple choice poll" def castVote(self, index):
- "Votes for a choice"
- "Returns total number of votes cast"
- "Returns number of votes cast for a given response"
- "Returns the sequence of responses"
- "Returns the question
- "A multiple choice poll" def castVote(self, index):
可以随意命名接口,但是不要用如"I"及其它特殊的标识符。
3.2.3 实现接口
为你的PRODUCT定义了一个接口后,下一步是在Python中创建一个原型来实现它
- 这是一个Poll实现类的原型,用来实现刚才的接口 from Poll import Poll
class PollImplementation:
- """ A multiple choice poll, implements the Poll interface. The poll has a question and a sequence of responses. Votes are stored in a dictionary which maps response indexes to a number of votes. """
implements=Poll
def init(self, question, responses):
- self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- self._votes[i] = 0
- "Votes for a choice" self._votes[index] = self._votes[index] + 1
- "Returns total number of votes cast" total = 0 for v in self._votes.values():
- total = total + v
- "Returns number of votes cast for a given response" return self._votes[index]
- "Returns the sequence of responses" return tuple(self._responses)
- "Returns the question" return self._question
- self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- """ A multiple choice poll, implements the Poll interface. The poll has a question and a sequence of responses. Votes are stored in a dictionary which maps response indexes to a number of votes. """
可以交互使用并测试此类。下面是一个交互测试的例子。
>>> 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类定义的例子: from Acquisition import Implicit from Globals import Persistent
from AccessControl.Role import RoleManager from OFS.SimpleItem import Item
class PollProduct(Implicit, Persistent, RoleManager, Item):
- """ Poll product class """
- ..
基类的顺序取决于你想要的优先级,大多数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是变化不定的非持久化子对象字典,你改变它时,需要让永久性对象知道。
- def castVote(self, index): "Votes for a choice" self._votes[index] = self._votes[index] + 1 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 参数。
- 代码如下:
class PollProduct(..., Item):
- meta_type='Poll'
- ..
def init(self, id, question, responses):
- self.id=id self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- self._votes[i] = 0
- self.id=id self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- 代码如下:
最后,你应该把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=({'id':'title', 'type': 'string', 'mode': 'w'},
- {'id':'color', 'type': 'string', 'mode': 'w'}, )
_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=({'id' : 'favorite_color',
- 'type' : 'selection', 'select_variable' : 'getColors'
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 类声明安全的范例:
from AccessControl import ClassSecurityInfo
class PollProduct(...):
- ..
security=ClassSecurityInfo() security.declareProtected('Use Poll', 'castVote') def castVote(self, index):
- ..
- ..
- ..
- ..
- ..
- ..
为了声明安全信息,需要到Zope中设置初始化一下产品类,下面是一如何初始化poll class的例子:
from Globals import InitializeClass
class PollProduct(...):
- ..
3.3.3 总结
- 祝贺你已经创建了一product类。全部代码如下 from Poll import Poll
from AccessControl import ClassSecurityInfo from Globals import InitializeClass from Acquisition import Implicit from Globals import Persistent from AccessControl.Role import RoleManager from OFS.SimpleItem import Item
class PollProduct(Implicit, Persistent, RoleManager, Item):
- """ Poll product class, implements Poll interface. The poll has a question and a sequence of responses. Votes are stored in a dictionary which maps response indexes to a number of votes. """
implements=Poll meta_type='Poll'
security=ClassSecurityInfo()
def init(self, id, question, responses):
- self.id=id self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- self._votes[i] = 0
- "Votes for a choice" self._votes[index] = self._votes[index] + 1 self._p_changed = 1
- "Returns total number of votes cast" total = 0 for v in self._votes.values():
- total = total + v
- "Returns number of votes cast for a given response" return self._votes[index]
- "Returns the sequence of responses" return tuple(self._responses)
- "Returns the question" return self._question
- self.id=id self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
InitializeClass(Poll) 现在该在Zope中测试product
- """ Poll product class, implements Poll interface. The poll has a question and a sequence of responses. Votes are stored in a dictionary which maps response indexes to a number of votes. """
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的例子:
from PollProduct import PollProduct, addForm, addFunction def initialize(registrar):
- registrar.registerClass(
PollProduct, constructors = (addForm, addFunction), )
- registrar.registerClass(
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函数的简单例子:
- def addForm():
- """ Returns an HTML form. """
return """<html> <head><title>Add Poll</title></head> <body> <form action="addFunction"> id <input type="type" name="id"><br> question <input type="type" name="question"><br> responses (one per line) <textarea name="responses:lines"></textarea> </form> </body> </html>"""
- """ Returns an HTML form. """
的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: def addFunction(dispatcher, id, question, responses): """ Create a new poll and add it to myself """
p=PollProduct(id, question, responses) dispatcher.Destination()._setObject(id, p) 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:
- ## Script (Python) "getPercentFor" ##parameters=index ## """ Returns the percentage of the vote given a response index.
Note,
- this script should be bound a poll by acquisition context. """ poll=context 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(类属性)中定义,下面是一个例子:
- manage_options=(
- {'label' : 'Edit', 'action' : 'editMethod'}, {'label' : 'View', 'action' : 'viewMethod'}, )
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。下面是一例子:
class PollProduct(..., Item):
- .. manage_options=(
- {'label' : 'Edit', 'action' : 'editMethod'}, {'label' : 'Options', 'action' : 'optionsMethod'},
) + RoleManager.manage_options + Item.manage_options
- {'label' : 'Edit', 'action' : 'editMethod'}, {'label' : 'Options', 'action' : 'optionsMethod'},
这个例子中包含标准的被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 方法,例如:
- from Globals import DTMLFile
class PollProduct(...):
- .. 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,参见下面例子:
- from Globals import DTMLFile
class PollProduct(...):
- .. _editForm=DTMLFile('dtml/edit', globals()) def editForm(self, ...):
- .. return self._editForm(REQUEST, ...)
- .. _editForm=DTMLFile('dtml/edit', globals()) def editForm(self, ...):
在底部。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 文件中,下面是在你的类中如何定义此方法:
class PollProduct(...):
- .. security.declareProtected('View management screens',
'editPollForm')
- editPollForm=DTML('dtml/editPollForm', globals())
- 注意edit form受View management screens
允许权限保护,这确保了只有管理者才能调用此方法。
注意这个form的action也是editPoll.既然poll不包括任何编辑方法,需要定义一个来接受改变,下面是一个editPoll 方法。
class PollProduct(...):
- ..
def init(self, id, question, responses):
- self.id=id self.editPoll(question, response)
- .. security.declareProtected('Change Poll', 'editPoll') def editPoll(self, question, responses):
- """ Changes the question and responses. """ self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- self._votes[i] = 0
- """ Changes the question and responses. """ self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
注意init
方法如何通过新的editPoll方法被反射,另外,注意editPoll方法改变Poll时是如何被一个新的权限保护的。
- editPoll
方法仍然有一个问题:当从WEB调用editPollForm时并没有返回任何东西,这样的管理界面比较糟糕。
- 当从WEB调用时,你希望返回一个HTML
响应,但从init调用时,不希望返回HTML响应。下面是解决方法:
- class Poll(...):
- .. def editPoll(self, question, responses, REQUEST=None):
- """ Changes the question and responses. """ self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- self._votes[i] = 0
- return self.editPollForm(REQUEST,
- manage_tabs_message='Poll question and responses
- """ Changes the question and responses. """ self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- .. def editPoll(self, question, responses, REQUEST=None):
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 方法的实现例子:
- def editPoll(self, question, responses, REQUEST=None): """ Changes the question and responses. """ self._question = question self._responses = responses self._votes = {} for i in range(len(responses)):
- self._votes[i] = 0
- return self.editPollForm(REQUEST,
- management_view='Edit', manage_tabs_message='Poll question and responses
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
参数。例如:
- def initialize(registrar):
- registrar.registerClass(
PollProduct, constructors = (addForm, addFunction), icon = 'www/poll.gif' )
- registrar.registerClass(
子目录中已经存在了的
有关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()
方法来注册帮助文件。如下所示:
- def initialize(registrar):
- .. registrar.registerHelp()
此方法负责找到帮助文件,并为每个帮助文件创建帮助主题。可识别的帮助文件扩展名有:.html, .htm, .dtml, .txt, .stx, .gif, .jpg, .png.
如果你想更多的控制创建帮助主题,用registerHelpTopic() 方法,它有一个id 和 a 帮助 主题对象。例如:
from mySpecialHelpTopics import MyTopic def initialize(context):
- ..
context.registerHelpTopic('myTopic', MyTopic())
帮助主题需要有(adhere)一个HelpTopic
- ..
interface。更多信息见API Reference。
把帮助主题和管理界面绑定,最主要的方法是在class 的manage_options 结构中包括关于帮助主题信息。例如:
- manage_options=(
- {'label':'Edit',
- 'action':'editMethod', 'help':('productId','topicId')},
- {'label':'Edit',
- 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')">
- topic='productId',
这样就给帮助主题了一个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,例如:
- class Sandwich(...): improved_spam=None
- .. def assembleSandwichMeats(self):
- .. # test for old sandwich instances if self.improved_spam is None:
- self.updateToNewSpam()
- ..
- .. # test for old sandwich instances if self.improved_spam is 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管理和协作