关闭

Zope3学习笔记

标签: componentsinterfacedebianpythonimportclass
2663人阅读 评论(1) 收藏 举报

发布时间:2005年07月21日

更新时间:2005年11月17日

Abstract

Zope3是一个全新的Python Web Framework,与zope2有很大的不同,它采用了很多先进的软件工程技术,开发的软件可重用性高,是很有前途的一种Framework。本文记录我在学习Zope3中的一些知识积累。由于现时Zope3的资料很少,而且都是英文的,学习的难度较大,本文难免会有出错之处,请各位网友多多指正。谢谢!


Chapter 1. Zope3术语

Zope3采用了全新的开发模式,充分利用了现时先进的软件工程方法,通过接口和组件开发,大大提高了软件产品的灵活性和可重用性。在Zope3开发架构中,引入了大量新的术语。在这里,我们对这些术语作简短的介绍。

  • Interface(接口)

    Interface用以定义软件需实现的功能和详细的描述文档。设计好接口后,我们就可以用Zope3提供的pyskel工具自动生成实现类。语法是:

    zope@debian:~/lib/python/mytest$pyskel mytest.interfaces.IExample > example.py
    

    mytest是我的模块目录,在运行上面命令前如在该目录下没有__init__.py文件,则需创建一个空的__init__.py文件,使zope3把mytest当作一个模块导入。

  • Components(组件)

    Components是一个具有自省接口的对象,接口是一个描述组件如何工作的对象,Components实现接口描述的功能。在Zope3中的组件有以下几种:

    • Content Components(内容组件),它是组件中最简单的一种,用来存放数据和内容,如文档和图像。

    • Factory Components(工厂组件)

      就象它的名字一样,Factory Components用以创建其它组件和对象的组件,就像一个组件的生产工厂,它可以是方法或类。

    • View Components(可视组件)

      可视组件为其它组件提供一个用户接口,通常用于显示内容组件。ZPT就是一个可视组件,在该组件中尽量不要包含逻辑处理。

    • Adapter Components(适配器组件)

      适配器组件位于内容组件和可视组件之间,用以扩展内容组件的功能。例如:你的文档(内容组件)已有一个edit()方法,没有你需要的mailToSubscribers()的功能。这时,我们不用修改内容组件或可视组件,只要增加一个适配器组件,用该组件实现mailToSubscribers()功能即可。

    • Utility Components(工具组件)

      工具组件只提供一个特定的功能,它不作用于其它组件。与适配器组件比较,我们可以Python语言中的函数和方法作为比喻。工具组件就像Python中函数,是一个独立的功能对象。适配器组件就像Python中的方法,需在其它对象的基础上才能工作。

    • Service Components(服务组件)

      服务组件是由Zope3提供的一组核心功能,它已存在于Zope3中,如:user sessions, authentication, workflow, content-type management等。

    Components还分Global Components和Local Components两类,前者不保存在Zodb中,它保存在系统文件中(zope_instance/lib/python/xxx),在服务器重启时通过ZCML注册和初始化,它与位置路径无关,可在全局范围内访问。后者保存在Zodb中,与定义时的位置相关。一个站点就是一个Local Components,系统有一个默认的站点叫top。我们可通过在ZMI中点击“Manage Site”创建一个Local Components。任何一个文件夹都可通过“Manage Site”提升为一个站点。

  • Metadata(元数据)

    元数据的意思是表示数据属性的数据,如对象标题、对象创建者和对象创建日期等。我们也可把它理解成关系数据库中表的字段。Zope3使用Dublin Core来表示内容对象的元数据。

  • Subscriber

  • Container(容器)

    Container用于存放不同对象,文件夹就是一个典型的Container。Container提供了一种机制处理对象间的包含关系。在Zope3中,Container位于zope.app.container类中,它有三个基类,分别是:

    • BTreeContainer,最通用到的类,可存放大量的对象。

    • SampleContainer,很少用,它为代层的存储提供一个hook。它是BTreeContainer的父类。

    • OrderedContainer,存放排序对象,也不可存放大量对象。

  • Unittest(单元测试)

  • ZCML(Zope Configuration Markup Language)

    Zope3通过ZCML配置文件把不同的组件组合成一个应用程序,它是一个符合XML标准的文档,配置文件的文件名为configure.zcml。还有一个名为meta.zcml的配置文件用以扩展ZCML配置文件里的XML标签。

  • i18n domains

    在ZCML中可以定义产品的i18 domains,配合Zope3的I18n工具(i18nextract)和gettext工具可以实现产品的国际化。Global Components的i18n信息保存在系统文件中(zope_instance/lib/python/xxx/locals/<REGION>/LC_MESSAGES/xxx.po),Local Components产品的i18n信息保存在Zodb中,它通过ZMI中的translation domain功能创建。

Chapter 2. 开发一个新的内容对象

下面是一个公告板的示例程序,这里将一步步展示如何在Zope3中实现它。该程序作为一个Python Package保存在文件系统的,而不是存放在Zodb中,也就是说组成该应用程序的Components是Global Components。Zope3还有一种开发方式叫TTW(Throught-The-Web),它可通过ZMI直接在浏览器中开发。用TTW开发的Components属于Local Components,保存在Zodb中。

  1. 准备

    开发的Python软件包存放在zope_instance/lib/python目录下,我们首先为该程序创建一个目录叫mymessage:

    zope@debian:~/lib/python$ mkdir message
    

    为了使该目录成为一个Python软件包,还需在该目录下创建一个空的__init__.py文件。

    zope@debian:~/lib/python/mymessage$ echo "# Make it a Python package" >> __init__.py
    
  2. 设计

    该程序将有一个根对象叫MessageBoard,它可容纳用户发布的信息和其它用户的反馈信息。另一个对象组件是Message,保存每条信息的内容。

  3. 创建接口

    编码过程的第一步就是定义我们的接口,我们的接口保存在名为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                                               from zope.app.file.interfaces import IFile
    
    class IMessage(Interface):
            """A message object. It can contain its own responses."""
    
            title = TextLine(
                    title = u'Title/Subject',
                    description = u'Title and/or subject of the message.',
                    default = u'',
                    required = True)
    
            body = Text(
                    title = u'Message Body',
                    description = u'This is the actual message. Type whatever you wish.',
                    default = u'',
                    required = False)
    
    class IMessageBoard(IContainer):
            """The Message board is the base object for our package.It can only contain IMessage objects."""
    
            def __setitem__(name,object):
                    """Add a IMessage object."""
    
            __setitem__.precondition = ItemTypePrecondition(IMessage)
    
            description = Text(
                    title = u"Description",
                    description=u"A detailed description of the content of the board.",
                    default = u"",
                    required = False)
                    
    class IMessageContained(IContained):
            """Interface that specifies the type of objects that can contain messages."""
            __parent__ = Field(
                    constraint = ContainerTypesConstraint(IMessageBoard,IMessage))
    
    class IMessageContainer(IContainer):
            """We also want to make the message object a container that can contain responses(other messages) and attachments(files and images)."""
    
            def __setitem__(name,object):
                    """Add a IMessage object."""
    
            __setitem__.precondition = ItemTypePrecondition(IMessage,IFile)
    
  4. 实现内容组件

    根据接口,我们要实现些什么组件呢?我们可借助pyskel.py这个工具来自动生成。该工具位于zope_instance/bin目录下。

    zope@debian:~/bin$./pyskel mymessage.interfaces.IMessage
    from zope.interface import implements
    from mymessage.interfaces import IMessage
    
    class Message:
        __doc__ = IMessage.__doc__
    
        implements(IMessage)
    
    
        # See mymessage.interfaces.IMessage
        body = None
    
        # See mymessage.interfaces.IMessage
        title = None
    zope@debian:~/bin$./pyskel mymessage.interfaces.IMessageBoard
    from zope.interface import implements
    from mymessage.interfaces import IMessageBoard
    
    class MessageBoard:
        __doc__ = IMessageBoard.__doc__
    
        implements(IMessageBoard)
    
    
        def __setitem__(self, name, object):
            "See mymessage.interfaces.IMessageBoard"
    
        # See mymessage.interfaces.IMessageBoard
        description = None
    
        def __getitem__(self, key):
            "See zope.interface.common.mapping.IItemMapping"
    
        def get(self, key, default=None):
            "See zope.interface.common.mapping.IReadMapping"
    
        def __contains__(self, key):
            "See zope.interface.common.mapping.IReadMapping"
    
        def __getitem__(self, key):
            "See zope.interface.common.mapping.IItemMapping"
    
        def keys(self):
            "See zope.interface.common.mapping.IEnumerableMapping"
    
        def __iter__(self):
            "See zope.interface.common.mapping.IEnumerableMapping"
    
        def values(self):
            "See zope.interface.common.mapping.IEnumerableMapping"
    
        def items(self):
            "See zope.interface.common.mapping.IEnumerableMapping"
    
        def __len__(self):
            "See zope.interface.common.mapping.IEnumerableMapping"
    
        def get(self, key, default=None):
            "See zope.interface.common.mapping.IReadMapping"
    
        def __contains__(self, key):
            "See zope.interface.common.mapping.IReadMapping"
    
        def __getitem__(self, key):
            "See zope.interface.common.mapping.IItemMapping"
    
        def __setitem__(self, name, object):
            "See zope.app.container.interfaces.IWriteContainer"
    
        def __delitem__(self, name):
            "See zope.app.container.interfaces.IWriteContainer"
    zope@debian:~/bin$
    

    上面的结果已很明白的了,但我们可以通过继承BTreeContainer类来简化实现过程。继承了BTreeContainer类后,我们就不用去实现IReadMapping、IEnumerableMapping、IReadMapping、IItemMapping和IWriteContainer接口的方法了。新创建一个叫messageboard.py的文件,实现IMessageBoard接口,内容如下:

    from zope.interface import implements
    
    from zope.app.container.btree import BTreeContainer
    
    from mymessage.interfaces import IMessageBoard
    
    class MessageBoard(BTreeContainer):
        __doc__ = IMessageBoard.__doc__
        """test
        >>> from zope.interface.verify import verifyClass
        >>> verifyClass(IMessageBoard,MessageBoard)
        True
        """
        implements(IMessageBoard)
    
        # See message.interfaces.IMessageBoard
        description = u""
    

    新创建一个message.py文件,实现IMessage、IMessageContained和IMessageContainer接口,内容如下:

    from zope.interface import implements
    from mymessage.interfaces import IMessage
    
    from mymessage.interfaces import IMessageContained,IMessageContainer
    from zope.app.container.btree import BTreeContainer
    
    class Message(BTreeContainer):
        __doc__ = IMessage.__doc__
    
        implements(IMessage,IMessageContained,IMessageContainer)
    
        # See message.interfaces.IMessage
        title = u''
    
        # See message.interfaces.IMessage
        body = u''
    
  5. 注册内容组件

    到此为止,我们已开发出了我们的组件,现在我们要告诉Zope3如何组装这些组件。这里要用到Zope的配置语言(ZCML),它采用XML语法格式。配置文件名为configure.zcml。

    <configure
            xmlns="http://namespaces.zope.org/zope">
    
            <interface
                    interface = ".interfaces.IMessageBoard"
                    type = "zope.app.content.interfaces.IContentType"
                    />
            <content class=".messageboard.MessageBoard">
                    <implements
                            interface = "zope.app.annotation.interfaces.IAttributeAnnotatable"
                            />
                    <implements
                            interface = "zope.app.container.interfaces.IContentContainer"
                            />
                    <factory
                            id = "mymessage.messageboard.MessageBoard"
                            description = "Message Board"
                            />
                    <require
                            permission = "zope.ManageContent"
                            interface = ".interfaces.IMessageBoard"
                            />
                    <require
                            permission = "zope.ManageContent"
                            set_schema = ".interfaces.IMessageBoard"
                            />
            </content>
    
            <interface
                    interface = ".interfaces.IMessage"
                    type = "zope.app.content.interfaces.IContentType"
                    />
            <content class=".message.Message">
                    <implements
                            interface = "zope.app.annotation.interfaces.IAttributeAnnotatable"
                            />
                    <implements
                            interface = "zope.app.container.interfaces.IContentContainer"
                            />
                    <require
                            permission = "zope.ManageContent"
                            interface = ".interfaces.IMessage"
                            />
                    <require
                            permission = "zope.ManageContent"
                            interface = ".interfaces.IMessageContainer"
                            />
                    <require
                            permission = "zope.ManageContent"
                            set_schema = ".interfaces.IMessage"
                            />
            </content>
    
            <include package=".browser" />
    
    </configure>
    
  6. 配置基本视图

    注册组件的动作只是进行一些逻辑处理,并不会产生任何的界面。这里我们可通过ZCML语言配置一个可通过浏览器访问的可视界面。我们在mymessage目录下创建一个browser目录,用以存放可视界面相关的内容。同样,为了使该目录成为一个Python Package。我们要在该目录下创建一个空白的__init__.py文件。接着创建一个configure.zcml配置文件,内容如下:

    <configure
            xmlns="http://namespaces.zope.org/browser">    定义browser名称空间
    
            <addform                                       定义添加对象的form
                    label = "Add Message Board"            
                    name = "AddMessageBoard.html"
                    schema = "mymessage.interfaces.IMessageBoard"
                    content_factory = "mymessage.messageboard.MessageBoard"
                    fields = "description"                 
                    permission = "zope.ManageContent"      定义访问权限
                    />
            <addMenuItem                                   在对象添加列表上添加一个Message Board项
                    class = "mymessage.messageboard.MessageBoard"
                    title = "Message Board"
                    description = "A Message Board"
                    permission = "zope.ManageContent"
                    view = "AddMessageBoard.html"        与addform中的name属性值对应
                    />
            <editform
                    schema = "mymessage.interfaces.IMessageBoard"
                    for = "mymessage.interfaces.IMessageBoard"
                    label = "Change Message Board"
                    name = "edit.html"
                    permission = "zope.ManageContent"
                    menu = "zmi_views" title = "Edit"
                    />
            <containerViews
                    for = "mymessage.interfaces.IMessageBoard"
                    index = "zope.View"
                    contents = "zope.View"
                    add = "zope.ManageContent"
                    />
    
            <addform
                    label = "Add Message"
                    name = "AddMessage.html"
                    schema = "mymessage.interfaces.IMessage"
                    content_factory = "mymessage.message.Message"
                    fields = "title body"
                    permission = "zope.ManageContent"
                    />
            <addMenuItem
                    class = "mymessage.message.Message"
                    title = "Message"
                    description = "A Message"
                    permission = "zope.ManageContent"
                    view = "AddMessage.html"
                    />
            <editform
                    schema = "mymessage.interfaces.IMessage"
                    for = "mymessage.interfaces.IMessage"
                    label = "Change Message"
                    fields = "title body"
                    name = "edit.html"
                    permission = "zope.ManageContent"
                    menu = "zmi_views" title = "Edit"
                    />
            <containerViews
                    for = "mymessage.interfaces.IMessage"
                    index = "zope.View"
                    contents = "zope.View"
                    add = "zope.ManageContent"
                    />
    
    </configure>
    

    为了使该配置文件生效,我们要在mymessage目录下的主configure.zcml文件中包含该文件。具体做法是在主configure.zcml文件的<configure>标签内包含以下语句:

    <include package=".browser" />
    
  7. 测试

    为了使该对象生效,我们要重启服务器。登录进Zope3后,我们可以看到,在右边的对象添加列表中多了一个MessageBoard项目,点击即可添加。在添加的MessageBoard对象内,我们可以添加Message对象,而且只能添加Message对象。点击打开Message对象,我们还可以再添加Message对象(回复)或附件(一般文件或图片等)。

    这里我们看到的界面是Zope3标准的格式,可能不是很符合你的要求,我们也可用可视组件(ZPT)重新设计一个界面,增强程序的表现力。

Chapter 3. How-To

现时Zope3的学习资料很少,网上的资料都比较零散。这个HowTo记录我接触Zope3时的一些积累。

3.1. 安装zope3

具体安装步骤请参考我网站上的debian学习笔记。

3.2. 安装zwiki for zope3

zwiki是在zope上的一个wiki系统,它在zope2平台上取得很大的成功。zope3平台现时的软件包还很少,官方网站上只有几个。下面介绍一个在zope3平台上安装zwiki的过程。

  1. http://www.zope.org/Products/Zope3-Packages/下载软件包。

  2. 把下载的软件包解压,生成zwiki-3.0.0目录,进入该目录并把该目录下的zwiki目录拷贝到zope3实例目录下的lib/python目录。

  3. zope3利用zcml文件来注册产品,所以我们要把zwiki-3.0.0/zwiki/zwiki-configure.zcml产品配置文件拷贝到zope3实例目录下的etc/package-includes目录中。

  4. 重启zope3服务器。登录http://localhost:8080即可在对象添加列表中看到多了Wiki这个对象,点击即可添加使用。

3.3. 常见问题

这里收集了我在使用Zope3中遇到一些小问题和我的解决方法。

  • 问题:用./runzope启动服务器时,提示“LookupError: unknown encoding: gb2312”出错信息。

    原因:我的python版本是python2.3.5,不支持gb2312编码。下面是我的测试过程:

    Python 2.3.5 (#2, Sep  4 2005, 22:01:42) 
    [GCC 3.3.5 (Debian 1:3.3.5-13)] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a="中国"
    >>> print a
    中国
    >>> a.decode('gb2312')
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    LookupError: unknown encoding: gb2312
    

    解决方法1:把python版本升级到python2.4。升级后我进行了验证:

    Python 2.4.1 (#2, May  5 2005, 11:32:06) 
    [GCC 3.3.5 (Debian 1:3.3.5-12)] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a="中国"
    >>> print a
    中国
    >>> a.decode('gb2312')
    u'/u4e2d/u56fd'
    

    解决方法2:为Python2.3.5增加gb2312编码支持。具体的操作方法可参考http://www.linuxmine.com/2915.html

Chapter 4. 参考资料

 

 
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1681869次
    • 积分:20460
    • 等级:
    • 排名:第415名
    • 原创:91篇
    • 转载:1131篇
    • 译文:0篇
    • 评论:146条
    文章分类
    最新评论