| Author: | Baiju M <baiju.m.mail AT gmail.com> |
|---|---|
| Version: | 0.1.96 |
| 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 |
Note
I assume you are using Debian 3.1 (Sarge) or similar systems.
Sorry! Zope3 cannot be introduced in 10 minutes :)
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.
Python, Zope, Interface, Component, ZCML, ZMI, Adapter, View, ZPT, Event, Utility, Principal, ZODB, ZEO.
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).
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.
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.
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/lib $ ../bin/test -vpu --dir boom
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.
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 configuartion 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!.
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.
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. Now by clicking on "Marks" tab you can see all book marks.
Ok! this is not the end, just the beginning of your study.
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/lib $ ../bin/test -vpf --dir boom
Now you can start learning Zope3 in detail, using Zope3 book. Also join 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
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.
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.
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
When we started discussing BookMarker, the first word I emphasized 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 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.
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.
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.
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.
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.
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.
Please go to the last section, otherwise you cannot 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.
Oh! it is just the end of this article. And the great beginning of your journey through Zope3 world. So, good luck.
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.
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.
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.
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 Ritcher 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 Plane Zope.
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
Initially it was to summarize my understanding of Zope3 and now I am maintaining this for newcomers.
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
Infact I want let you know that there is fuctional testing existing, thats all. In 3.2 onwards we will be using docttest for fuctional testing. You can read more about it from here. A standalone version is also available .
If you survived after that, then surely you will become a great Zope3 programmer :)
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)
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 excpetion.
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 $