========================
Zope 3 Tutorial (Part 1)
========================
Introduction
------------
FIXME: Two or three small paragraphs.
Installation
------------
You will be required to install `Python 2.4.2`_ or later for
installing Zope 3. The latest stable release of Zope 3 can be
downloaded from http://www.zope.org/Products/Zope3 .
To install Zope 3 in GNU/Linux::
# tar zxvf Zope-3.2.0.tgz
# cd Zope-3.2.0
# ./configure --with-python=/path/to/python2.4
# make install
I recommend binary setup for win32_, you may go for source
installation, if required.
If you cannot install Zope3, see the `detailed instruction`_.
.. _Python 2.4.2: http://www.python.org/ftp/python/2.4.2/Python-2.4.2.tar.bz2
.. _win32: http://www.zope.org/Products/Zope3/3.2.0final/Zope-3.2.0.win32-py2.4.exe
.. _detailed instruction: install.html
Make an instance
----------------
Zope 3 uses ``instances`` to contain the information relevant to a
server (or set of servers). To create an instance go to installation
directory and run ``mkzopeinstance`` command, it will ask for few
values. It will look like something like this::
$ cd /usr/local/Zope-3.2.0/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: /home/admin/myzope
Please choose a username for the initial administrator account.
This is required to allow Zope's management interface to be used.
Username: admin
Please select a password manager which will be used for encode the password of
the initial administrator account.
1. Plain Text
2. MD5
3. SHA1
Password Manager Number [1]: 2
'MD5' password manager selected
Please provide a password for the initial administrator account.
Password:
Verify password:
After creating the instance go to ``instance home``, then run zope::
$ cd /home/admin/myzope
$ ./bin/runzope
You should see something like this::
------
2006-03-13T08:20:12 INFO root -- HTTP:localhost:8080 Server started.
Hostname: localhost
Port: 8080
------
2006-03-13T08:20:12 INFO root Startup time: 9.746 sec real, 5.660 sec CPU
If you get a port error, check whether 8080 is already used by other
programs; for the time being, just stop it.
The instance home
-----------------
Let's look into your instance home, first read the README.txt.
This instance home can be considered something like your workplace.
::
$ ls * -F
README.txt
bin:
debugzope* i18nmergeall* importchecker* runzope* test.bat*
debugzope.bat* i18nmergeall.bat* importchecker.bat* runzope.bat* zopectl*
i18nextract* i18nstats* pyskel* static-apidoc* zopeservice.py*
i18nextract.bat* i18nstats.bat* pyskel.bat* test* zpasswd*
etc:
ftesting.zcml package-includes/ securitypolicy.zcml ssh_host_rsa_key
overrides_ftesting.zcml principals.zcml server.pem zdaemon.conf
overrides.zcml securitypolicy-ftesting.zcml site.zcml zope.conf
lib:
python/
log:
access.log README.txt z3.log
var:
Data.fs Data.fs.index Data.fs.lock Data.fs.tmp README.txt
As you can see, the ``bin`` directory contains few scripts like
``runzope``, ``test`` etc. Some scripts are only specific to Windows.
Zope saves all data in `var` directory. Backup this directory
regularly. If ``Data.fs`` is missing, it will be automatically
created when you run zope again. In `etc` you can see some
configuration files. In ``etc/zope.conf`` you can edit port numbers
of various servers. In ``lib/python`` you can place your Zope3
packages. By default this won't be in your Python path. You can also
place your packages in any standard Python path.
The ZMI
-------
If you open a web browser and go to http://localhost:8080 you'll see
the ZMI (Zope Management Interface).
Go ahead and click the `Login` link at the upper right. Enter the user
name and password you gave when creating the instance. Now click on
[top] under `Navigation` on the right. Play around with adding some
content objects (the Zope 3 name for instances that are visible in the
ZMI). Note how content objects can be arranged in a hierarchy by
adding "folders" which are special content objects that can hold other
content objects.
There is nothing special about the ZMI, it is just the default skin
for Zope 3. You can modify it to your liking, or replace it entirely.
When you're done exploring with the ZMI, go back to the window where
you typed "runzope" and see that each request from your browser was
displayed there as it happened. Press Control-C to stop Zope.
Ticket Collector application
----------------------------
.. sidebar:: Extreme Programming
We will be using Extreme Programming style terminologies
through this tutorial. I recommend you to use this (or similar)
development process in your workplace. Zope 3 has taken
lots of ideas from `Extreme Programming`_.
To learn Zope 3 application development we will be using a
issue/bug/ticket collector/tracker/manager application.
First I will tell you the user stories, but we won't be
doing the complete stories here.
1. Individual small ticket collector for each project.
Many collectors can be added to one running zope.
2. Any number of tickets can be added to one collector.
3. Each ticket will be added with a description and
one initial comment.
4. Additional comments can be added to tickets.
.. _Extreme Programming: http://en.wikipedia.org/wiki/Extreme_programming
Interfaces
----------
The first thing you have to do is to finalize the initial intraces of
content objects, of course you can evolve it later. This is something
similar to table design, if you are using RDBMS, but not exactly the
same, you will understand why it's not.
As I said earlier you can place your packages in ``lib/python``
directory. So create ``lib/python/collector``, and don't forget
to add ``__init__.py`` (empty file) to make it a Python package.
To write our interfaces, create `interfaces.py` file.
This 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 IComment(Interface):
"""Comment for Ticket"""
body = Text(
title=u"Additional Comment",
description=u"Body of the Comment.",
default=u"",
required=True)
class ITicket(IContainer):
"""A ticket object."""
summary = TextLine(
title=u"Summary",
description=u"Short summary",
default=u"",
required=True)
description = Text(
title=u"Description",
description=u"Full description",
default=u"",
required=False)
def __setitem__(name, object):
"""Add an IComment object."""
__setitem__.precondition = ItemTypePrecondition(IComment)
class ICollector(IContainer):
"""Collector the base object. It can only
contain ITicket objects."""
def __setitem__(name, object):
"""Add an ICollector object."""
__setitem__.precondition = ItemTypePrecondition(ITicket)
description = Text(
title=u"Description",
description=u"A description of the collector.",
default=u"",
required=False)
class ITicketContained(IContained):
"""Interface that specifies the type of objects that can contain
tickets. So a ticket can only contain in a collector."""
__parent__ = Field(
constraint = ContainerTypesConstraint(ICollector))
class ICommentContained(IContained):
"""Interface that specifies the type of objects that can contain
comments. So a comment can only contain in a ticket."""
__parent__ = Field(
constraint = ContainerTypesConstraint(ITicket))
As you can see the first three are our main interfaces, and the rest two
are actaully constraint interfaces.
Unit testing
------------
For most of the unit/functional testing, we will be using doctests.
But for testing containers we are make using some already written
unit tests. All unit tests will written in ``tests`` sub-directory.
This is ``tests/test_collector.py``::
import unittest
from zope.testing.doctestunit import DocTestSuite
from zope.app.container.tests.test_icontainer import TestSampleContainer
from collector.ticketcollector import Collector
class Test(TestSampleContainer):
def makeTestObject(self):
return Collector()
def test_suite():
return unittest.TestSuite((
DocTestSuite('collector.ticketcollector'),
unittest.makeSuite(Test),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Create a ``tests/test_ticket.py`` file similarly.
This is very similarly to the above file. ::
import unittest
from zope.testing.doctestunit import DocTestSuite
from zope.app.container.tests.test_icontainer import TestSampleContainer
from collector.ticket import Ticket
class Test(TestSampleContainer):
def makeTestObject(self):
return Ticket()
def test_suite():
return unittest.TestSuite((
DocTestSuite('collector.ticket'),
unittest.makeSuite(Test),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
And now ``test/test_comment.py`` file::
import unittest
from zope.testing.doctestunit import DocTestSuite
def test_suite():
return unittest.TestSuite((
DocTestSuite('collector.comment'),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Comment is not a container, but to run our doctests which
we are going to write should run automatically, so this hook.
In fact we can write tests in stand alone text files.
To run the unit test::
$ cd $HOME/myzope/etc
$ ../bin/test -vpu --dir collector
Of cource now all tests should fail.
Implementation
--------------
As you can see in the unit test module, collector is going to be
implemented in ticketcollector.py::
from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from interfaces import ICollector
class Collector(BTreeContainer):
"""A simple implementation of a collector using B-Tree Containers.
Make sure that the ``Collector`` implements the ``ICollector``
interface::
>>> from zope.interface.verify import verifyClass
>>> verifyClass(ICollector, Collector)
True
Here is an example of changing the description of the collector::
>>> collector = Collector()
>>> collector.description
u''
>>> collector.description = u'Ticket Collector Description'
>>> collector.description
u'Ticket Collector Description'
"""
implements(ICollector)
description = u''
Similarly ticket.py::
from zope.interface import implements
from zope.interface import classProvides
from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained
from interfaces import ITicket
from interfaces import ITicketContained
class Ticket(BTreeContainer, Contained):
"""A simple implementation of a ticket using B-Tree Containers.
Make sure that the ``Ticket`` implements the ``ITicket`` interface::
>>> from zope.interface.verify import verifyClass
>>> verifyClass(ITicket, Ticket)
True
Here is an example of changing the summary and description of the ticket::
>>> ticket = Ticket()
>>> ticket.summary
u''
>>> ticket.description
u''
>>> ticket.summary = u'Ticket Summary'
>>> ticket.description = u'Ticket Description'
>>> ticket.summary
u'Ticket Summary'
>>> ticket.description
u'Ticket Description'
"""
implements(ITicket, ITicketContained)
summary = u''
description = u''
Similarly comment.py::
from zope.interface import implements
from interfaces import IComment
from interfaces import ICommentContained
from zope.app.container.contained import Contained
class Comment(Contained):
"""A simple implementation of a comment.
Make sure that the ``Comment`` implements the ``IComment`` interface::
>>> from zope.interface.verify import verifyClass
>>> verifyClass(IComment, Comment)
True
Here is an example of changing the body of the comment::
>>> comment = Comment()
>>> comment.body
u''
>>> comment.body = u'Comment Body'
>>> comment.body
u'Comment Body'
"""
implements(IComment, ICommentContained)
body = u""
Configuration
-------------
We have written intefaces and its implemenations, now how to bind this
with Zope 3 framework. We will use Zope Configuration Markup Language
(ZCML) based configuaration file for this.
This is our configure.zcml::
Running application
-------------------
Before running the applcation we will add one some view for adding
Collector. We will go in to more details in the next part.
Create a `browser` directory and under that, a new `configure.zcml`
file::
The "class" attribute specifies the module path for the class, a
leading dot means to make the import relative to the package
containing the ZCML file. Therefore in this case Zope will import the
collector.ticketcollector module, then import "Collector" from that module.
The "title" attribute provides the title to display in the add menu.
The "permission" attribute is used to describe what permission is
required for a person to be able to add one of these objects. The
"zope.ManageContent" permission means that the user can add, remove,
and modify content (the "admin" user you created while making the
instance is one such user).
We need to tell Zope to read our ZCML file, and the easiest way to do
that is to put a "slug" in the $HOME/myzope/etc/package-includes/
directory. A "slug" is a ZCML file that just includes another
file. Here's what our slug should look like (save it as
"collector-configure.zcml")::
Now if we start Zope back up, we can go to the ZMI and add our content
type by clicking on "Add Collector" and entering a name for our
object; name it "MyCollector".
Now restart Zope and visit http://localhost:8080
You can add collector from menu.
.. Note::
Some part of this tutorial is taken from Benji York's Zope 3 Quick
Start Guide, http://www.benjiyork.com/quick_start/
"Benji York (http://benjiyork.com)"