Zope 3 快速参考 本文由 Arthur Ccube 翻译,未完。 原文来自http://www.benjiyork.com/quick_start.txt 本文由 Osmond Liang 整理为 Docbook 格式的简体中文版。
简介 本快速参考手册假设读者已经熟悉Python, Subversion, 以及一般的网站知识 (如:HTML, servers, 等)。
下载 Zope 3 本快速参考手册使用Zope pre-3.2 版本。新的版本应该没问题。 留意你需要 Python 2.4 及 Subversion 以取得 Zope 3 主干版本。 要得到 Zope 的新版本, 你可以使用如下的指令 (如在窗口环境中, 用 "\" 符号代替文件名中的 "/" 符号): mkdir zope3_quick_start cd zope3_quick_start svn checkout svn://svn.zope.org/repos/main/Zope3/trunk zope3
安装
Linux 执行以下命令 (注意尾随点号是命令的一部份): cd zope3 python setup.py build_ext -i install_data --install-dir . cd ..
Windows 如果你已安装适当的编译器, 你可以使用以上的与 Linux 环境相同的命令。 否则, 执行如下命令(注意尾随点号是命令的一部份): cd zope3 python setup.py install_data --install-dir . cd .. 然后下载 Tim Peter 的窗口 binaries 给 Zope 3 C 编码code 及 跟随网页指示安装 (确保你已得到 Python 2.4 版本): http://www.zope.org/Members/tim_one/
创建事件 Zope 3 使用事件 (instances) 来记录服务器(或 一组服务器)的相关信息。 我们现在创建一个事件来应用: python zope3/bin/mkzopeinstance 你将会透过这工具软件完成以下对答: Please choose a directory in which you'd like to install Zope 'instance home' files such as database files, configuration files, etc. Directory: instance Please choose a username for the initial administrator account. This is required to allow Zope's management interface to be used. Username: admin Please provide a password for the initial administrator account. Password: Verify password: 当你建成事件后, 修改后转换至事件活页夹以及执行: bin/runzope 你应该看到和以下相似的输出: ------ 2005-09-28T20:40:11 INFO PublisherHTTPServer zope.server.http (HTTP) started. Hostname: my-computer Port: 8080 ------ 2005-09-28T20:40:11 INFO PublisherFTPServer zope.server.ftp started. Hostname: my-computer Port: 8021 ------ 2005-09-28T20:40:11 INFO root Startup time: 5.538 sec real, 3.120 sec CPU
The ZMI 如果你打开浏览器并连结至http://localhost:8080 你将看到 ZMI (Zope 管理接口). 继续并按 "Login" 右上角的连结. 输入你在建立事件时设定的用户名称 以及密码. 现在在按下右方 "Navigation" 之下的 [top] . 试试新增一些内容对象( Zope 3 事件名字在 ZMI 中是可见的). 留意新增"活页夹"(一个可以装起其它内容对象的特别的内容对象) 后, 内容对象怎样列成其结构. ZMI 没有什么花巧, 这只是一个预设的 Zope 3 面柀(skin). 你可以修改你喜欢的, 或整个代替. 当你浏览 ZMI 完毕后, 返回窗口环境, 在那里键入 "runzope" 及留意你每个浏览器的 request 也会在发生后显示. 按下 Control-C 停止 Zope.
Hello World 编写一个简单的 "Hello World" 程序。
我们的第一个内容对象 我们需要打开在 lib/python 目录的事件及建立一个叫 "hello" 的目录及在目录中建立 "hello.py". 在这文件内, 键入这些文字: import persistent class HelloWorld(persistent.Persistent): greeting = 'Hello' subject = 'world' 这个不同于一个 "普通"的 Python 类(class), 这个从 persistent.Persistent 衍生而成. 那 "Persistent" 基本类别将留意任何在事件发生的改变 以为确保它们会抄写至 object database (ZODB). 你应要知晓几个关于 persistence 的东西, 但我们将迟些再参考. 我们还需要一个空白的文件名字为 __init__.py, 将它放在 "hello" 目录,以便 Python 把它认作包装 (package)。
登记内容类别(Content Type) 我们需要让 Zope 3 知道我们希望在 ZMI 的"新增 餐单" 看见新的内容类别. 我们需要在 "hello" 目录新增一个 "configure.zcml" 檔 及在那里放一个 "browser:addMenuItem" 指令. ZCML (Zope Configuration Markup Language) 是一个 XML 语言用来设定 Zope 组件. 任何指定的ZCML 可以用给合适的 Python 码替代, 但 ZCML 通常比较简洁, 易作成, 及可维护. 第一个在档案出现的是 "configure" 标签(tag). 所有 ZCML 檔用 "configure" 标签开始, 其中包含其它标签. Zope 使用 XML 名称集 (namespaces) (那个 "xmlns" 属性) 以分辨每个标签的内容. 最基本的标签是在 http://namespaces.zope.org/zope namespace. 其它我们将在这里用的名称集是浏览器主导 (browser-specific) 指令. 那 "configure" 标签还包含那 (随意的, 但极度建议) "i18n_domain" 指令用来形容国际化区域其 应用在这 ZCML 档案中可阅读(human-readable) 的文字 (像下面的 "title" ). 因此我们的 "configure.zcml" 档会像这样: <configure xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" i18n_domain="hello" > <browser:addMenuItem class=".hello.HelloWorld" title="QS Hello World" permission="zope.ManageContent" /> </configure> "class" 属性指明给类使用的模块路径, 一个起始点号 代表导入相对于这 ZCML 档的包装. 因此这里 Zope 将导入 hello 模块, 然后由那模块导入 "HelloWorld". "title" 属性提供新增餐单的显示标题 (我们用"QS" 前缀那标题 因为 Zope 连载另一个 "hello world" 对象, 所以这确保使用正确的 那一个). "permission" 属性用来形容一个用者新增一个对象的所需权限. 那 "zope.ManageContent" 权限代表用者可以新增、移除及修改内容 (那个你在新增事件是的 "admin" 用者就是一个这样的用者). 我们需要告诉 Zope 阅读我们的 ZCML 檔, 以及最方便的方法是 放一个 "插条" (slug) 至 instance/etc/package-includes 目录. "插条" 是一个 ZCML 档案, 其中只包括另一档案. 这里就是我们的插条应该有的 样貌 (另存为 hello-configure.zcml): <include package="hello" /> 现在我们开始 Zope 备份, 我们可以移至 ZMI 然后新增我们的内容类别, 按下 "QS Hello World" 并输入给对象的名称; 取名为 "hello". 如果我们在新增后按下我们的对象, 我们只能看见一个通用的 检视, 告诉我们对象的类 ("hello.hello.HelloWorld").
A New View We want to provide a more interesting view. Our view will use the greeting and subject from the content object and construct a message from it. A "view" is simply a way of rendering a particular content object. We'll create a simple view that renders our content object to HTML. Views are informed about their content object by being assigned a "context" attribute. In other words, if you're writing code for your view class you can access the content object you're providing a view of as "self.context". Here's a view class to add to "hello.py": class MessageView(object): def message(self): return '%s %s!' % (self.context.greeting, self.context.subject) def __call__(self): return '<html><body><big>%s</big></body></html>' % self.message() So, why do we have views? There are a few of reasons:
views are a way to separate the data from the presentation. Content objects represent the problem domain objects in our application; the views represent implementation objects. you might want more than one view of a single content object (HTTP, FTP, JSON, XML, etc.) you might also want to apply the same view to different content objects (like the built-in "Introspector")
Configuring the View We have to configure the view so it is available for our content object. This is done in ZCML so we (or people using our code) can add, remove, or substitute views later without changing the code. Add the following to the end of hello/configure.zcml (but before "</configure>").: <browser:page for=".hello.HelloWorld" name="index.html" class=".hello.MessageView" permission="zope.Public" /> Now restart zope (Control-C, bin/runzope) and visit the view in your browser at this URL: http://localhost:8080/hello. Uh oh! What does "A system error occurred." mean? Whenever an exception is uncaught that error message will be generated. You can go to the console window you have Zope running in and look at the line that starts "ForbiddenAttribute". The rest of the line should look like this: ('greeting', <hello.hello.HelloWorld object at 0xb687182c>) That means that we tried to access an attribute (named "greeting") that we don't have permission to see. That's because Zope 3 comes with an extensive security system which assumes that any attribute is not accessible unless declared otherwise.
Security So, to say that anyone can see the "greeting" (and just so we don't get another ForbiddenAttribute exception, "subject" too) we'll add this to hello/configure.zcml: <content class=".hello.HelloWorld"> <require permission="zope.Public" attributes="greeting subject" /> </content> In English, this says: Require the zope.Public permission for a user to be able to read the "greeting" and "subject" attributes of HelloWorld objects. Any user (even anonymous ones) have the zope.Public permission, so because our view only uses those two attributes, anyone should be able to see it. Restart Zope and go back to http://localhost:8080/hello and you will see the results of the view.
Sidebar: So why doesn't Zope 3 just assume every attribute is public until told otherwise? History has shown that writing secure software can be difficult. Therefore it is best to make everything secure by default and make conscious decisions to open up security when necessary. The alternative of making everything public by default and having to "add in" security is not practical.
Zope Page Templates You'll note that our view doesn't "cooperate" with the ZMI, the HTML we generated was returned untouched to the browser. Sometimes that is exactly what you want, but sometimes you want to include standard headings, menus, etc. like the ZMI does. The way you do that in Zope 3 is by using page templates. The main ZMI page template provides a way for us to generate content for it to display using ZPT macros. Create a file called "hello.pt" in our "hello" package. Make the file look like this: <html metal:use-macro="context/@@standard_macros/view"> <body> <div metal:fill-slot="body"> <big tal:content="python: view.message()"></big> </div> </body> </html> Note how the template calls the view's "message" method to get the contents of the "big" tag and then fills the ZMI's "body" slot with the "big" tag (or anything else we put in the slot). Now we need to wire up the template so it will be used. Add this import at the top of "hello.py": from zope.app import pagetemplate Add the template to the MessageView class and change its "__call__" to use it: template = pagetemplate.ViewPageTemplateFile('hello.pt') def __call__(self): return self.template()
ZCML and Templates Because associating templates with views is so common and to make it easier to substitute one template with another without having to change the view code, there is a ZCML shortcut for doing it. If we remove the three lines we added above so our view class just looks like this (and remove the "from zope.zpp import pagetemplate" line): class MessageView(object): def message(self): return '%s %s!' % (self.context.greeting, self.context.subject) and add the line 'template="hello.pt"' to the "browser:page" tag in hello's "configure.zcml" so that it looks like this: <browser:page for=".hello.HelloWorld" name="index.html" class=".hello.MessageView" permission="zope.Public" template="hello.pt" /> Now, if you restart the server you can refresh http://localhost:8080/hello and see that we get the same results.
An Edit View Now we want to be able to edit the subject and greeting attributes of our objects through the web. We could go through the effort of creating HTML "form" tags and processing the responses, making sure to validate the user input, but Zope makes it easier.
Interfaces First, we need to know what data the form will collect. Zope uses "interfaces" with "schema fields" to do that. Add this simple interface to your "hello.py" file: class IHelloWorld(interface.Interface): greeting = schema.TextLine() subject = schema.TextLine() And don't forget to import "interface" and "schema" at the top of "hello.py": from zope import interface, schema Now that we have an interface describing our HelloWorld class, we can annotate the class with the interface it implements. Add this line after "class HelloWorld(object):": interface.implements(interfaces.IHelloWorld) That line lets other parts of the system know what interface(s) our instances promise to provide.
Forms Now we can add an edit view to "hello.py". Instead of building an HTML form "by hand", we'll use a standard zope package called "formlib". We have to import formlib at the top of "hello.py" like so: from zope.formlib import form And then add our edit view to the end of the file.: class EditView(form.EditForm): form_fields = form.Fields(interfaces.IHelloWorld) We want the form to render inside the ZMI, so we'll create a template named "edit.pt" with these contents: <html metal:use-macro="context/@@standard_macros/view"> <body> <div metal:fill-slot="body" tal:content="view"> </div> </body> </html> Now all we have to do is to tell Zope about this new view, and its template. So we add this to the "hello" package's "configure.zcml" file (this is the same data we provided for our first view).: <browser:page for=".hello.HelloWorld" name="edit.html" class=".hello.EditView" permission="zope.ManageContent" template="edit.pt" /> Now, if we restart Zope and go to http://localhost:8080/hello/edit.html we'll see an edit form.
Schemas When we made the interface IHelloWorld we set the attributes to "schema.TextLine". That is a way of describing what that attribute holds. There are other schema fields, and they can also get more descriptive. Let's flesh out our IHelloWorld schema a bit. First we'll add titles and descriptions: class IHelloWorld(interface.Interface): greeting = schema.TextLine( title=u'Greeting', description=u'The salutation used to greet the subject.') subject = schema.TextLine( title=u'Subject', description=u'Who or what is being greeted.') If we restart Zope and refresh the edit form we'll see the results of our changes: now the text fields have titles, and if we click (and hold) the mouse button on the field title, we'll see the description. There are several other settings for fields including: required/optional, constraints (max/min, match regular expression, etc.), complex data types (sequences, mappings, etc.).
More Security Change the value of one of the fields and press "Apply". Another exception! Click the back button and follow the "Errors" link. See the "ForbiddenAttribute" error? What happened was this: you tried to modify an attribute that you don't have permission to modify. Remember the ZCML for the "greeting" and "subject" attributes we added before (in "configure.zcml"): <content class=".hello.HelloWorld"> <require permission="zope.Public" attributes="greeting subject" /> </content> That only let us read those attributes, not write them. Now we'll let anyone with "zope.ManageContent" permission (like the "admin" user) change the attributes. Instead of naming the attributes one-by-one like we did before, we can leverage the fact that the interface already knows about them, so we'll make the "content" element look like this: <content class=".hello.HelloWorld"> <require permission="zope.Public" interface=".interfaces.IHelloWorld" /> <require permission="zope.ManageContent" set_schema=".interfaces.IHelloWorld" /> </content> We replaced the "attributes" attribute with "interface", and added a "require" directive indicating that only users with "zope.ManageContent" (like "admin") can actually change the values. Now you can restart Zope and change the values.
Adding Menu Entries Now click on "[top]" and then click on "hello" and you'll see that our views aren't displayed; Zope doesn't assume that we want them to be listed in the ZMI view menu (along with "Introspector") just because they exist. In this case we do want them displayed, so we'll register them as part of the "zmi_views" menu. We could register them using only Python, but it's easier to do with ZCML. The ZMI view menu works by associating an interface with the views that should be listed for it, therefore instead of the "for" attribute pointing directly to our content class, we need to use the IHelloWorld interface instead. This has the added benefit that any content object that implements the IHelloWorld interface will also get our "Message" and "Edit" views! This also means that it would be easy for a third party to add views to our content objects. One way to add a view to the ZMI's view menu is by adding "menu" and "title" attributes to the "browser:page" directive. The two "browser:page" elements should look like this now: <browser:page for=".interfaces.IHelloWorld" name="index.html" class=".hello.MessageView" permission="zope.Public" template="hello.pt" menu="zmi_views" title="Message" /> <browser:page for=".interfaces.IHelloWorld" name="edit.html" class=".hello.EditView" permission="zope.ManageContent" template="edit.pt" menu="zmi_views" title="Edit" /> Restart zope and go to http://localhost:8080/ and click on our "hello" object. You'll see that both of our views are listed and can be clicked on. The views are listed in order of "specificity" and then order of definition in the ZCML. Our views are more specific because they are registered for only IHelloWorld, where as the Introspector view is registered for the most basic interface "Interface".
More Persistence So far we haven't worried much about persistence, everything has just magically worked. That's because we've been using attribute assignment to mutate our instances, so it has been easy for Zope (actually ZODB) to know when our objects change, but for more complex object models that isn't always the case. Fortunately we only have to do a couple of simple things and Zope takes care of the rest. If you want a persistent list or dict, use persistent.list.PersistentList and persistent.dict.PersistentDict, (respectively) instead of the built-in objects. If you want to use a built-in or other instance for which there are no persistent versions available (and you can't make your own), all you have to do is make sure the persistent object to which it is attached knows that its value has changed. There are two ways to do that. First, after mutating the object re-assign it to it's attribute: def doSomething(self): self.some_mutable.mutate() self.some_mutable = self.some_mutable Alternatively you can directly set the _p_changed attribute.: def doSomething(self): self.some_mutable.mutate() self._p_changed = True Note that you only have to think about persistence for objects that are actually persistent, not for things like views. In practice even very large Zope 3 applications won't have to do much of this.
Version This is version 0.4 of the Zope 3 Quick Start Guide.
License This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 License. Attribution in derivative works should be given as "Benji York (http://benjiyork.com)".