最小的Zope编程 How-to
作者:maxm 最后修改时间:2001/08/12 翻译:limodou 翻译时间:2001/10/25
>>原文链接:http://www.zope.org/Members/maxm/HowTo/minimal_01/
Zope产品的编程享有困难的名声。这不是真的。我相信这只不过是因为所演示的例子造成的,就象令人讨厌的类一样,展示了太多的特性的缘故。而且这些例子在解释功能的时候还跳过了几个步骤,使得“Zope”的道路看上去很长并令人厌烦。所有不必要的额外的代码“只是因为要加入”而被加进去了。
我还记得第一次看到这些令人讨厌的例子的时候,我想这些都是关于Zope的难懂的巫术,不会有人真正的理解,除非在别的地方看到过。
因此我决定写一系列的How-to,用来解释以最简单的方法来进行Zope编程。这一过程将会非常非常缓慢,并且每一行代码将被详细地解释。如果有任何东西你不太清楚,请把它告诉我。
这一系列的第二部分可以在这里找到。
一个非常简单的类
作为开始,我将创建一个可以想到的最简单的Python类,然后演示都需要向它加些什么东西,用来生成Zope的产品(product)。在那之后,我将演示另一个例子,它将这个简单产品进行扩展用于其它方面。
我的最小的“Hello world”类看上去象这样:
class minimal:
def index_html(self):
return '<html><body>Hello World</body></html>'
到了这里,它可以在Python中运行,但在Zope中不行。有几个东西需要增加或进行修改。第一件事情就是要知道Zope不会公开(publish)不带文档字符串(doc string)的任何东西。所以需要把它加进去:
class minimal:
"minimal object"
def index_html(self):
return '<html><body>Hello World</body></html>'
Zope进行对象公开。意味着一个在Python对象中的方法可以被看成web服务器上的一个页面。这就是说你最终可以将上面的类的寻址写成象这样:
http://localhost:8080/minimal_id/index_html
会得到“Hello World”的结果。但是你可以看到,这个对象有了一个叫做“minimal_id”的寻址。它是从哪来的?嗯没什么地方。但是应该很明显,任何一个对象需要一个id,以便Zope能够对它寻址。所以我需要向对象增加一个id属性。我将重复叙述“任何Zope产品必需拥有一个叫做id的属性。否则Zope没有办法去调用它,你将没有办法给它一个URL”:
class minimal:
"minimal object"
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id # And this little fella is NOT optional
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
编写和使用一个Zope产品有三个步骤:
1 所有的Zope产品需要被构造为Python的包。放在“lib/python/products/"中的一个文件夹中。Zope将这个目录下的所有的包看成为产品,并且试着在启动时安装它们。这个包在某些阶段必需使用Zope API以便可以通过Zope进行发布。
2 如果包创建的正确,它将在Zope管理视图中的产品列表中显示出来。
3 它现在可以被安装了。意味着你可以将这个产品的一个拷贝放到任何你喜欢的文件夹中去。只要进入一个文件夹,从选择框中选择这个产品就可以了 (译注:这里已经是在说从Zope的内容管理界面中增加一个产品,而不是产品的安装)。这就是Zope真正的力量。如果你生成了一个产品,它就能够在你的或其它某个人的站点的任何地方被容易地重用。你可以把它放到Zope.org上,让其它人在他们的站点上使用它。这个真是太-太-太-伟大了,恕我直言,这就是使用Zope的最真正的原因。
哦,另外还有一点东西需要加入,就是允许向一个文件夹添加产品。需要在选择框中有一个名字才行。这就叫做"元类型(meta_type)",它应该是给你的产品起的唯一识别的名字。所以不要把它叫成象“dtml-document”或“Folder”或类似的东西。
我把这个产品的元类型定为“minimal”:
class minimal:
"minimal object"
meta_type = 'minimal' # this is the same for all
# instances of this class
# so it's declared here and
# not in "__init__()"
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id # And this little fella is NOT optional
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
为了同Zope一起良好的运行,我的类得需要一些基本的功能。通常通过对一个叫做“SimpleItem.Item”的类进行子类化来实现。
从Zope书中引用的原话为“这个基类向你提供能够同Zope管理界面一同工作所需要的基础。通过从Item进行继承,你的产品类可以获得一大堆的特性:剪切和粘贴的能力,具有管理视图的能力,WebDAV支持,基本的FTP支持,撤消支持,所有权支持,和遍历(traversal)控制。同时它也向你提供了一些标准的方法用于管理视图和包括了manage_main()的错误显示。你也可以得到getId(),title_or_id(), title_and_id()方法和this()的DTML应用方法。最后这个类向你的产品提供基本的对dtml-tree标记的支持。Item真是一个提供了除厨房和水池外一切东西的基类。”
但实际上我可以做的比它更出色。有另一个叫做“SimpleItem.SimpleItem”的类,它可以实现所以上面的功能,而且更多。“SimpleItem.SimpleItem”还提供了获取(aquisition)和持续(persistence)的功能。
这个类位于OFS的Zope包中,所以它必需被导入。然后我修改了类的声明以便“minimal”类可以对其子类化(译注:用派生可能更清楚):
from OFS import SimpleItem
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal' # this is the same for all
# instances of this class
# so it's declared here and
# not in "__init__()"
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id # And this little fella is NOT optional
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
技巧!这面是Zope在使用中的技巧之一。Python可以一次从几个基类进行继承。一些类提供了某些种类的功能,别一些则提供了另一些。这种混合类的编码风格叫作“mixins”[1],它强大但理解困难。小心一点!
现在我有了一个完整的类。我仅需要一个可以将对象放入一个文件夹的方法。这可以通过名为“_setObject()”的标准Zope方法来实现。
_setObject是一个在ObjectManager类中的方法。这一部分要花一点心思去理解,因为_setObject()不是做为minimal的方法,而是作为ObjectManager的方法(minimal类将被加入其中)来调用的。
如果我试着向一个叫做myFolder的文件夹添加一个minimal类,我象这样调用:
myFolder._setObject('minimal', minimal('minimal_id'))
仅有“_setObject”是不可能将一个你的对象实例插入到一个文件夹中去的。必需要调用它,并给出一些参数。所以我需要一个新的方法来做这件事。我叫它为“manage_addMinimal()”方法。
from OFS import SimpleItem
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal' # this is the same for all
# instances of this class
# so it's declared here and
# not in "__init__()"
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id # And this little fella is NOT optional
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
def manage_addMinimal(self):
"Add a Minimal to a folder."
self._setObject('minimal_id', minimal('minimal_id'))
从函数的作用范围可以清楚的看到,manage_addMinimal()函数是从试图向自身添加minimal产品的文件夹中调用的。所以换句话说,manage_addMinimal()方法不是属于“minimal”类的方法,而是属于“ObjectManager”类的方法。
"self._setObject('minimal_id', minimal('minimal_id'))"
传入的第一个参数是id。这里我叫它为minimal_id。所以id是对这个对象的硬连接,应该总是minimal_id。自然这样不太好,因为在一个文件夹中只能有一个实例。但,嗨 ... 这是最小的 ... 记得吗?
然而最后一个方法存在一个问题。当调用时,它不返回任何值。所以当这个方法被调用时,浏览器会显示些什么呢??你猜一猜。什么都没有!或者当你已经放开选择框来增加实例时,只是看上去什么都没有发生。真扫兴。很自然我应增加某种返回页面,但是这对于一个最小产品来说太麻烦了。我将只是让浏览器重定向到index_html。
“redirect()”方法存在于RESPONSE对象中,并且每次Zope调用一个方法时,RESPONSE都作为一个参数传给方法。所以很容易得到它。它应该正好被加入到方法的参数列表中。(这种用法与.asp的方法相比不同之处为在.asp中“Response”是一个全局对象。)
但不管怎么样,类现在完成了,并且看上去象这样:
from OFS import SimpleItem
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal' # this is the same for all
# instances of this class
# so it's declared here and
# not in "__init__()"
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id # And this little fella is NOT optional
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
def manage_addMinimal(self):
"Add a Minimal to a folder."
self._setObject('minimal_id', minimal('minimal_id'))
RESPONSE.redirect('index_html')
现在我只需要将它放入一个Python包中,并且把这个包放到我的Zope安装中的正确目录中去即可。
我的包叫做minimal,结构为:
minimal
minimal.py
__init__.py
创建包
现在所要做的是使用某种方法通知Zope这个包是一个Zope产品,并且当某人放开选择框向一个文件夹添加实例时,Zope需要知道调用哪个方法来实现添加。我知道,你也知道是“manage_addMinimal()”,但是Zope不知道。我们使用在包中叫做__init__.py的文件来告诉Zope所有这一切:
import minimal
def initialize(context):
"""Initialize the minimal product.
This makes the object apear in the product list"""
context.registerClass(
minimal.minimal,
constructors = (
minimal.manage_addMinimal, # This is called when
# someone adds the product
)
)
首先我从minimal.py中导入minimal,这么做是因为minimal被用在了“initialize()”方法中。
“initialize()”方法是当启动Zope服务器时由Zope来调用的。这就是为什么我们需要重新启动Zope来安装一个新产品的原因。
Zope传递“context”类给“initialize()”。然后“context.registerClass()”通过将 “minimal”作为第一个传给它的参数在Zope中注册类。第二个参数是从minimal模块来的方法元组(tuple),它们被用作构造器。构造器是这样的方法,它们向文件夹添加实例。并且你应该记得它是“manage_addMinimal()”。记住不要在方法名字后面加小括号。我们不想让方法被执行,只是想告诉Zope是什么方法。这就是将“manage_addMinimal”方法绑定到objectManager上去的地方。
通常使用一个表格来添加产品,但是我不在这里那样做。因为那样不会是最小的。关于这一点后面再详细讲。
这就是一个我能想到的具有全部Zope产品功能的最小的类。当然它仍然十分粗糙,但是它就是编写一个类所需要的全部知识了。这个类可以具有象.asp或.php一样的功能。它没有持续(persistence),没有安全或用户管理。只是象其它那些产品。
但这些只是Zope冰山之一角。实现对象的持续现在相当容易,你不需要额外的数据库来存放数据。当在不同的文件夹中加入产品的同一实例时,这一点特别好。它们会自动保存实例的值,并且使得重用更容易。
Zope可能不十分象.asp或.php一样容易,但我想说它也是“陡峭的学习曲线”(译注:好象是说越往后往简单)。Zope在创建web应用程序时有大量的好处,而其它工具则没有。尽管Zope看上去复杂,这是因为它对其它工具考虑得更多的缘故。
想象一下当使用其它工具为你的某个客户建立一个讨论区的情形吧。你创建应用。到目前为止还很好。但是现在你想要为另一个客户重用它。也许是你的老板要你这么做的。这不是什么好消息,因为你的html和代码结合的很紧密。所以你几乎需要为新的客户重新编写。
突然,有某个人在讨论区中使用了“转贴”。所以你的客户想要能够删除注解。但管理页面应该被隐藏在一个口令之后。所以你创建了一个简单的口令系统,并且在一个会话(session)变量中保存用户名。不错 ... 现在每个人都高兴了。
直至老客户也想要同样的管理功能和口令保护。哦,是的,贴新文件的方式也是口令保护的,但在讨论区中允许贴新贴与删除消息是不一样的,所以某些更高级的用户管理表格需要创建。
哦,顺便提一下用户已经买下了一家在德国的公司,它有着自已的讨论区和新闻工具。
这类事情在做web应用时是很典型的。有时候你将需要Zope已经内置这些功能。但是因为Zope内置了这些功能,这样就比起“原始”工具如“mod_perl”,“.asp”和“.php”看上去难得多。
“minimal”类的两个最终文件
__init__.py:
import minimal
def initialize(context):
"""Initialize the minimal product.
This makes the object apear in the product list"""
context.registerClass(
minimal.minimal,
constructors = (
minimal.manage_addMinimal, # This is called when
# someone adds the product
)
)
minimal.py:
from OFS import SimpleItem
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal'
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
def manage_addMinimal(self, RESPONSE):
"Add a Minimal to a folder."
self._setObject('minimal_id', minimal('minimal_id'))
RESPONSE.redirect('index_html')
就是这些了。我现在已经编写了一个最小类,它可以被用来开发其它的类。
minimal类的一些简单增强
改善minimal类的最明显的方法是当增加一个实例时能够修改id。为了做到这一点,我需要增加一个表格到minimal,这样当用户放开选择框时,他们就可以输入一个id。
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal'
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
def manage_addMinimal(self, id, RESPONSE=None):
"Add a Minimal to a folder."
self._setObject(id, minimal(id))
RESPONSE.redirect('index_html')
def manage_addMinimalForm(self):
"The form used to get the instance' id from the user."
return """<html>
<body>
Please type the id of the minimal instance:
<form name="form" action="manage_addMinimal">
<input type="text" name="id">
<input type="submit" value="add">
</form>
</body>
</html>"""
由“manage_addMinimalForm()”返回的html表格在提交后会调用“manage_addMinimal()”。“id”被作为一个参数传递。
但我也需要修改“manage_addMinimal()”,因为它需要使用id。所以将使用固定的“minimal_id”改为现在使用用户在文本框中输入的内容作为一个id。现在在文件夹中可以有任意数量的实例了,只要它们有着不同的id。
我也修改了__init__.py,因为当添加一个实例时,Zope现在需要调用“manage_addMinimalForm()”,而不是“manage_addMinimal()”。
import minimal
def initialize(context):
"""Initialize the minimal product.
This makes the object apear in the product list"""
context.registerClass(
minimal.minimal,
constructors = (
minimal.manage_addMinimalForm, # The first method is
# called when someone
# adds the product
minimal.manage_addMinimal
)
)
最后最后要说的
仍然还有一个小问题。当向一个文件夹中添加产品的一个实例时,我可以看到对象id,如果我在上面点击,我进入到产品的管理屏幕。唯一的问题是我还没有定义这样一个屏幕。所以当进入到minimal_id/manage_workspace时,Zope给我一个错误。它对于实例的工作没有什么影响。只是看上去有些粗糙。通过向类中增加一个“manage_options”的元组可以避免:
manage_options = (
{'label': 'View', 'action': 'index_html'},
)
在管理视图中将会有一个单个的tab页,叫做“view”,并且作为缺省的它将进入index_html。所以当某人点击在文件夹中的实例的链接时,他们将看到“index_html”。这样就没有不确切的尾巴了。
那么最终的类看上去象这样:
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal'
manage_options = (
{'label': 'View', 'action': 'index_html'},
)
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
def manage_addMinimal(self, id, RESPONSE=None):
"Add a Minimal to a folder."
self._setObject(id, minimal(id))
RESPONSE.redirect('index_html')
def manage_addMinimalForm(self):
"The form used to get the instance' id from the user."
return """<html>
<body>
Please type the id of the minimal instance:
<form name="form" action="manage_addMinimal">
<input type="text" name="id">
<input type="submit" value="add">
</form>
</body>
</html>"""
向产品增加更多的页或方法
我将加入一些额外的东西演示如何向类中加入更多的方法。在这里我已经加入了三个方法“counter”,“squareForm”和“square”。试着理解它们是如何工作的。比起mod_perl,.asp或.php没有太多的区别。
from OFS import SimpleItem
class minimal(SimpleItem.SimpleItem):
"minimal object"
meta_type = 'minimal'
manage_options = (
{'label': 'View', 'action': 'index_html'},
)
def __init__(self, id):
"initialise a new instance of Minimal"
self.id = id
def index_html(self):
"used to view content of the object"
return '<html><body>Hello World</body></html>'
def counter(self):
"Shows the numbers from 1 to 10"
result = '<html><body>Counts from from 0 to 10\n'
for i in range(10):
result = result + str(i) + '\n'
result = result + '</body></html>\n'
return result
def squareForm(self):
"User input form for the suare method"
return """<html>
<body>
Please type the number you want squared:
<form name="form" action="square">
<input type="text" name="value:int">
<input type="submit" value="Square">
</form>
</body>
</html>"""
def square(self, value=0):
"Returns the input value squared"
return """<html><body><b>The result of %s squared is:</b>
%s</body></html>""" % (str(value), str(value*value))
# Administrative pages
def manage_addMinimal(self, id, RESPONSE=None):
"Add a Minimal to a folder."
self._setObject(id, minimal(id))
RESPONSE.redirect('index_html')
def manage_addMinimalForm(self):
"The form used to get the instance' id from the user."
return """<html>
<body>
Please type the id of the minimal instance:
<form name="form" action="manage_addMinimal">
<input type="text" name="id">
<input type="submit" value="add">
</form>
</body>
</html>"""
最后
我希望这个How-To有些帮助。当然在这里我所做的不是Zope通常使用的方法。但是我是想把这个How-To做为一个基本的关于产品创建的介绍,它可以用来解释zope的其它部分,通过从这个例子进行扩展。
--------------------------------------------------------------------------------
[注1] 这里对Mixin的解释好象不对,似乎应该是在运行时对类的基类进行重定义,从而使新生成的类实例具有新的属性和方法。关于这一点,本人有一篇文章,可以看一下。《Mix-in技术介绍》
原文可以去我的主页看,这里的格式不好。