Personal tools
You are here: Home Zope Tutorials 30分钟学会zope3

30分钟学会zope3

Document Actions

Note: This is the print view with all the tutorial pages on one page. The paginated version is available here, if you prefer that.

本文由firehare等翻译。 http://zissue.berlios.de/z3/Zope3In30Minutes.html

准备工作

背景知识,安装zope3
Author: Baiju M <baiju.m.mail AT gmail.com>
Version: 0.1.91
Copyright: (C) 2005 Baiju M, Placed under GNU GPL version 2, or (at your option) any later version
Source:http://svn.berlios.de/viewcvs/zissue/trunk/z3in30m/Zope3In30Minutes.txt?view=markup

特别注意 :我假设你正在使用Debian3.1(Sarge)或相似系统

为什么要30分钟

很抱歉!Zope3是不可能在10分钟内介绍清楚的

为什么我要学?

如果你想为基于Web的应用开发作一个Pythonic的框架,那么请继续。我并不打算对什么是Pythonic作任何新的解释Smile,主要试图回答“为什么”的问题。

关键词

Python, Zope, Interface, Component, ZCML, ZMI, Adapter, View, Event, Service, Utility, Principal.

那么让我们开始...

Zope3是第三代的Zope,一个Web应用框架。你可以从http://www.zope.org/Products/zope3下载Zope3.1,解压Zope3源包,并以根用户权限运行:

# cd Zope-3.1.0
# ./configure;make;make install

(译者)如果是在Ubuntu下,请按下面命令运行:

$tar -zxvf Zope-3.1.0.tgz -C /tmp
(如果是Breezy下,请安装Python2.3 Python2.3-dev等相关包,
 因为Zope3是基于Python2.4.1和Python2.3.5开发的,而Breezy默认是Python2.4.2,
 所以必须安装Python2.3的包文件了)
$./configure --prefix /opt/zope
$make
$make check
$make install

安装完成后,你必须建立一个Zope Instance(别担心!只需照作就行)。命令如下:

$ cd /usr/local/Zope-3.1.0/bin
$ ./mkzopeinstance --dir=$HOME/myzope --user=admin:secret123

要启动你的Zope3服务,需要到你的Instance目录下运行:

$ cd ~/myzope
$ ./bin/runzope

如果你得到一个端口错误,请检查8080和8021端口是否被其他程序占用;暂时停止运行该程序。打开你的浏览器,然后输入http: //localhost:8080。你将看到Zope Management Interface(ZMI)。ZMI是你的Python提示符,嗯...不!应该是Zope的提示符,看到了吗?你可以登录进去并且到处点击看看。如果你在ZMI玩够了的话,在终端用Ctrl+C中断它。

你的第一个Zope3应用

开始编写第一个zope3应用

你的第一个Zope3应用

是的!我们打算开始创建一个Zope3应用,一个在线书签。我们的应用将显示到网站的链接和每条链接的描述。

那么,在开始一个Zope3项目前,你得做何准备呢?哦!很抱歉!我没法一言以蔽之,你最好先学习并实践XP(极限编程,一种轻量级的软件开发过程) http://en.wikipedia.org/wiki/Extreme_programming 。总之,在你初步设计之后,就将编写接口。让我们期待Python 3.0 会让它变得容易些吧!然后开始编写单元测试,至此你的想法已非常具体!最后编写正式代码。当你一个接一个的实现接口并通过单元测试,你会拥有(前所未有的)满足感! 我已经在这里给出了BookMarker应用的源代码:boom.tar.bz2

我们的代码将放在$HOME/myzope/lib/python/boom目录中

首先创建一个interfaces.py的文件,我们将在该文件内保存所有接口。后面我们将在单元测试的强力支持下,一个接一个的实现这些接口。

接口

下面就是我们的interfaces.py:

from zope.interface import Interface
from zope.schema import Text, TextLine, Field

from zope.app.container.constraints import ContainerTypesConstraint
from zope.app.container.constraints import ItemTypePrecondition
from zope.app.container.interfaces import IContained, IContainer

class IMark(Interface):
    """这是书签对象."""

    url = TextLine(
        title=u"URL/Link",
        description=u"URL of the website",
        default=u"http://www.zope.org",
        required=True)

    description = Text(
        title=u"Description",
        description=u"Description of the website",
        default=u"",
        required=False)

class IBookMarker(IContainer):
    """这是所有书签对象的容器."""

    name = TextLine(
        title=u"Name of BookMarker",
        description=u"A name for BookMarker",
        default=u"",
        required=True)

    def __setitem__(name, obj):
        pass

    __setitem__.precondition = ItemTypePrecondition(IMark)


class IMarkContained(IContained):
    """一条书签只能包含在一个BookMarker容器中"""

    __parent__ = Field(
        constraint = ContainerTypesConstraint(IBookMarker))

我们的第一个接口IMark有两个属性,一个是站点的URL,另一个是它的描述。请注意,IMark不是一个类,尽管我们使用了Python的类定义。IMark继承自Interface,因此是个接口。第二个是一个容器接口,它是一个扩展的Icontainer接口。我们可以使用这个容器接口来保存我们的数据(IMark实现的实例)。我们将IMark的所有对象放到IBookMarker的一个容器对象里。我们连同IMarkContained(作为一个约束接口)一起来实现IMark。所以IMark对象只能包含在IBookMarker对象中。

单元测试

现在创建tests.py文件,输入下列语句:

import unittest
from zope.testing.doctestunit import DocTestSuite

from zope.app.container.tests.test_icontainer import TestSampleContainer

from boom.bookmarker import BookMarker, Mark

class BookMarkerContainerTest(TestSampleContainer):

    def makeBookMarkerObject(self):
        return BookMarker()

def test_suite():
    return unittest.TestSuite((
        DocTestSuite('boom.bookmarker'),
        unittest.makeSuite(BookMarkerContainerTest),
        ))

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')

实际上我们在这里并没有编写任何的单元测试,不过它会自动对我们的文档进行测试

然后运行单元测试:

$ cd $HOME/myzope/lib
$ ../bin/test -vpu --dir boom

正式编码

现在让我们继续去实现(bookmarker.py):

__docformat__ = 'restructuredtext'

from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained

from boom.interfaces import IMark, IMarkContained, IBookMarker

class Mark(Contained):
    """IMark的实现

    确认`Mark` 实现了`IMark` 接口::

      >>> from zope.interface.verify import verifyClass
      >>> verifyClass(IMark, Mark)
      True

    确认`Mark` 实现了`IMarkContained`接口:

      >>> from zope.interface.verify import verifyClass
      >>> verifyClass(IMarkContained, Mark)
      True

    一个检查Mark的链接地址的示例::

      >>> mk = Mark()
      >>> mk.url
      u'http://www.zope.org'
      >>> mk.url = u'http://www.python.org'
      >>> mk.url
      u'http://www.python.org'

    检查Mark描述信息的示例::

      >>> mk = Mark()
      >>> mk.description
      u''
      >>> mk.description = u'Zope Project Web Site'
      >>> mk.description
      u'Zope Project Web Site'
    """

    implements(IMark, IMarkContained)

    url = u"http://www.zope.org"
    description = u""

class BookMarker(BTreeContainer):
    """使用B-Tree容器来实现IBookMarker接口

    确认`BookMarker` 实现了`IBookMarker` 接口::

      >>> from zope.interface.verify import verifyClass
      >>> verifyClass(IBookMarker, BookMarker)
      True

    改变书签名字的示例::

      >>> bm = BookMarker()
      >>> bm.name
      u''
      >>> bm.name = u'MyBookMarker'
      >>> bm.name
      u'MyBookMarker'
    """

    implements(IBookMarker)

    name = u""

我们已经在实现的同时也编写了Doctests,该Doctests是同例子一起的,所以这种实现方式我们称之为事例驱动单元测试

配置

现在编写配置(保存在configure.zcml文件中):

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

  <interface
      interface=".interfaces.IBookMarker"
      type="zope.app.content.interfaces.IContentType"
      />

  <content class=".bookmarker.BookMarker">
    <implements
        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
        />
    <implements
        interface="zope.app.container.interfaces.IContentContainer" 
        />
    <factory
        id="boom.bookmarker.BookMarker"
        description="Book Marker" 
        />
    <require
        permission="zope.ManageContent"
        interface=".interfaces.IBookMarker"
        />
    <require
        permission="zope.ManageContent"
        set_schema=".interfaces.IBookMarker"
        />
  </content>

  <interface
      interface=".interfaces.IMark"
      type="zope.app.content.interfaces.IContentType"
      />

  <content class=".bookmarker.Mark">
    <implements
        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
        />
    <factory
        id="boom.bookmarker.Mark"
        description="A book mark." 
        />
    <require
        permission="zope.ManageContent"
        interface=".interfaces.IMark"/>
    <require
        permission="zope.ManageContent"
        set_schema=".interfaces.IMark"
        />
  </content>

  <browser:addform
      label="Add Book Marker"
      name="AddBookMarker.html"
      schema="boom.interfaces.IBookMarker"
      content_factory="boom.bookmarker.BookMarker"
      fields="name"
      permission="zope.ManageContent"
      />

  <browser:addMenuItem
      class=".bookmarker.BookMarker"
      title="Book Marker"
      permission="zope.ManageContent"
      view="AddBookMarker.html"
      />

  <browser:editform
      schema="boom.interfaces.IBookMarker"
      for="boom.interfaces.IBookMarker"
      label="Change Book Marker"
      name="edit.html"
      permission="zope.ManageContent"
      menu="zmi_views" title="Edit" 
      />

<browser:containerViews
    for="boom.interfaces.IBookMarker"
    index="zope.View"
    contents="zope.View"
    add="zope.ManageContent"
    />

<browser:addform
    label="Add Mark"
    name="AddMark.html"
    schema="boom.interfaces.IMark"
    content_factory="boom.bookmarker.Mark"
    fields="url description"
    permission="zope.ManageContent"
    />

<browser:addMenuItem
    class="boom.bookmarker.Mark"
    title="Mark"
    description="URL of Website"
    permission="zope.ManageContent"
    view="AddMark.html"
    />

<browser:editform
    schema="boom.interfaces.IMark"
    for="boom.interfaces.IMark"
    label="Change Mark"
    fields="url description"
    name="edit.html"
    permission="zope.ManageContent"
    menu="zmi_views" title="Edit" 
    />

<browser:page
    name="marks.html"
    for="boom.interfaces.IBookMarker"
    class=".browser.BookMarks"
    template="marks.pt"
    permission="zope.Public"
    menu="zmi_views"
    title="Marks"
    />

</configure>

它能不言自明(即能够自我解释,XML之类的标记语言都有这个特点)吗?“不...!”好吧,我们将稍后简单的谈谈Zope Configuration Markup Language(ZCML,Zope配置标记语言)。实际上,如果你对ZCML熟悉的话,你会发现这个配置不止是不言自明,它将带给你的是对整个应用的总体思路。现在你可能在想,这并不Pythonic嘛 Sad ,嗨!再想想!

开始运行吧

最后一步就是要让我们的应用运行起来,在文件$HOME/myzope/etc/package-icludes/boom-configure.zcml中插入下列行:

<include package="boom"/>

现在你已经注册了你的包。

重新启动Zope,然后打开你的浏览器,添加一个BookMarker和一些书签。

现在,你不想让你的书签排列得更好点吗?先暂时休息一下,之后我们将为书签创建一个视图。

增加用户界面

添加视图、页面,增加功能测试代码

视图

现在新建一个名为browser.py的文件,并输入下列代码:

from boom.interfaces import IMark

class BookMarks:

    def __init__(self, context, request, base_url=''):
        self.context = context
        self.request = request
        self.base_url = base_url

    def listMarks(self):
        marks = []
        for name, child in self.context.items():
            if IMark.providedBy(child):
                info = {}
                info['url'] = child.url
                info['description'] = child.description
                marks.append(info)
        return marks

然后创建一个模板(marks.pt):

<html metal:use-macro="views/standard_macros/view">
  <body>
    <div metal:fill-slot="body">

      <div class="row">
        <div class="label">Book Marks:</div>
        <br/><br/>
          <li tal:repeat="item view/listMarks">

            <a href="" tal:attributes="href item/url">
               <span tal:content="item/url">Link</span>
            </a>
            <pre tal:content="item/description">Description</pre>
            <br/>

          </li>
      </div>

    </div>
  </body>
</html>

现在点击一下“Marks”标签,你就可以看到所有的书签了。

好的!这样可不能算是结束,这仅仅是你学习的开始。

功能测试

让我们为视图写一个功能测试来结束我们的例子(ftests.py):

import unittest
from zope.app.testing.functional import BrowserTestCase

class BookMarksTest(BrowserTestCase):

    def testMarksListing(self):
        pass


def test_suite():
    return unittest.TestSuite((
        unittest.makeSuite(BookMarksTest),
        ))

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')

然后运行该功能测试:

$ cd $HOME/myzope/lib
$ ../bin/test -vpf --dir boom

现在学什么?

现在你可以通过 Zope3 Book 开始详细学习Zope3了。也可以加入到zope3-user邮件列表里。

这里有Benji York写的一个不错的Zope3快速入门指南: http://www.benjiyork.com/quick_start/ 还有一本已出版的介绍性图书,很不错,请访问: http://worldcookery.com/

还有一点:我想不断完善这份文档,因此有何意见/建议千万别犹豫,请发送到: baiju.m.mail@gmail.com

深入的解释

介绍深入的概念

Hey! What's time now?

Oh! it's only 10 minutes since we started reading this article. We got 20 more minutes to explain what's happened here. So let's look back again.

Installation and Configuration

You can download Zope 3.1.0 or later for your major works. Install Zope3 as root, though it is not necessary. When you experiment with Zope3, make instance as a normal user. For production systems, you get the additional advantage of OS level security. Since we manually installed Zope3 (as root), the default installation path will be /usr/local/Zope-3.1.x . When making zope instance, you have to specify zope manager's user name and password. You can make more instances, if required.

In your Zope 3 instance directory, there are few directories with specific purposes. Here ownwards I will be using $ZOPE3INSTANCE to refer your instance directory

$ZOPE3INSTANCE/etc
In 'etc' you can see some configuration files. In $ZOPE3INSTANCE/etc/zope.conf you can edit port numbers of http and ftp servers.
$ZOPE3INSTANCE/var
Zope saving all data here. Backup this directory regulary. If 'Data.fs' is missing, it will be automatically created when you run zope again.
$ZOPE3INSTANCE/bin

Few utility scripts. 'runzope' and 'test' are already familiar to you. 'pyskel' script will be very useful for creating template codes from interfaces, Example:

$ $ZOPE3INSTANCE/bin/pyskel  boom.interfaces.IMark > file.py
$ZOPE3INSTANCE/lib/python
As I said earlier you can place your Zope3 packages here. By default this won't be in your Python path. You can also place your package in any standard Python path.

Why should I code like this?

When we started discussing BookMarker, the first word I coined is Extreme Programming (XP). I strongly recommend you to read about Extreme Programming. It will really influence you to write code in Zope3 way (directly and indirectly). Ok, one causion: many XPers believe that frameworks are bad. Here, I can not give an answer in one sentence, so you find out yourself. "Extreme Programming Explained" by Kent Beck will be a good starting point for your study. By the way, another classic book which you can read is "Design Patterns" by Gang of Four. In this book they say : 'Program to an interface, not to an implementation'. To simulate a formal definition of interfaces in C++, they used classes with virtual functions. Just like this, in Python we will use zope.interface.Interfce inherited metaclass for defining an interface. There is every chance for an 'interface' concept in Python language by version 3.0 . According to Extreme Programming: 'The four basic activities of development are coding, testing, listening, and designing.' Zope3 makes your software testing, a breeze. You can write unit and functional tests very easily.

Let's look into interfaces

I hope you are familiar with the concept of interfaces. Anyway, when we speak about an interface, we meant it to use with reference to an object (i.e, an instance of a class). For example if 'A' is a class which implements 'IA', and 'a' is an instance of 'A', then 'IA' is the interface of 'a', got it? But normally we only deals with the interface and its implementing class.

In our interfaces.py we defined three interfaces. The first interface 'IMark' defines a book mark object. A book mark has two attributes, one is the url and another one is the description. We used schemas available at zope.schema package to specify these attributes.

As we mentioned earlier IBookMarker is a container interface. We extended IContainer interface with one name attribute. Also we put one item type pre-condition. So IBookMarker object can only contain an IMark object. Now before moving to the next section, please note from which packages and modules we imported different classes/interfaces. Just open the sources of those packages, and see how well documented they are!. Some documentation are written in seperate ReStructuredText files with unit testing, this is the Zope3 way of unit testing.

Unit testing re-visited

We wrote our test in a file named tests.py. You can also write tests in a package named tests. All test modules under this package should have test_ prefix. We also automated our doctests written along with implementation. To test our BookMarker container, we inherited TestSampleContainer. For testing containers, you can stick with unittest module. Also use it where you want more reusablity. Anyway, we will integrate all doctests with unittest module. This will help us to run tests automatically, from a single point.

Let's talk about implementations

In the first line of bookmarker.py we set a special variable attribute __docformat__ = 'restructuredtext'. Our documentation strings are written in ReStructuredText format. I strongly reccomend you to use ReST for all kinds of Python documentations. (This article is written in ReST: svn co svn://svn.berlios.de/zissue/trunk/z3in30m).

First we imported implements function from zope.interface package. Using this function we can say a class implements one or more interfaces. BTreeContainer is full implementation of IContainer interface. We inherit it to implement our Container interfaces. Similarly Contained is an implementation of IContained. Mark class implements both IMark and IMarkContained. IMarkContained is an extended interface of IContained. So by inheriting Contained class we get a partial implementation of our interfaces. Now the remaining things to implement is two attributes, url and description. Similarly majority of IBookMarker is implemented in BTreeContainer. I hope documentation strings are self explanatory.

ZCML Explained

Let me ask one question, which you are going to face many times later. Do you think a Programming language should be used to write configuration files? If yes, why?. Why we can not use a text files with some conventions or markups.

Anyway, ZCML is the XML based configuration system for Zope3. The base tag 'configure' specifies namespaces to use. In our configuration we used two namespaces, 'zope' and 'browser'. 'zope' namespace contains the basic elements required to register our content objects. 'browser' is for view related configuration.

The first tag we used is 'interface'. It is called a directive. In our configuration 'iterface' is a simple directive and 'content' is a complex directive.

Views and ZPT

Your objects are finally saved in ZODB, you may choose other storage mechanisms also. In zope you can present your objects in different ways. I mean, through different protocols like http, ftp, xmlrpc etc. In our case, we created a view for browser (yes, through http). We put all logics for presentation in browser.py. And created a Zope Page Template (ZPT) for real presentation. Normally you should avoid any information retrieval logic in ZPT.

The things we didn't mentioned

Please go to the last section, otherwise you can not finish this article in 30 minutes :)

Ofcourse, in an introductory document we can only cover a fraction of the whole technology. Here I have omitted many things for various reasons.

In the beginning, I said just one assumptiion about you. I really meant many other things. Surely you should be a Python programmer. You should know basic administration. Basic understanding of web technologies. And ofcourse, willing to spend time :)

While installation you might get error due to Python development libraries not installed. If you know Python and system administration you will just run: apt-get install python-dev Similary you have created __init__.py file to make our package working.

If you are interested in Zope3 by now, surely you will explore more in future. While you exploring, you will see that doctests can be written in stand alone text files. Similarly you will understand lots of other things.

One thing I can assure you is that zope is really a matured framework. It has almost 9 year history of successfull running. Many technologies evolved through zope. Infact zope and zope developers has influenced Python language itself. Yes! it is one of the gratest Python application ever written.

The End

Oh! it is just the end of this article. And the great beginning of your journey through Zope3 world. So, good luck.

About the author

I am Python programmer from Kerala. I love Python. Previously I have worked for Malayalam i18n & l10n in free softwares. I have worked for Free Software Foundation of India (as a job). I was a Koha consultant for some time. Currently I am doing lots of Python, PyGTK and PostgreSQL (about one and half years). I am a prod GNU Emacs user.

$Id$


Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: