Zope 3 in 30 Minutes

Author: Baiju M <baiju.m.mail AT gmail.com>
Version: 0.1.98

Note

I assume you are using Debian 3.1 (Sarge) or similar systems.

1   Quick Start

1.1   Why 30?

Sorry! Zope3 cannot be introduced in 10 minutes :)

1.2   And why should I ...?

If you are looking for a Pythonic framework for web application development, just continue reading. And I am not going to make a new definition for What is Pythonic? :) Here I will try to answer your "why?" questions.

1.3   Keywords

Python, Zope, Interface, Component, ZCML, ZMI, Adapter, View, ZPT, Event, Utility, Principal, ZODB, ZEO.

1.4   So let's start ...

Zope3 is the third generation of Zope, a framework for web applications. You can download Zope 3.1 from Zope products page: http://www.zope.org/Products/Zope3 . To install untar Zope3 source package, then as root:

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

After installation you have to make an instance (don't worry! just do it). To do so:

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

To start Zope3 server, go to your instance directory, then:

$ cd ~/myzope
$ ./bin/runzope

If you get a port error, check whether 8080 and 8021 is already used by other programs; for the time being, just stop it. Start your browser, the open http://localhost:8080 . What you see is the Zope Management Interface (ZMI). ZMI is your Python prompt, hmm... no! Zope prompt, got it?. You can login and look around to see what's happening. If you played enough with ZMI, stop it from terminal (Control + C).

1.5   BookMarker : Your first Zope3 app

Yes! we are going to create a Zope3 application, an online book marker. Our app will display links to websites and a description for each link.

So, what you have to think about when you start a Zope3 project. Oh! sorry! I can't put it in one sentence, you better learn and practice Extreme Programming . Anyway, after your initial design, you will write interfaces. Let us hope Python 3.0 will make it much easier. Then you will write unit tests, now your ideas become very concrete!. At last, write your real code. You will be satisfied when you implement interfaces one by one and unit tests succeeds!. I have given the source code of BookMarker? here: boom.tar.bz2 .

Our code will be placed at $HOME/myzope/lib/python/boom

First create a file named interfaces.py where we will keep our interfaces. Later we will implement these interfaces one by one, with strong support of unit testing.

1.6   Interfaces

Here is our 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):
    """This is the book mark object."""

    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):
    """This is the container for all book marks."""

    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):
    """A book mark can only contain in a BookMarker"""

    __parent__ = Field(
        constraint = ContainerTypesConstraint(IBookMarker))

Our first interface IMark? has two attributes, one is the URL of the site and the other one is the description. Please note, IMark? is not a class even though we used Python's class definition. We inherited from Interface to make it an interface. Second one is a container interface, which is an extended IContainer? interface. By using this container interface we can persist our objects (instances of IMark? implementations). We will put all objects of IMark? in a container object of IBookMarker?. We will implement IMark? along with IMarkContained? as a constraint interface. So that IMark? object will be only contained in an IBookMarker? object.

1.7   Unit Testing

Now create tests.py and put the following code there:

import unittest
from zope.testing.doctestunit import DocTestSuite

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

from boom.bookmarker import BookMarker


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') 

Actually, we are not written any unit tests here, but this will make our doc tests running automatically.

To run the unit test:

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

1.8   Real coding!

Now let's move on to the implementation (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):
    """Implementation of IMark

    Make sure that the `Mark` implements the `IMark` interface::

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

    Make sure that the `Mark` implements the `IMarkContained`
    interface::

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

    An example of checking the url of Mark::

      >>> mk = Mark()
      >>> mk.url
      u'http://www.zope.org'
      >>> mk.url = u'http://www.python.org'
      >>> mk.url
      u'http://www.python.org'
  
    An example of checking the description of 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):
    """Implementation of IBookMarker using B-Tree Container

    Make sure that the `BookMarker` implements the `IBookMarker`
    interface::

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

    An example of changing the name of BookMarker::

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

    implements(IBookMarker)

    name = u""

We have written doctests along with the implementations. Doctests are accompanied with examples, so it is called example driven unit testing.

1.9   Configuration

Now configuration (save in 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>

Is it self explanatory? "no...!" then ok! we will discuss Zope Configuration Markup Language (ZCML) briefly later. Actually, if you are familiar with ZCML this configuration will be more than self explanatory. It will give you an overall idea about the entire application. Now you might think, it is not Pythonic :( Hey! think twice!.

1.10   Just run it

As the last step to work our application, put the following line in: $HOME/myzope/etc/package-icludes/boom-configure.zcml:

<include package="boom"/>

Now you registered your package.

Run zope again, then open your browser, add a BookMarker? and few book marks.

Now you want to arrange your your book marks in a better way, don't you?. For the time being, just relax, then we will create a view for book marks.

2   Move on

2.1   Views

Now create a file named browser.py with following code:

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

Then one template (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>

Uncomment the last directive 'browser:page' in our configuartion. Then restart Zope again. Now by clicking on "Marks" tab you can see all book marks.

Ok! this is not the end, just the beginning of your study.

2.2   Functional testing

Let's finish our example by writing a functional test for the view. Our code will be placed in ftests.py:

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

class BookMarksTest(BrowserTestCase):

    def testAddBookMarker(self):
        response = self.publish(
            '/+/AddBookMarker.html=bm',
            basic='mgr:mgrpw',
            form={'field.name': u'MyBookMarker',
                  'UPDATE_SUBMIT': 'Add'})
        self.assertEqual(response.getStatus(), 302)
        self.assertEqual(response.getHeader('Location'),
                         'http://localhost/@@contents.html')

    def testAddMark(self):
        self.testAddBookMarker()
        response = self.publish(
            '/bm/+/AddMark.html=mrk',
            basic='mgr:mgrpw',
            form={'field.url': u'http://www.zope.org',
                  'field.description': u'Zope Project Site',
                  'UPDATE_SUBMIT': 'Add'})
        self.assertEqual(response.getStatus(), 302)
        self.assertEqual(response.getHeader('Location'),
                         'http://localhost/bm/@@contents.html')

    def testMarksListing(self):
        self.testAddMark()
        response = self.publish(
            '/bm/@@marks.html',
            basic='mgr:mgrpw')
        body = response.getBody()
        self.checkForBrokenLinks(body, '/bm/@@marks.html',
                                 basic='mgr:mgrpw')
        self.assert_(body.find('Zope Project Site') > 0)


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

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

To run the functional test:

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

2.3   Now what?

Now you can start learning Zope3 in detail, using the Zope3 book. Also join the zope3-users mailing list.

There is a good Zope3 quick start guide by Benji York: http://www.benjiyork.com/quick_start/

A good introductory book is also available in print, visit: http://worldcookery.com/

Just one more thing: I want to improve this document, so don't hesitate to write your feedback to: baiju.m.mail AT gmail.com

3   Big Picture

3.1   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 has happened here. So let's look back again.

3.2   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.

3.3   Why should I code like this?

When we started discussing BookMarker?, the first word I emphasized was 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 cannot 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. In a similar fashion, we will use zope.interface.Interfce inherited metaclass for defining an interface. There is every chance for an explicit '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.

3.4   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 fields available at zope.schema package to specify these attributes. A schema is an interface with attributes defined using zope.schema fields (these are classes). So, our IBookMarker?, IMark?, and IMarkContained? are schemas, and not simple interfaces.

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 [Re]?StructuredText? files with unit testing, this is the Zope3 way of unit testing.

3.5   Unit testing re-visited

We wrote our unit 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. Supporting modules should not start with that prefix. Now Zope3 test automation script can auto detect all your test modules.

To test our BookMarker? container, we inherited from TestSampleContainer?. See even test modules are reusable in Zope3!. Many tests which we have to write for a container are written in TestSampleContainer?. So, for testing containers, you can stick with unittest module. Also use it where you want more reusablity.

DocTestSuite? class help us to integrate all doctests with unittest module. This will enable us to run tests automatically, from a single point. Sometimes you have to write more codes for testing than real implementation. Gradually this will make your development fast. Otherwise there is something wrong with your development process.

3.6   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 a 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 are two attributes, url and description. Similarly majority of IBookMarker? is implemented in BTreeContainer?. I hope documentation strings are self explanatory.

3.7   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 cannot use a text file 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 'interface' is a simple directive and 'content' is a complex directive.

3.8   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 writing any information retrieval logic in ZPT. In ZCML we have written browser:page directive to support the views.

4   Let's Finish

4.1   The things we didn't mention

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

Of course, 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 made just one assumption 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 of course, willing to spend time :)

While installation you might get an 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 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 the Python language itself. Yes! it is one of the gratest Python application ever written.

4.2   The End

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

4.3   About the author

I am a 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 proud GNU Emacs user.

5   Appendix

Our time is over :) And this section is not an essential part of this article. This section is for curious readers, who cannot jump to other places at the moment.

5.1   Define few words

ZODB
Zope Object Database, Zope primarily use this object (oriented) database for storing all objects (data). Other storages like RDBMS or external file systems can also be used with Zope.
Component

Zope use a component architecture for developing applications. Zope3 Offcial site says like this:

A component is defined as an "object with introspectable interfaces". Specifically, we define a component to be an object that asserts interfaces using the ZopeInterfaces?.

5.2   Where to look next

Zope3 has two books available in print, the links are given above. But I think still there is no Indian edition available, so I didn't yet bought them :)

Zope3 book by Stephan Richter is available online.

Zope3 IRC channel #zope3-dev is at irc.freenode.net

And remember Zope3 development site has lots of documents.

zope3-users mailing list is the first place to ask any questions related to Zope3. But before asking any question please go through online documentations.

You can read other Zope developers/users blog entries from Planet Zope.

5.3   Questions and Answers

Here I will try to answer few questions which is very likely to arise in your mind while reading this article. If your simple question is not answered here please send it to : baiju.m.mail AT gmail.com

5.3.1   Why this article?

Initially it was to summarize my understanding of Zope3 and now I am maintaining this for newcomers.

5.3.2   How did you create this tutorial?

I am using ReStructuredText to write this document. To create HTML, just copied three files in Zope3/doc/style/ to current folder, then:

$ rest2html --stylesheet=rest.css Zope3In30Minutes.txt > Zope3In30Minutes.html

5.3.3   Did you said something about functional testing?

Infact I want let you know that functional testing exists, thats all. In 3.2 onwards we will be using doctest for functional testing. You can read more about it from here. A standalone version is also available .

5.3.4   I think you wrote more lines for configuration than other parts?

If you survived after that, then surely you will become a great Zope3 programmer :)

5.3.5   Oh! lots of Python web frameworks, I am totally confused?

Here I am not going to compare Zope3 with any other frameworks. But I think Python Web Server Gateway Interface (WSGI) gives a real hope for lots of reusability. Most of the Python web frameworks are trying to adhere to WSGI specification. Infact many components of Zope3 can be used outside Zope3 (eg:- zope.interface, zope.testbrowser)

5.3.6   Is 'tests.py' and 'ftests.py' file names mandatory?

Yes, for a simple package like BookMarker?, you can use simple file based modules. If the file names are given like that, Zope3 testing script will autodetect your testing modules. For big packages use 'test' and 'ftests' sub-packages to place your test modules. All those test modules should start with test_ prefix.

Note

According to Zope3 naming convention all module/package names should be in lower cases, and should not contain underscore in between. But test module names are one exception.

5.3.7   What will be the final file structure of 'boom'?

Here it is:

$ pwd
/home/baiju/myzope/lib/python/zissue
$ ls
bookmarker.py        browser.py      ftests.py    interfaces.py  README
boom-configure.zcml  configure.zcml  __init__.py  marks.pt       tests.py

$Id: Zope3In30Minutes.txt 48 2005-12-04 12:16:21Z baijum81 $