最小的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技術介紹》
原文可以去我的主頁看,這裡的格式不好。