Copyright © 2004 本文遵从GNU 的自由文档许可证(Free Document License)的条款,欢迎转载、修改、散布。
发布时间:2004年9月17日
最近更新:2005年06月14日
Abstract
ZopeBook是Zope系统的官方资料,是我们学习Zope系统最重要的在线资料。本笔记基于zopebook2.7版本,英文版位于http://www.plope.com/Books/2_7Edition/bt_sections_view。
Table of Contents
Table of Contents
当你用zope建立一个基于Web的应用程序时,对象就是你这个应用程序的基础。这章主要介绍一些基础的zope对象。
Zope对象能帮我们处理不同的任务,不同的对象可处理应用程序中的不同部份。一些对象可保存内容(文本、电子表格和图象等);一些对象可处理逻辑操作,如接收Web表单的输入或执行脚本;一些对象可控制内容的显示,如显示网页、邮件等。
一般来说,基础的Zope对象有以下三类:
内容对象
内容对象可保存各种文本二进制数据,另外,内容对象还可存取外部数据,如系统文件和关系数据库。
显示对象
Zope提供两种对象来控制网站的显示,一种是DTML,一种是ZPT。两者间的不同点是,DTML同时处理显示和逻辑关系,ZPT则可与表现层分离。
逻辑处理对象
Zope提供了一些工具帮助我们处理商业逻辑。有三种对象可处理进行逻辑处理:DTML,python script,perl script。通过这些对象我们可以改变对象属性、发送信息和邮件、进行条件测试和对某些事进行动态反馈等。
DTML对象的分类有些不清,它即可作为显示对象,也可作为逻辑处理对象。我们还可安装第三方的Zope对象来扩展zope的功能。我们通常称这些对象为“产品”
Folders
Zope的Folder对象主要用于包含其它对象,包括Folder。文件夹的结构对于安全和显示很重要,建立时需考虑清楚。
Files
Zope的Files对象包含raw数据,象音频、视频和文档。它还可保存一些Files对象不支持的内容,如Flash文件、Java Applets等。Files对象保存内容时不考虑内容的类型,所以可以用Files对象来保存任意的数据。
每 个File对象都有一个叫“content type”的属性,该属性符合MIME标准。如:“text/plain(纯文本)”、“text/html(html格式)”、 “application/pdf(pdf格式)”。当你上传一个文件时,zope会根据文件名自动分配一种MIME类型给“content type”属性。
建立和编辑文件
从zmi 右上角的对象添加列表中选择“File”,按“Add”。并填上“id”(必填)、“title”(可选)。如果想建立一个空白文件,直接按“Add”即 可,如果想上传一个文件,可按“Browse”按钮,从本地选择一个文件上传。文件一旦建立,就可打开进行内容编辑或修改属性。也可通过“upload” 功能通过本地文件更新它。
浏览文件
编辑和上传完文件后,我们可通过“view”标签来浏览文件内容。当然 前提是要zope能识别该文件的类型,否则zope会弹出一个下载窗口,要求你下载该文件。你也可直接通过浏览器显示文件内容,例如有一个放在zope的 根目录下的test.pdf文件,你只要在浏览器输入Http://localhost:8080/test.pdf即可,
Images
Image对象包含如GIF,JPEG,PNG格式的文件,和文件对象很类似,但Image对象包含一些专有的属性,如图像的宽、高等属性。
表 现层和逻辑层应该分开。表现层负责显示静态和动态内容。一般是html文档。ZPT采用XML名称空间元素,能有效分离逻辑层和表现层。DTML采用 “tags”元素,所以表现层和逻辑层分离得不好。ZPT和DTML都是“server-side”技术,就象SSI,PHP,JSP一样。代码都由服务 器来执行。
“tag -based”的脚本语言不能很好地分离表现层和逻辑层。如DTML、SSI、PHP和JSP。这样使程序设计师和网页设计师不能很好地分工合作。为了有 效分离表现层和逻辑层,就产生了ZPT这种“attribute-based”的语言。这两种语言在zope中支持,而且为一直共存。ZPT和DTML的 功能有些重复,致使一些人感到困惑,不知怎么选择。下面有几点提示希望能帮到大家更好地使用这两种语言:
如果你的团队包含程序设计师和网页设计师的团队,就应该选择ZPT,以使团队能更好地分工合作。
ZPT建立的页面需是XHTML、XML兼容,而DTML不用。ZPT不能动态改变CSS样式表、SQL语句等,但DTML可很容易实现。
DTML能提供很好的条件控制语句,在这方面,DTML更像PHP、ASP这样的脚本语言。可以用DTML来替代它们。
表现层与逻辑层分离也不是适用于所有情况,所以有时DTML能比ZPT工作地更好。
DTML对象包括DTML文档和DTML方法两种。DTML对象受到zope安全机制的约束,所以能安全地显示内容。
DTML 文档和DTML方法有什么不同呢?表面上,两者都包含DTML代码和数据,都有相同的用户接口和API。DTML方法主要用于显示其它对象,DTML文档 用于显示本身的内容。DTML文档支持属性功能,DTML方法不支持属性。一般来说,你应该用DTML方法来存放DTML内容,除非你有一个好的理由需使 用DTML文档,如你需要使用对象属性。详细介绍请参考13、14章。
逻 辑对象返回数据给显示对象进行显示。在zope中内置的逻辑对象有python script和外部方法,这两种对象都是使用Python脚本语言来开发的。现在也可以使用perl script这个附加的逻辑对象了。好象也有一些产品可以在zope中使用php和jsp,如PHParser,PHPObject和ZopeJSP,但 我没试过。
Script (Python)对象是基于的Web的,受zope安全约束的python代码,并不是所有的python代码都可以在zope中运行。也就是说你不能导 入受限的Python模块和直接访问本地文件系统。这些特性能帮管理员更好地管理网站。Script(Python)对象的建立、编辑和更新同File对 象类似,这里不细讲了。详细内容中参考第9、16章。
外部方法和Script(Python)对象差不多,都是采用python脚本语言编写,作用也一样,但它们一些不同:
外部方法不能用ZMI编辑,它放到本地文件系统中的zope实例目录的Extensions目录下。
由于外部方法不在ZMI中编辑,所以它不受zope的安全机制约束,可以直接访问本地文件系统,能导入和执行任意的python模块和代码。
外部方法不支持“bindings”的概念。
首先,在zope实例目录的Extensions目录下建一个文本文件,并命名为“SalesEM.py”。在文件中写入以下内容:
def SalesEM(self, name="Chris"):
id = self.id
return 'Hello, %s from the %s external method' % (name, id)
接着在ZMI中建立一个External Method Object。“id”和“title”可随便起,这里填“SalesEM”。“Module Name”要填脚本名,这里是SalesEM.py。“Function Name”要填函数名,这里是SalesEM。填完按“Add”即可。
添加完成后就可进行测试,当前刚建立的外部方法,按“test”标签就可执行外部方法中定义的函数“SalesEM”了。
我们还可通过http请求把参数传递给外部方法。如:
http://localhost:8080/Sales/SalesEM?name=Fred 显示: Hello, Fred from the Sales external method
该应用很简单,只是计算所有债务利息的总计。
首先,建立一个页面模板表单“interestRateForm”,用于收集数据。代码如下:
<html> <body> <form action="interestRateDisplay" method="POST"> <p>Please enter the following information:</p> Your current balance (or debt): <input name="principal:float"><br> Your annual interest rate: <input name="interest_rate:float"><br> Number of periods in a year: <input name="periods:int"><br> Number of years: <input name="years:int"><br> <input type="submit" value=" Calculate "><br> </form> </body> </html>
提交该表单将会打开“interesRateDisplay”页面模板。
接着,建立一个脚本“calculateCompoundingInterest”,用于计算利息。它有四个参数,分别是:principal, interest_rate, periods,years。脚本的代码如下:
""" Calculate compounding interest. """ i = interest_rate / periods n = periods * years return ((1 + i) ** n) * principal
在parameters list栏填上参数,多个参数以逗号分隔。
最后建立一个页面模板“interestRateDisplay”以显示结果。代码如下:
<html>
<body>
Your total balance (or debt) including compounded interest over
<span tal:define="years request/years;
principal request/principal;
interest_rate request/interest_rate;
periods request/periods">
<span tal:content="years">2</span> years is:<br><br>
<b>$
<span tal:content="python: here.calculateCompoundingInterest(principal,
interest_rate,
periods,
years)" >1.00</span>
</b>
</span>
</body>
</html>
该页面由“interestRateForm”所调用,该页面里面又调用了“calculateCompoundingInterest”脚本,并把表单收集的四个参数传递给该脚本。最后把脚本计算出的结果显示出来。
有关错误处理,在上例中,“interestRateDisplay”页面模板不能按“test”标签进行测试,因为,它需要四个参数。所以如果按“test”标签,将会出现出错信息。如下所示:
Site Error An error was encountered while publishing this resource. Error Type: KeyError Error Value: years
它提示找不到yesrs变量,在zope根目录下的error_log文件中有更详细的出错日志可供查询。如:
* Module Products.PageTemplates.TALES, line 217, in evaluate URL: /Interest/interestRateDisplay Line 4, Column 8 Expression: standard:'request/years'
执行该应用只需打开“interestRateForm”页面,按“test”标签即可。
Zope自带有一个在线教程,你可以通过它来学习Zope。该教程可在对象添加列表中找到,添加后就可使用了。我已整理了一份The Zope Tutorial的学习笔记以供参考。
获取(Acquisition)是一种通过包含关系动态共享Zope对象的技术,在Zope中,该技术无处不在,DTML、ZPT、scrip甚至zope urls都有用到获取技术。所以在zope中理解它是很重要的。
继承是面向对象理论中的一个重要概念。使用继承,一个对象可以继承特定类的行为,在有需要时,还可以重载或增加一个类的行为。类的行为用方法来定义,属性也可通过继承获得。
python、java都是典型的面向对象的编程语言。通过类的继承能方便我们在子类中继承父类的各种方法和属性,加快我们的开发速度和增加软件架构的稳定性。具体有关面向对象编程方法这里就不讲了,请参考相应的书籍。下面是一个用python写的有关类继承的例子:
>>> class SuperA: ... def amethod(self): ... print "I am the 'amethod' method of the SuperA class" ... def anothermethod(self): ... print "I am the 'anothermethod' method of the SuperA class" ... >>> class SuperB: ... def amethod(self): ... print "I am the 'amethod' method of the SuperB class" ... def anothermethod(self): ... print "I am the 'anothermethod' method of the SuperB class" ... def athirdmethod(self): ... print "I am the 'athirdmethod' method of the SuperB class" ... >>> class Sub(SuperA, SuperB): ... def amethod(self): ... print "I am the 'amethod' method of the Sub class" ...
这里定义了三个类,Sub类型继承自SuperA类和SuperB类。下面是使用这些类的例子:
>>> instance = Sub() >>> instance.amethod() I am the 'amethod' method of the Sub class >>> instance.anothermethod() I am the 'anothermethod' method of the SuperA class >>> instance.athirdmethod() I am the 'athirdmethod' method of the SuperB class
当调用Sub类的amethod()方法时,由于子类已定义了该方法,所以系统就直接调用。但当调用Sub类的 anothermethod()方法和athirdmethod()方法时,在Sub类是没有这些方法的,但由于Sub类继承了SuperA类和 SuperB类。所以系统会到这两个类中查询该方法。从而打印出以上结果,这就是继承。在非Zope系统中通常是用以上的方法进行方法的继承的,但在 Zope中,对象是用获取(Acquisition)这种工具来搜索方法的。
获取的概念很简单:
一个对象是位于另一个对象内的。对象就象一个容器,包含其它对象。如:DTML_Example folder文件夹对象内有一个DTML Method对象叫“amethod”,则DTML_Example文件夹就是包含“amethod”对象的容器。
对象能获得所在容器的方法。
在Zope中,对象的继承机制先于获取机制。
我们现在用一个简单的例子来说明如何“获取”。在根目录中建一个叫“acquisition_test”的DTML Method。内容如下:
<html> <body> <p> I am being called from within the <dtml-var title> Folder! </p> </body> </html>
保存并使用“view”功能可看到以下输出:
I am being called from within the Zope Folder!
Zope根目录文件夹的标题名是“Zope”,所以这样显示。现在我们在根目录下建立一个文件夹叫“ AcquisitionTestFolder”,标题为“TheAcquisitionTest”。现在我们在 “AcquisitionTestFolder”文件夹下执行“acquisition_test”这个DTML Method,在浏览器上打入以下地址:http://localhost: 8080/AcquisitionTestFolder/acquisition_test。你将看到以下内容:
I am being called from within the TheAcquisitionTest Folder!
注意到了吗?在“ AcquisitionTestFolder”文件夹内并没有“acquisition_test”,但zope照样可找到该方法,并把“title”换 成“AcquisitionTestFolder”的标题。这就是获取的概念,很简单。如果对象名在当前文件夹内找不找,zope就会到包含它的上一层文 件夹内去找,一层层地找,直到找到对象为止。用这种方法,获取可为对象添加方法,如“ AcquisitionTestFolder”文件夹就通过获取增加了一个“acquisition\ _test”方法。
对 象可以通过获取而获得相应的服务,同时也对其它对象提供服务。这使得我们重用服务变得很简单和容易。例如我们在一个叫AFolder的文件夹内建立了一个 “Mail Host”对象,则在该文件夹的对象就获得了发邮件的功能,同时也为“AFolder”文件夹的中的所有子文件夹提供了相同功能。
有时我们可能需要从多层目录结构中进行获取,如:我们有一个DTML Method,叫“HappySong”,它位于根目录中。根目录下有一个多层目录分别是“Users”、“Barney”、“Songs”。现在我们执行以下语句:
/Users/Barney/Songs/HappySong
由于“HappySong”位于根目录,所以这三个目录都可以调用该方法。如上例这样调用时,Zope的搜索顺序是这样的,它先在 “Songs”目录中搜索,如果找不到该方法,则到上一层目录“Barney”中搜索,如果还是找不到,则再上一级到“Users”目录搜索;以此类推, 最后,在根目录找到。这就是搜索的路径。
获取不仅能搜索一个包含结构,也可进行上下文结构搜索。但上下文搜索很复杂。接上例,如果在root目录找到“HappySong”。则有两种有趣的结构:
因为“HappySong”在位于root目录,所以包含结构由root和它自已组成。
因为“HappySong”通过三个文件夹“Users”、“Barney”、“Songs”。所以上下文结构将会包含这三个对象。
![]() | |
| 获取功能只在在包含结构中搜索不到对象时才会通过上下文结构进行搜索。 | |
如果大家想更深一层了解获取的工作原理,可以参考以下网址:http://zope.org/Members/jim/Info/IPC8/AcquisitionAlgebra/index.html。下面一节使用Zope调试器来演示两种获取结构。
在你的zope实例目录运行bin/zopectl debug,按以下步骤输入:
$ bin/zopectl debug
Starting debugger (the name "app" is bound to the top-level Zope object)
>>> app.manage_addFolder('Users')
>>> users = app.Users
>>> users.manage_addFolder('Barney')
>>> barney = users.Barney
>>> barney.manage_addFolder('Songs')
>>> songs = barney.Songs
>>> songs.aq_chain # show the whole chain
[<Folder instance at f651b290>, <Folder instance at f651b260>, <Folder instance at f651b230>, <Application instance at f65b0290>]
>>> songs.aq_inner.aq_chain # show only containment; here its the same
[<Folder instance at f651b290>, <Folder instance at f651b260>, <Folder instance at f651b230>, <Application instance at f65b0290>]
在上面,我们可以看到“Songs”文件夹的包含结构,且包含结构和上下文结构一样。现在让我们建立一个“HappySongs”页面模板,并测试它们的结构,在本例中,它们还是一样的。
>>> app.manage_addDTMLMethod('HappySong', file="""\
... <dtml-if favorite_color>
... My favorite color is &dtml-favorite_color;.
... <dtml-else>
... I don't have a favorite color.
... </dtml-if>
... """)
''
>>> happy = app.HappySong
>>> happy.aq_chain
[<DTMLMethod instance at f649d050>, <Application instance at f65b0290>]
>>> happy.aq_inner.aq_chain
[<DTMLMethod instance at f649d050>, <Application instance at f65b0290>]
下面我们模拟用“/Users/Barney/Songs/HappySong”来访问。
>>> happy2 = app.unrestrictedTraverse('/Users/Barney/Songs/HappySong')
>>> happy2.aq_chain
[<extension class OFS.DTMLMethod.DTMLMethod at f6b0d980>, <extension class OFS.Application.Application at f6a234d0>]
>>> happy2.aq_inner.aq_chain
[<DTMLMethod instance at f651b320>, <Application instance at f65b0290>]
我们注意到,包含结构(通过happy2.aq_inner.aq_chain访问)和上面一样,但上下文结构改变了,包含了所有经过的文件夹。
现在,让我们尝试通过获取的两种结构来查找属性,首先,我们显示通过包含获取没有得到“favorite_color”:
>>> happy2(songs, {}, None)
" I don't have a favorite color.\n"
我们通过上下文结构设置“favorite_color”属性,这样我们就可以使用它了。
>>> barney.favorite_color = 'purple'
>>> happy2(songs, {}, None)
' My favorite color is purple.\n'
获 取使得对象方法行为可分布在系统各处,当你添加一个新对象时,不用为这个对象建立所有对象方法,你可只建立只用于该对象的方法行为,其余的可依靠其它对象 获取。这样你就可通过修改一个对象的方法行为而改变其它与该对象有获取关系对象的行为。这可大大增中Zope应用程序的灵活性和适应性。下来的一些章节都 会用到获取技术,有关获取技术的基础原理介绍可参考以下网址:http://www.zope.org/Documentation/Books/ZDG/current/Acquisition.stx
Table of Contents
可通过ZMI来管理Zope对象,也可通过脚本来管理,本章就是介绍如何用编程的方法来控制和管理Zope对象。
由 于Zope是一个Web应用服务器,所以可直接通过浏览器和它打交道,任何URL都被映射为Zope中的对象和方法,如“http: //www.ringkee.com/forum/”这个链接,会被zope映射为根目录下的forum文件夹,并会默认打开forum目录下的 index_html文档(如果有的话,否则会打开上一级目录下的index_html文档);如链接是“http: //www.ringkee.com/forum/test”,则会被zope映射为forum文件夹下的一个test方法,test方法操作对象并返回 结果。正如我们所知,打开“http://localhost:8080/”可进入“Zope Quick Start”页面,其实就是打开了根目录下一个index_html文档。我们可修改该文档,替换成自已设计的首页,这样就可通过“http: //www.ringkee.com”来访问我们的首页了。(关于如何把Zope默认的8080端口改为80端口请参考我整理的Zope how-to文档)
可通过“http://localhost:8080/mamage_main”直接打开zope根目录。如果配置虚拟目录出错,进不了ZMI,可通过这种方法进入zope的根目录,从而可以重新配置虚拟目录。
可通过“http://localhost:8080/manage_main?skey=meta-type”打开根目录,同时,把skey=meta-type这个参数传递给manage_main方法,这里的skey参数指定排序关键字为meta-type。
记住,在zope中,默认的页面是index_html,相当于apache中的index.html或index.htm。
通 过“acquire获取机制”,一个文件夹会自动获得父文件夹的index_html文件。所以当你在根目录下建立了一个空的文件夹test,当用 “http://localhost/test/”来访问时会自动打开根目录的index_html文档。如果不想显示父目录的index_html,而 要显示当前目录的index_html,只需在当前目录建立一个index_html文档即可。这种机制可使我们能方便地设计一个缺省页面,把它放到目录 的最顶层,当链接出错,找不到对象时,可借助acquire机制自动地打开该页面。
index_html可以是一个zpt、dtml、python script或任意可被浏览器访问和识别的Zope对象。
在Zope中,可以使用python编程语言,以操作对象。
从ZMI的右上角的对象列表中选中“Script(Python)”,按“Add”添加,接着输入id和title,最后按“Add”添加即可。
脚本对象的“parameter list”栏用于定义脚本需接收的参数。如在该栏内定义一个参数“name”,则可在代码栏中这样使用:
return "Hello %s." % name
编写完脚本后可按“test”标签进行测试,如果脚本需接收参数则会出现接收参数的画面,按要求输入完参数后按“runscrip”按钮即可。
在script中,你可用context变量来访问当前目录的Zope对象。如下代码可获得当前目录下的zope对象的id列表,并打印出列表的长度,也就是对象的个数。:
list=context.objectIds() print len(list) return printed
objectIDs()是文件夹类的一个方法,所以context对象应该是可包含在文件夹内的对象。
我们可以通过context来访问zope对象的属性,如下:
## Script (Python) "objectsForStatus"
##parameters=status
##
""" Returns all sub-objects that have a given status property.
"""
results=[]
for object in context.objectValues():
if object.getProperty('status') == status:
results.append(object)
return results
该代码可通过context和对象的getProperty()方法来获得当前目录下所有对象的“status”属性(status属 性需自已在对象的Properties标签内增加),并根据对象的“status”属性的值与参数“status”的值是否相等来操作results列 表。
我们可通过REQUEST对象来获得给HTTP传递的参数。如以下提交表单的请求:http://localhost:8080/test?forum_id=1。我们可通过context.REQUEST.forum_id来访问forum_id的值。
另 外一种方法是通过脚本的parameters来传递。如果我们定义REQUEST为脚本的参数,也就是把“REQUEST”填到parameters list栏内,则zope会自动地把http请求传递给这个参数,在脚本中我们就可能通过REQUEST.forum_id来访问forum_id变量。
在zope中由于安全原因不能在python script中使用正则表达式,但可使用python中的字符处理模块的功能。如果要在zope中使用正则表式可用外部方法来实现。
一个处理字符串替换的例子:
## Script (Python) "replaceWord"
##parameters=word, replacement
##
""" Replaces all the occurrences of a word with a replacement word in
the source text of a text file. Call this script on a text file to use
it.
Note: you will need permission to edit the file in order to call this
script on the *File* object. This script assumes that the context is
a *File* object, which provides 'data', 'title', 'content_type' and
the manage_edit() method.
"""
text = context.data
text = text.replace(word, replacement)
context.manage_edit(context.title, context.content_type,
filedata=text)
replace()函数用于替换字符串,该函数的详细参考请查阅我整理的Python参考篇。 你可以在Web中执行该脚本,如把URL指向Spam/replaceWord?word=Alligator&replacement= Crocodile,则会把Spam文件中所有的“Alligator”替换成“Crocodile”。不过有一个前提是要确保Spam是一个文件对象 (File)。manage_edit()是一个文件对象方法。
我们可用字符串处理程序进行内容搜索,但在zope中提供了一个更好的选择---Catalog。Catalog的用法请参考“内容的搜索和分类”这一章。
在script中另一个常用功能是进行数学计算,这在ZPT和DTML中是较难实现的。math和random两个模块提供大量数学计算的函数,可使我们在zope中进行各种数学计算。
下面一个利用random模块随机显示图片的例子,它利用了random模块中一个很有趣的函数choice(),它可随机地从一个对象列表中返回对象。
## Script (Python) "randomImage" ## """ When called on a Folder that contains Image objects this script returns a random image. """ from random import choice return choice( context.objectValues(['Image']) )
如果你有一个文件夹,里面包含很多图象文件,你可以用zpt调用上面的script来随机显示文件夹中的图象。只要把以下语句加到 zpt中即可:<span tal:replace="structure context/Images/randomImage" />,randomImage脚本应该在Images目录可它的父目录中。
在zope中可通过printed变量显示print的输出。如:
## Script (Python) "printExample"
##
for word in ('Zope', 'on', 'a', 'rope'):
print word
return printed
返回:
Zope on a rope
该变量可用于调试和返回信息给zpt和dtml。
zope中的python script的内建功能和python的内建功能有一些不同,这些不同主要是基于安全考虑,如open这个函数在zope的python script是没有的,因为该函数可直接访问文件系统,存在安全问题。
大 部份python的内建功能在zope中都是可用的,如:None, abs, apply, callable, chr, cmp, complex, delattr, divmod, filter, float, getattr, hash, hex, int, isinstance, issubclass, list, len, long, map, max, min, oct, ord, repr, round, setattr, str, tuple。
range和pow在zope中也是可用的,但限制了可生成的最大数值和最大序列以防止DOS攻击。
same_type()可比较两个对象的类型,相同则返回真,否则返回假。
脚本有利于计算和表示逻辑关系,而zpt有利于显示界面,所以我们可利用这个特点把逻辑层和表现层分开。脚本负责逻辑层,zpt负责表现层。下面以一个例子说明一下这种关系,首先建立一个名为hello_world_pt的zpt模板文件,用于显示内容:
<p>Hello <span tal:replace="options/name | default">World</span>!</p>
建立一个script处理逻辑状态
return context.hello_world_pt(name="John Doe")
最终显示为:
<p>Hello John Doe!</p>
脚本的name变量的值传递给了options/name,从面显示以上内容。
在python script中使用的对象id名不能用“.”分隔,因为在python script中,点用于分隔对象,所以在zope的默认页面要写成index_html,而不是通常使用的index.html。 hello_world_pt不能写成hello_world.pt。但如果一定要在ids中用“.”,则可以用getattr()函数来获得zpt。如
return getattr(context, 'hello_world.pt')(name="John Doe")
和zpt一样,我们也可在脚本中调用dtml,例如在脚本中调用一个a_dtml_method:
# grab the method and the REQUEST from the context dtml_method = context.a_dtml_method REQUEST = context.REQUEST # call the dtml method, for parameters see below s = dtml_method(client=context, REQUEST=REQUEST, foo='bar') # s now holds the rendered html return s
在zope中脚本就像一个函数或方法,可返回值。如果返回的是多值,则zope把它们放到列表或字典中。下面是一个返回一个字典的例子:
## Script (Python) "compute_diets"
d = {'fat': 10,
'protein': 20,
'carbohydrate': 40,
}
return d
你可在下面的zpt中显示这些值:
<p tal:repeat="diet context/compute_diets"> This animal needs <span tal:replace="diet/fat" />kg fat, <span tal:replace="diet/protein" />kg protein, and <span tal:replace="diet/carbohydrate" />kg carbohydrates.</p>
编写脚本的一个重要资源是访问Zope API,它描述了Zope对象的内建动作,熟悉它方便我们使用Zope对象。Zope API文档可在zope系统的在线帮助里查到,也可到zope的官方网站上去查询最新的版本。下面举几个使用Zope API的例子。
objs = context.objectValues(),获得当前文件夹中的所以对象列表。
id = context.getId(),获得当前文件夹id。
root = context.getPhysicalRoot(),获得zope的根路径名。
restrictedTraverse()是getPhysicalRoot()的补充,它使用相对路径。
path = "/Zoo/LargeAnimals/hippo" hippo_obj = context.restrictedTraverse(path)
使用manage_edit()可动态改变dtml和文档的内容。
# context has to be a DTML method or document!
context.manage_edit('new content', 'new title')
getProperty()可返回对象的属性。
pattern = context.getProperty('pattern')
return pattern
manage_changeProperties()可改变对象的属性,需改变对象的属性一定要存在。
values = {'pattern' : 'spotted'}
context.manage_changeProperties(values)
改变对象的属性。
path = "/Zoo/LargeAnimals/hippo"
hippo_obj = context.restrictedTraverse(path)
hippo_obj.manage_addProperty('weight', 500, 'int')
在上下文中增加一个对象
context.manage_addProduct['PackageName'].manage_addProductName(id)
内建zope类型的PackageName为“OFSP”
一个例子,增加一个dtml方法:
add_method = context.manage_addProduct['OFSP'].manage_addDTMLMethod
add_method('object_id', file="Initial contents of the DTML Method")
对于其它类型,我们只要改变方法因子和参数即可。
如果要添加Folders,则用manage_addFolder。
如果要添加 UserFolders,则用manage_addUserFolder。
如果要添加 Images,则用manage_addImage。
如果要添加 Files,则用manage_addFile。
如果要添加 DTML Methods,则用manage_addDTMLMethod。
如果要添加 DTML Documents,则用manage_addDTMLDocument。
如果要添加 Version,则要用manage_addVersion。
如果你要在另外安装的产品目录下添加对象,则需用产品的安装目录替找“OFSP”即可,例如你安装了一个产品叫Boring,则添加对象的写法如下:
dd_method = context.manage_addProduct['Boring'].manage_addBoring add_method(id='test_boring')
删除对象使用以下方法
folder_object.manage_delObjects(id)_
更新对象内容使用以下方法
object.manage_upload(content)
Table of Contents
Zope页面模板是一种网页生成工具,它能帮助程序员与网页设计师协同开发zope应用程序。本章只介绍页面模板的基础功能,高级内容在高级页面模板这章介绍。
页面模板设计的目的是使程序员和网页设计师能够很好地一起协同工作。网页设计师可以用WYSIWYG的编辑器来建立模板,接着,程序员可编辑模板,增加逻辑处理功能,把模板组合到应用程序中。如果需要,网页设计师还可以把模板文件调出来修改而不会破坏程序员所做的工作。
页 面模板使用的是TAL(Template Attribute Language),DTML Methods、DTML Documents和SQL Methods使用的是DTML(Document Template Markup Language)。在zope中存在两种模板语言的原因主要有两个:
历史的原因,页面模板是一种新的技术,在zope2.5时才第一次发布,而DTML已经存在很长时间,有大量的基于DTML的产品和应用,很多人都还在使用DTML进行开发。
页 面模板和DTML都有各自的优缺点,页面模板能实现表现层、逻辑层和数据层分离,而DTML一旦嵌入html,就很难修改。还有页面模板能使你更好地控制 名字空间,而不象DTML,太灵活了,难于控制。但DTML在动态生邮件信息和SQL查询方面有优势,所以DTML还是应该学习一下的。
页面模板有两种模式:HTML Mode和XML Mode。多数使用HTML Mode,对于HTML Mode,Content-Type须设为text/html。
可以使用HTML编辑器来编写页面模板而不会显示TAL标记。
一个页面模板的例子:
<h1 tal:content="context/title">Sample Page Title</h1>
tal:content属性就是一个TAL语句,它有一个有效的XML名字空间(tal),所以有大多数编辑器都可以支持它。 content代表h1中包含的是文本,context/title是一个代表式,它提取所在文件夹的标题以替代“Sample Page Title”。
所有的TAL语句都以“tal:”开头。
建立一个页面模板只需从右上角 的对象添加列表选择“Page Template”即可。在页面模板中,“title”可填写模板的标题,“context-type”填模板的模式,默认是“text/html”。按 “Browse HTML source”可显示HTML的效果,但不解释tal标记。“Expand macros when editing”可控制是否在显示HTML时显示宏。
“template/title”是一个TAL的路径表达式,这种表达式在TALS中是很常用的,它获得模板的标题。
“context/objectValues”列出模板文件所在文件夹中所有对象。
“request/URL”获得当前请求的web地址。
“user/getUserName”获得已验证用户名。
路径表达式是默认的表式法,所以不用加前缀,如果要用python表达式,需加“python:”前缀。
以上面的例子为例,模板页面的写法如下:
<h1 tal:content="context/title">Sample Page Title</h1>
用python表达式可重写为:
<h1 tal:content="python: context.getProperty('title')">Sample Page Title</h1>
“python:variable1 == variable2”可用于进行比较运算。
“python:context.objectValues(['Folder'])”可把Folder传递参数给objectValues方法。
模板中的内容是一个模式,可通过TAL指令来控制如何动态替换模板内容。TAL的属性就是这样一组指令,它控制如何动态地替换、删除和重复模板内容。
上面介绍过tal:content可替换内容,替换完成后可保持原来的html元素属性。下面介绍的tal:replace也用于替换内容,但替换后不保持原来的html元素属性。如下:
<p>The URL is
<span tal:replace="request/URL">
http://www.example.com</span>.</p>
当你预览网页时,你只会看到“The URL is http://www.example.com.”,但当你访问该网页时它才会自动用当前路径替换它,你会看到“The URL is http://localhost:8080/template_test/simple_page”这样的实际路径。
tal:repeat用于重复内容。
<p tal:repeat="number python: range(4)" tal:content="number"> 999 </p>
range(4)会生成一个包含[0,1,2,3]的列表。repeat会遍历这个列表,并打印出它的值。
<table border="1" width="100%">
<tr>
<th>Id</th>
<th>Meta-Type</th>
<th>Title</th>
</tr>
<tr tal:repeat="item context/objectValues">
<td tal:content="item/getId">Id</td>
<td tal:content="item/meta_type">Meta-Type</td>
<td tal:content="item/title">Title</td>
</tr>
</table>
repeat还可遍历对象列表,如上所示,getId方法可获得的id,myta_type属性是元数据类型,title属性是标题。
在页面模板中,你可以根据条件动态地查询信息和可选地替换内容。这就要用到tal:condition这个模板属性了。
在zope中,零值、空串、空列表和内建变量nothing代表假,其它的为真。下面一个例子说明condition的用法:
<table tal:condition="python: context.objectValues(['Folder'])"
border="1" width="100%">
<tr>
<th>Id</th>
<th>Meta-Type</th>
<th>Title</th>
</tr>
<tr tal:repeat="item python: context.objectValues(['Folder'])">
<td tal:content="item/getId">Id</td>
<td tal:content="item/meta_type">Meta-Type</td>
<td tal:content="item/title">Title</td>
</tr>
</table>
上例将列出当前文件夹下所以子文件夹的id,meta-type和title。如果不存在文件夹,则不会显示表格。
tal:attributes可替换元素的属性。如下例所示,img标签的src属性被item/icon属性替换了。
<table tal:condition="python: context.objectValues(['Folder'])"
border="1" width="100%">
<tr>
<th>Id</th>
<th>Meta-Type</th>
<th>Title</th>
</tr>
<tr tal:repeat="item python: context.objectValues(['Folder'])">
<td tal:content="item/getId">Id</td>
<td><img src="/misc_/OFSP/File_icon.gif"
tal:attributes="src item/icon" />
<span tal:replace="item/meta_type">Meta-Type</span></td>
<td tal:content="item/title">Title</td>
</tr>
</table>
当你保存和按“test”标签进行测试,zope能帮你发现和定位页面模板的错误。保存时主要检测页面模板的语法错误,测试时主要检测逻辑错误。根目录下的error_log文档是一个日志,记录有系统出错的信息,还可通过配置控制日志记录的内容。
你可把需重复使用的表现形式和页面风格定义为宏,从而使它可在其它页面重用,使网站保持统一的页头、页脚和导航等。
定义宏时,用一种叫Macro Expansion Tag Attribute Language(METAL)的指令。这里有一个例子:
<p metal:define-macro="copyright"> Copyright 2001, <em>Foo, Bar, and Associates</em> Inc. </p>
这里定义了一个叫“copyright”的宏,它包含一些版权信息。
现在可在其它页面模板中使用该宏了,假设宏保存在名为“standard_template.pt”的页面模板。在其它页面模板中调用宏的方法如下:
<hr /> <b metal:use-macro="container/standard_template.pt/macros/copyright"> Macro goes here </b>
这样,当宏改变时,所有引用宏的页面都自动跟着更新。这样就可确保网站的表现统一。
在定义宏的页面模板中,宏的名字必须唯一。
在metal:use-macro语句中可以使用路径表达式,只要它能返回一个宏,如:
<p metal:use-macro="python:context.getMacro()"> Replaced with a dynamically determined macro, which is located by the getMacro script. </p>
在上例中,宏由getMacro()脚本动态生成。
在metal:use-macro中你可以使用“default”变量,表示没有使用宏。如:
<p metal:use-macro="default"> This content remains - no macro is used </p>
该变量可用于根据条件选择使用或不使用宏。
如果在页面模板编辑界面选中“Expand macros when editing”,则在页面中使用的宏就会在页面模板中展开。
我们可利用slots来在Macro中定制可选内容,如一个宏如下:
<div metal:define-macro="sidebar">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
</div>
这个宏已可正常使用,但有时在个别页面中会需要有一些附加的内容。这样我们就可以在宏中定义一个solts。如下所示:
<div metal:define-macro="sidebar">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
<span metal:define-slot="additional_info"></span>
</div>
这样,我们在页面模板中使用宏的时候就可以可选地使用这个slot以插入附加内容。如下所示:
<p metal:use-macro="container/master.html/macros/sidebar">
<b metal:fill-slot="additional_info">
Make sure to check out our <a href="/specials">specials</a>.
</b>
</p>
这样,就可在这个页面中增加了一个叫specials的链接。
我们知道,利用slots能够可选地增加内容,所以我们可利用这种特性,建立一个完全可定制的默认页面。如下所示:
<div metal:define-macro="sidebar">
<div metal:define-slot="links">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
</div>
<span metal:define-slot="additional_info"></span>
</div>
这个Macro是完全可定制的,你可重新定义links的内容以适应新的要求,如果你不重新定义links的内容,则利用上面已经定义的默认内容。
Slot可嵌套,可以在一个slot里面再定义一个slot。这为我们定制界面提供了最大的灵活性,下面是一个改进版的例子:
<div metal:define-macro="sidebar">
<div metal:define-slot="links">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
<span metal:define-slot="additional_links"></span>
</ul>
</div>
<span metal:define-slot="additional_info"></span>
</div>
如果你想完全改变links的内容,就可以以新的内容填充links即可,如果你不想全部改变,只是想增加一些新的内容,就可用嵌套的additional_links宏。在zope中,这种嵌套的层次可不受限制。
你可在同一上标签中使用METAL和TAL,例如:
<ul metal:define-macro="links"
tal:repeat="link context/getLinks">
<li>
<a href="link url"
tal:attributes="href link/url"
tal:content="link/name">link name</a>
</li>
</ul>
在这个例子中,getLinks是一个脚本,它返回链接对象的列表。METAL指令先于TAL指令执行,指令间不会存在冲突问题。我们可利用这种方式替代slot以定制宏,宏利用getLinks脚本可动态返回links,这样就可动态地改变链接的内容。
一般来说,我们利用slot来定制我们界面,用脚本来提供动态的内容。
利用slots,我们可用宏定义整个页面,从而提供一个统一的网站表现形式。例如:
<html metal:define-macro="page">
<head>
<title tal:content="context/title">The title</title>
</head>
<body>
<h1 metal:define-slot="headline"
tal:content="context/title">title</h1>
<p metal:define-slot="body">
This is the body.
</p>
<span metal:define-slot="footer">
<p>Copyright 2001 Fluffy Enterprises</p>
</span>
</body>
</html>
宏“page”定义了三个slots,分别是“headline”、“body”和“footer”。我们可在其它页面使用它:
<html metal:use-macro="container/master.html/macros/page">
<h1 metal:fill-slot="headline">
Press Release:
<span tal:replace="context/getHeadline">Headline</span>
</h1>
<p metal:fill-slot="body"
tal:content="context/getBody">
News item body goes here
</p>
</html>
这里重新定义了“headline”和“body”。其实该功能和样式表的原理差不多,目的都是为了在整个网站内保持风格的一致。在zope中,已预设了一个“standard_template.pt”文件,里面定义了head和body两个slot的宏“page”。
有 一点需指出的是,我们如何定位宏的路径,有三种选择,一个是root,直接使用根目录下的宏;一个是container;一个是context。 container和context之间的差主要是在如何继承关系方面。container以文件夹为起点,context以对象为起点。
zope支持内容组件、表现组件和逻辑组件,页面模板是一种表现组件,但它也可作为显示内容的组件。
zope中的内容组件有ZSQL Methods,Files和Images。DTML文档和方法因为可以在里面执行代码,所以不算纯内容组件。你可以在Files中编辑和存储小于64K的文本文件。但Files对象很简单,不支持元数据和很多其它特性。
zope 的CMF通过提供一个“rich”内容组件有效解决了这些问题。CMF是zope的一个内容管理插件,它扩展了zope有关内容管理方面的功能,提供如工 作流、skins和内容对象等功能。听说Zope3里已包含了所有CMF的功能,这样Zope3在内容管理方面的功能就比Zope2强大很多了。
Table of Contents
Zope是一个多用户系统,但它的安全机制不依靠操作系统的用户帐号,而是有自已的一个用户数据库。通过该用户数据库的授权和验证就可访问Zope应用程序和管理Zope系统。最重要的一点是zope的用户和操作系统的用户是完全没关系的。
在Zope中,通过Zope安全策略设置用户的权限,管理员有权设置相应的权限以适应你的商业要求。而且,使用安全策略还可以在Zope站点的不同地方设置“代理管理员”,代理管理员只在被授权管理的区域有管理员的权限。这样可实现分级管理,有效减轻管理员的工作量。
当一个用户访问受保护的资源时(如一个DTML方法),Zope将会弹出一个验证窗口,用户一旦填完提交,Zope就用这个信息来查找用户,默认,Zope使用Http基本验证方式。
基于cookie-based的验证可把cookie信息存在站点的文件夹中。
Zope通过验证窗口提供的用户名和密码进行标识用户。当在用户数据库找到相同的用户名时,则这个用户就可被Zope标识了。
用 户一旦被标识,验证可能会进行,也可能不需做验证。如果用户不访问受限资源,Zope会信任这个用户,而不做密码验证。匿名用户访问公共网站就是这种情 况。一旦用户需访问受限资源,验证就会发生,只要前面输入的密码和数据库该用户的密码一样,则验证成功。ZMI就是一种受限资源,所以我们每次访问时都需 输入用户名和密码以进行标识和验证。
一旦用户通过验证,Zope就会确认该用户可以访问什么受限资源,这个过程称为“授权”。记住,Zope唯一需要验证信息的原因是因为用户需访问不被匿名用户访问的资源。
授权的过程包含有两个媒介,角色和许可。角色表示用户可以做什么,如管理员、作者,编辑等,这些角色由管理员管理。用户可能会有一个或多个不同的角色;对象许可表示可以对对象做些什么操作,如查询、删除和管理属性等。这些许可可在Zope系统内或Zope产品中定义。
Zope 中的上下文可表示为Zope 对象结构中的一个“位置”。由于安全性要求,上下文对象存储在zope对象数据库中。我们可以这样描述一个上下文,Examples文件夹位于Zope根 文件夹内;或者也可这样说,一个名为show_css的DTML方法对象位于Zope根文件夹内。实质上,上下文可以看成对象在Zope对象数据库中的位 置,可用路径来描述。在对象数据库中的每一个对象都有一个和Web管理接口关联的安全策略。为了减轻建立安全策略的负担,所有对象都可从包含对象中获取安 全策略。这样也能大大减轻安全策略的维护量。只当有个别例外情况下,才为单独的对象指定不同的安全策略。
实质上,安全策略是通过角色来控制 用户对上下文可以进行什么操作,换句话说,也就是“谁”在“哪里”可以做“什么”。例如:一个文件夹(上下文)的安全策略允许“Manager”角色对该 文件夹有“删除对象”的许可。这个安全策略允许管理员在这个文件夹内删除对象。如果一个DTML Method在这个文件夹被建立,而且我们没有对该对象指定独立的安全策略,而是获取自文件夹,则管理员就有权删除它。在该文件夹下建立的子文件夹同样也 获取了该安全策略,除非你在子文件夹中重新指定新的安全策略。
Zope提供一个默认的用户,用户名由建立实例时由用户命名。该用户拥用管理员角色,允许我们管理实例的对象。为了使其它用户可以登录入Zope,我们可以建立一个新的用户。
用户帐号由用户对象定义,一个Zope用户有一个名称、一个密码、一个或多个角色和几个属性。指定角色是为了方便控制用户在Zope中的行为。
一 个Zope用户必须在用户文件夹内定义,用户文件夹内包含了所有的Zope用户帐号。用户文件夹通常有一个叫“acl_users”的id号。在一个 Zope实例中可以存在多个用户文件夹,但在一个文件夹内只可包含一个用户文件夹。建立用户的方法很简单,我们只要进行用户文件夹,按“Add”按钮即可 进入增加用户的窗口,在增加用户窗口填上用户名和密码,如果现在要指定角色就可在下面的Roles栏中选择相应的角色,默认有两个,一个是 Manager,另一个是Owner。Manager角色使你拥有管理zope系统的权限,Owner角色已在多数情况已不用了,它的存在只是为了系统向 后兼容。因为如果你把Owner角色指定给用户,则该用户就会变成用户文件夹所在的文件中所有对象的拥有者,包含该文件夹的子文件夹。这显然是不合适的。 所以一般都不用该角色了。我们也可自定义角色,这在下面的内容中会讲到。Domains是用来控制用户只能由该域登录,该选项可增加系统的安全性。例如我 只想我的用户只能从“myjob.org”登录,就可把它填到Dominos栏内,该可填多条项目,也支持用IP,如“192.168.1.*”。最后按 “Add”完成用户添加,这时你可在用户文件夹内看到刚建的用户了。基本的用户帐号不支持邮件地址、电话号码等附加的属性。不过我们可通过使用扩展用户文 件夹来获得这些功能,如exUserFolder等。还有一点,用户帐号不能在两个用户文件夹间进行复制和粘贴操作。
通过单击用户文件夹中的用户名就可进行编辑,内容和新增用户时的差不多。在基本用户文件夹中,除用户名外,其它的内容都可修改。如果要修改用户名,就只有删除再重建了。
我们不能通过管理界面来找到用户的密码,即使是管理员也不行,所以如果用户丢失了密码,只有通过管理员从新设置一个新的密码,而旧的密码就永久丢失了。
和 其它的Zope管理功能一样,编辑用户功能也是受安全策略保护的,用户只有在所属用户文件夹内拥有“Manage Users”许可才能更改密码。我们都希望用户可以自由地修改自已的密码,但如果开放了用户的“Manage Users”许可,那该用户也就获得了修改同一用户文件夹内其它用户密码的许可,这可不是我们想要的。为了把修改用户密码的功能下放给用户,而不影响其它 用户。我们可以通过设置“Proxy Roles”来执行一段修改代码来实现。有关代码和代理角色下面会详细介绍。网上也有一篇关于如何通过代理角色修改自已密码的文章,网址是http://www.zope.org/Members/msx/ChangeOwnPassword-mini-howto
用户文件夹和一般文件夹差不多,你可以建立,编辑和删除文件夹内的对象,但你不能在用户文件夹内进行复制和粘贴。
在Zope的对象数据库结构中,允许在不同的位置建立多个用户文件夹。一个用户文件夹内的用户只能访问该用户文件夹所在文件夹的受限资源。所以用户文件夹的位置确定了用户可访问资源的区域。
如 果你在根目录的用户文件夹下定义了一个用户,则该用户就可访问根目录下的受限资源。你也可在Zope中的任意文件夹内建立用户文件夹。当你在一个子文件夹 内建立一个用户文件夹时,该用户文件夹内的用户只可访问这个子文件夹内的受限资源。一个例子:考虑一个位于 /BeautySchool/Hair/acl_users的用户文件夹。test是该用户文件夹下的一个用户。test不能访问 /BeautySchool/Hair文件夹的上一级文件夹的受限资源,他只能访问/BeautySchool/Hair文件内和该文件夹下其它子文件夹 的受限资源。如果test被赋予“Manager”角色,则他可通过/BeautyScholl/Hair/manage路径来管理该文件夹下的资源,但 不能用/BeautySchool/manage路径访问。把路径指向有管理员角色的用户文件夹所在的目录,可打开ZMI,通过验证后就可进行管理工作。 当然了,用户权限区域只在访问受限资源时有意义,如果我们访问不受限资源,Zope就不会进行验证和授权的工作。
zope中这种权限区域划分管理的概念可方便网站管理员把管理权细化,并下放到下一级管理员,大大减轻管理员的工作量。
有时,你不想用Zope自带的用户文件夹来管理你的用户,因为你可能已有一个用户数据库,或者你想用一些更好的工具来管理用户数据。Zope允许我们使用其它的存储方式来存储用户信息。你可到Zope网站的产品区找到很多相关的产品,如:
可插式用户文件夹。它提供一套插件,可根据请求来生成用户。
可扩展式用户文件夹。可通过不同的数据源来验证用户信息,如数据库、RADIUS、SMB或者ZODB。
这个用户文件夹用Unix/etc/passwd风格的文件来验证用户信息
这个用户文件夹允许我们通过一个LDAP服务器来进行用户验证。
通过NT的用户帐号来验证,Zope服务器必须运行在Windows平台下。
该用户文件夹把验证信息存储在MySQL数据中。
以上的用户文件夹给我们提供了多种选择,但登录和验证的过程都是一样的。在Zope中,有几个特殊帐号是不能通过这些用户文件夹来管理的。下一节就会讲到了。
Zope 有三个特殊帐号,它们不通过用户文件夹来定义。这三个帐号分别是:anonymous user(匿名用户)、emergency user(紧急用户)、initial manager(初始管理员用户)。匿名用户使用得最多,紧急用户帐号和初始管理员帐号较少使用,但他们很重要。
匿 名用户不需验证,如果你没有用户帐号,就默认为匿名用户。匿名用户拥有Anonymous角色,可访问公共信息。你还可通过设置安全策略使它拥有更多的权 限。即使你拥有用户帐号,但当你访问公共信息时,Zope会一直把你当成匿名用户,直到你访问受限资源时,通过输入用户名和密码成功验证后,才成为特定的 用户。
紧 急用户不受一般安全策略的约束,但它除用户对象外,不能建立任何其它新的对象。当你用紧急用户帐号建立“content”,“logic”、 “presentation”对象时,系统将提示“ Error caused by trying to create a new object when logged in as the emergency user.”出错信息。紧急用户的主要作用有两个:
修正错误的许可。如果你设置了错误许可而被系统拒于门外时,就可用紧急用户帐号登录入管理界面并修改错误。
建立和修改用户帐号。紧急用户帐号一个典型应用就是用它来修改管理员帐号,当你丢失管理员用户名或密码时,就不会被系统拒于门外了。
和Zope中其它用户帐号的建立方法不同,建立紧急用户帐号要在文件系统中通过命令来建立。这个命令叫zpasswd.py,位于ZOPE_HOME的utilities目录下。具体操用如下:
$ cd (... where your ZOPE_HOME is... ) $ python zpasswd.py access Username: superuser Password: Verify password: Please choose a format from: SHA - SHA-1 hashed password CRYPT - UNIX-style crypt password CLEARTEXT - no protection. Encoding: SHA Domain restrictions:
设置了紧急用户帐号了,需重启服务器帐号才会生效。
初始管理员帐号由Zope安装程序建立,用于第一次登录系统。我们也可用zpasswd.py程序来管理,如:
$ cd ( ... were your ZOPE_HOME is ... ) $ python zpasswd.py inituser Username: bob Password: Verify password: Please choose a format from: SHA - SHA-1 hashed password CRYPT - UNIX-style crypt password CLEARTEXT - no protection. Encoding: SHA Domain restrictions:
当你启动zope服务器时,如果root下面的用户文件夹内没有用户,而inituser文件又存在话,那么存在inituser里的 用户就会在root下的用户文件夹内创建。如果root下面的用户文件夹已存在用户,则inituser就不会使用。这个帐号文件很少使用。当你用一个新 的数据库启动服务器时(旧的var/Data.fs被删除了),你才需要它。
zope 的用户文件夹为了兼容所有浏览器,使用了基本的HTTP验证协议来验证用户,信息经过线路时很容易被截获并解码。所以如果你想建立一个高安全性的zope 服务器。你需用SSL连接你的服务器。一种实现方法是用apache等支持SSL的Web服务器作为Zope的前端。这样位于后端的Zope服务器就会受 到保护,这里有一篇apache+zope+ssl的集成文档http://zope.org/Members/unfo/apache_zserver_ssl。大家可参考一下。本书的“虚拟主机”一章会介绍一些相关的知识。
Zope 安全策略控制授权,它定义谁在哪里可以做什么。安全策略可以描述为如何用角色和许可来配置上下文中的对象。角色可把用户分类,许可用以保护对象。所以安全 策略也就是定义一类用户(角色)可以在网站中做什么(许可)。比指定一个用户对一个对象可以做什么更好的方法是指定一类用户在网站的一个区域可以做些什 么。这可使我们的安全策略简单而有力。当然,Zope两种方式都能很好地支持。
zope 用户可赋予定义了许可的角色,如Manager(管理员)、Anouymous(匿名用户)和Authenticated(验证用户)。zope中的角色 类似于unix系统中的组,每个zope用户都可以有一种或多种的角色。这可方便管理员进行安全策略管理。管理员可以把一组许可赋予一个角色,再把一组用 户定义为该角色,这样,这一组用户就会有相同许可,可大简化管理员的管理工作。Zope内置有四种角色:
Manager,这是zope的管理员角色,执行管理任务。
Anonymous,这是匿名用户的角色。这个角色可以访问公共资源。该角色一般不能修改对象。
Owner,所有者角色在你建立对象时自动被赋予。也就是说,如果这个对象是由你建立的,你在该对象上就被赋予了所有者的角色。
Authenticated,当你通过验证,登录入Zope系统时,自动被分配该角色。这个角色表示Zope知道这个用户的身份。
全 局角色是在对象的“Security”标签内“roles”列显示的角色。建立一个角色可在“Security”标签页,拉到页面底部,就会有一个 “User defined roles”栏,在该栏填上角色名,按Add Role按钮即可。角色名最好和角色的功能相关联,不要取没意义的名字。
角色建立后,我们可在“roles”列中看到新建的角色,在页面底也出现了“delete role”按钮。刚建的角色也出现在这里。选中一个角色按“delete role”按钮就可把角色删除。
角色的使用范围也是按对象结构的获取来定义的,只能用于定义该角色的文件夹内和下层文件夹,上层文件夹不能使用该角色。所以如果你想定义一个在整个系统中可用的角色,就可在root文件夹内定义。
一般来说,角色应该应用于网站的大区域,如果你想建立一个角色来限制用户只可部份地访问你网站,我们可用另外一种方法来完成,而不必须要使用角色。如:通过在你想保护的文件夹中设置已有的角色的许可,或在该文件夹中设置用户文件夹,定义一组用户来控制他们的访问。
本 地角色是Zope中的一种高级安全特性。特定用户可通过本地角色获得操作特定对象的额外权限。例如,我通过ZMI新建一个对象,并把对象通过本地角色功能 把Owner角色指定给了一个普通用户。本来这个普通用户是不具有编辑对象许可的,但它可通过额外的Owner本地角色来编辑新建立的这个对象。一般我们 应该尽量避免使用这种一对一的安全设置,因为这样会增加管理的复杂程序和维护的难度。
许 可是定义在一个Zope对象可做的动作。就象角色是一组用户的抽象一样,许可是对象行为的抽象。例如,很多对象都可以被浏览,则这个浏览行为就受到 “View”许可的约束。许可由zope产品开发者和内核定义。每个Zope产品都会建立一套与产品对象相关的许可。有些许可只对应一种对象,如 “Change DTML Methods”许可只是保护DTML Methods。一些许可对应多种对象,如“FTP access”和“WebDAV access”许可就可保护所有经由FTP和WebDAV访问的所有对象。你可通过“Security”管理标签来查看对象的许可。所有核心的许可在以下 网址查到,http://www.zope.org/Documentation/Books/ZDG/current/AppendixA.stx
安 全策略是通过配置角色和许可来设置的,它定义在网站中“谁”在“哪里”可以“做什么”。要设置一个对象的安全策略,只要进入该对象的“Security” 标签设置即可。“Security”页面中间有很多选择框,竖列是角色栏,水平行是许可项。通过横坚交叉方式确定角色的许可。
很多Zope产品会增加自已的许可,所以这个表格的长度会随着你安装产品的数量不断增加。所以我们建立产品开发者尽量要用回系统自带的许可。
当 你打开根目录的“Security”页时,你会注意到系统已默认为我们配置了很多许可,而且该许可已可很好地工作。它赋予了管理员角色最大的许可,以进行 系统管理工作;而匿名用户角色就只配置了较少的许可,适用访问公共信息。我们可在这个基础上进行配置,以适合我们自已的安全需求。例如:我们可以把匿名用 户的许可全部取消,这样你的网站就变成私有的了,只有管理员可以访问。
![]() | |
| 如果你在根目录上作以上设置,则该设置会应用到整个站点。如果你想在个别目录进行设置就需进入相应文件夹再进行配置。 | |
Zope 中不同的安全策略是如果相互作用的呢?我们可以在不同的对象中建立安全策略,但哪个策略才是最终控制对象的呢?答案是如果对象本身设置了一个安全策略,则 使用这个安全策略。否则,对象会通过获取机制获取上一层对象的安全策略。Zope在安全策略中广泛使用获取机制。获取中Zope中在文件夹或子文件夹内共 享对象信息的一种结构。Zope安全系统通过获取来共享安全策略,所以可通过上层文件夹来控制下层对象和子文件夹。
你可通过 “Security”标签来设置安全策略获取功能。进入该标签后,屏幕左边有一列叫“Acquire permission settings”的选择框,每一个选择框代表一个或多个许可。默认所有框都是选中的,也就是说该对象获取了上一层对象的许可,再加上在这里指定给角色的 许可共同组成该对象的安全策略。
![]() | |
| 根文件夹的左边是没有这些选择框的,因为它已是最高级的对象的,不存在获取。 | |
假 设你需要一个私有的文件夹,只有认证用户可以浏览。正如前面所说,我们可以把这个对象的上下文中Anonymous角色的“View”许可去掉即可。但这 还不够,因为如果“Acquire permission settins”选择框是选中的话,它还是会获取上层对象中Anonymous角色的“View”许可的。所以,要设置私有文件夹,一定要取消 “Acquire permission settings”选项。这样才可确保只有在当前显式设置的许可才有效。
![]() | |
| 如果你不想获取安全策略,请把“Acquire permission settings”的选择框清空。 | |
zope 安全的基本概念很简单:角色和许可是建立安全策略的两大要素。角色(全局或本地)分配给用户,用户的行为受到对象中角色所拥有许可的控制。这些简单的工具 以许多不同的方法组合在一起,使安全管理复杂化。以下有几个基本的安全管理模板的例子,可以帮助我们有效地和容易地管理我们的安全架构。
这里有几点安全管理的原则,这些原则不是特效药和秘方,但会在你面对一个未知领域时给你一些指导性的建议。
在你需控制范围的最高层设置用户,而不仅仅是上一层。
把需由相同人员管理的一组对象放到同一个文件夹内。
保持简单。
第一和第二差不多,是Zope安全结构的基础要求。一般你需把互相关系密切的资源和对象放在一起,这不是必须的,但可帮助你合理安排文件夹和子文件夹。
不 管你网站结构怎么样,请尽量保持简单。复杂的安全设置会使你较难理解、管理和确保它的有效性。尽量减少新角色的建立,同时多一些用安全策略获取机制来代替 显式的安全策略设置。当你的安全策略、用户和角色越来越多,越来越复杂时,你应该重新考虑一下你的配置,可有会有一种更简单的方法可以帮助你实现相同的功 能。
一 个常用的安全规则是在根文件夹定义一个全局的安全策略,其它文件夹通过获取机制获得这个安全策略。在需要时,也可在子文件夹内附加新的策略以扩充全局策 略。但要限制这种情况,当你发现你要在多个文件夹使用附加策略时,应考虑把这些对象集中到一个文件夹内来,只在这个文件夹内设置附加策略。
如果你的子策略中某个许可的约束性大于全局策略时,你应该取消该许可的获取功能,在本地重新设置本地许可。
简单的规则能使你的系统更安全、更强健、更容易管理和维护。这一点对于任何的安全架构是都很重要的。
委 托的概念在zope中很重要。Zope鼓励你把相同的资源放在一个文件夹内,并在该文件夹内建立一个用户来管理这些资源。这个用户将被分配管理员角色,对 该文件夹及该文件夹下面的对象有管理员的权限,它不能以管理员身份登录进任何除该文件夹外的文件夹。这个规则可递归使用,也就是说,该文件夹的管理员也有 权定义子文件夹的管理员,把一些工作再细分下去。
本地管理员规则是强大而稳定的,但控制的程度较粗,只能控制有权或没权访问。有时我们需要一些较细致的访问控制,例如我们可能要具体控制到这个资源可以由谁来访问,这就要用到角色。角色可以让我们定义一类用户,针对这类用户设置相应的安全策略。
角 色可以帮我们解决本地管理员委托的中存在的一个问题,那就是本地管理员规则需要一个固定的从属结构。但当两个不同组的人需访问同一资源,而一个组又不是另 一个组的管理员时,委托就做不到很好地管理了。换句话说,我们不能在网站的这一个文件夹中定义一个管理员来管理网站另外一个文件夹中的资源。但角色可以帮 我们做到控制网站中不同地位置的资源访问。
让我们用一个例子来说明本地管理委托的第二个缺陷。假设你维护一个大型的医药公司的网站,有两类 用户,一类是科研人员,一类是销售人员。一般来说,这两类人员都各自管理自已的网站资源。但是,假设有一些资源两类人员都需要,比如一些广告信息。如果我 们科研人员在“Science”文件夹里定义,销售人员在“Sales”文件夹里定义。那广告信息文件夹应该放到哪个文件夹里呢?因为这两个文件不是嵌套 的,所以放在哪个文件夹都不能使两个文件夹的管理员同时管理广告信息。那我们应该怎么做呢?答案就是使用角色。我们可以在这两个文件夹的上一层文件夹内建 立两个角色,一个叫“Scientist”,一个叫“SalesPerson”。在这个上层文件夹里给这两个角色指定合适的许可,而不是管理员角色。在 “Science”文件夹把“Scientist”角色设为管理员,同样,在“Sales”文件夹,把“Salesperson”设为管理员。最后,在广 告文件夹,我们授予“Scientist”和“SalesPerson”适当的许可。这样,利用角色就实现了不同位置的访问控制了。
经过上面有关安全规则的讨论,我们应该知道怎么使用用户文件夹、角色和安全策略来制定应用程序的安全结构。下面我们讨论两个有关安全的高级主题,一个是怎样进行安全检测,另一个是如果安全地执行可执行内容。
在开发一个Zope应用程序时,开发人员一般不用编写程序进行安全检测,Zope会做很多工作。如果你访问一个受限资源时,Zope会自动弹出登录框,进行安全验证。如果你没有适当的访问许可,Zope会禁止你对资源的访问。
但是,我们希望可进行手动安全检测工作。主要原因不是为了阻止非法用户的访问,而是对合法用户访问受限资源的条件进行约束,以保证用户的正常访问,而不会进行一些无效的操作。
最 通常的安全检测是检查当前用户是否有一个许可。我们可以用“checkPermission”API来完成这项工作。假设你的应用程序允许用户上传文件, 这个操作受“Add Documents,Images,and Files”许可保护。我们可以用一个DTML里测试一个用户是否有这个许可。代码如下:
<dtml-if expr="_.SecurityCheckPermission(
'Add Documents, Images, and Files', this())">
<form action="upload">
...
</form>
</dtml-if>
“SecurityCheckPermisson”函数有两个参数,一个是许可名,另一个是对象名。在DTML中可用this()传递当前的对象名。对于ZPT,语法不些不同,但操作是一样的。如:
<form action="upload"
tal:condition="python: modules['AccessControl'].getSecurityManager().checkPermission('Add /
Documents, Images, and Files', here)">
...
</form>
python script也可以用来完成相同的任务,通过ZPT来调用安全检测的python script,下面一个调用check_security安全检测脚本的页面模板:
<form action="upload"
tal:condition="python: here.check_security('Add Documents, Images and Files', here)">
check_security脚本如下:
## Script (Python) "check_security" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=permission, object ##title=Checks security on behalf of a caller from AccessControl import getSecurityManager sec_mgr = getSecurityManager() return sec_mgr.checkPermission(permission, object)
在上例我们可以看到可通过手工的方式进行许可检测,Zope API中还有一些其它的函数为完成该工作,但“checkPermission”是最有用一个函数。通过“checkPermission”函数,我们可 以确认当前用户是否有适当的许可。你可通过访问用户对象获得当前用户名,用户对象和其它Zope对象一样,你可通过在API里定义的方法来操作对象。假设 我们想显示当前用户名,可在DTML里这样写:
<dtml-var expr="_.SecurityGetUser().getUserName()">
在DTML中用SecurityGetUser的getUserName()方法可获当前登录的用户名。ZPT中用 getUserName的user方法也可获得当前登录用户名。如果用户没有登录,则获得一个匿名用户名叫“Anonymous User”。在Zpt中的写法如下:
<p tal:content="user/getUserName">username</p>
Zope的安全API在本书的附录中有详细介绍。在Zope的ZMI通过中“help”也可查询到相关的内容。
我们已了解了有关Zope安全的基础内容,所有权和可执行内容的概念是什么呢?Zope用所有权来关联对象和对象的创建者,可执行内容包含脚本、DTML方法和DTML文档等执行用户代码的对象。
在针对可信用户的小型网站,这些高级主题可以忽略,但在一个允许不可信用户创建和管理Zope对象的大型网站,明白所有权和可执行内容这些内容就显得很重要了。
木马攻击是我们需理解所有权和可执行内容控制的一个原因,木马会欺骗用户进行有害的操作,一个典型的木马是它会假装一个没问题程序,但当你不自觉地运行它时,就会对你的系统造成损害。
所有操作系统对这类型攻击的防护能力都较弱。对于Web平台,进行这种攻击所要做的工作是欺骗一个授权用户访问一个存在有害程序的网址。这种攻击很难预防。你可以很容易欺骗其他人点击一个友好的链接,或用javascript技术来把链接重定向到一个有害的网址。
Zope 提供了对这类木马的保护措施。Zope在服务器端通过限制以作者为基础的Web资源的能力来保护网站。如果一个不受信任的用户创建了一个页面,则这个页面 通过可信用的浏览户来执行有害操作的能力就会受到限制。例如,假设一个不受信用户创建了一个DTML页面或Python script来删除你网站上的所有页面。如果有一个用户访问该页面,将会出错,不会执行有害代码,因为该页面没有足够的许可来执行删除操作。如果是管理员 浏览该网页,也会出错,即使管理员有足够的许可来执行这些有害操作。Zope是通过所有权信息和可执行内容控制来提供这些限制保护的。
当 一个用户创建一个Zope对象,这个用户就拥有该对象。如果一个对象没有拥有者则被称为“unowned”。所有权信息保存在对象内部,就象Unix系统 中文件所有权的概念。你可通过对象的“Ownership”管理标签来查看所有权相关信息。该标签可显示该对象是否被拥用和被谁拥有。如果你有“Take ownership”的许可,你可以把其它有所拥用的对象变成自已的。这项功能在拥有对象的帐号被删除后很有用,或者可通过该功能接管理其它用户的对象。
在前面的章节中我们提到,因为可以设置本地Owner角色,所以所有权会影响安全策略的设置。同时也因为所有权可控制角色的可执行内容。
对象的“owner”角色和所有权没有什么关系。“owner”角色和可执行内容的所有权是不同的。因为一个可执行对象有“Owner”本地角色并不意味着它是这个对象的拥有者。
DTML 文档、DTML方法、SQL方法、python脚本都是可执行内容,因为它们可动态生成内容。对象内容可通过Web来编辑。当你访问它的URL时, Zope就会执行对象的可执行内容。对象的操作会受到对象拥用者的角色和访问者的角色的限制。也就是说,一个可执行对象只能执行拥用者和访问者都经授权允 许的操作。这可阻止一个无特权的用户写一个有害的脚本来欺骗一个特权用户来执行。你不能欺骗其他人执行一个你没有权执行的操作。这就是zope为什么能利 用所有权来保护服务器端免受木马攻击的原因。
![]() | |
| 一个“unowned”对象是不能执行的。如果你要运行可执行对象,请确保设置了正确的所有权。 | |