A Zope Primer
By Jon Udell, Byte.com
Jan 21, 2000 (7:13 AM)
URL: http://www.byte.com/column/BYT20000121S0001

This week, the subject of Zope came up again. I'd been pounding my head against the wall trying to figure some things out, and finally posted an exasperated plea to my newsgroup, in which I detailed some problems getting Zope's ZCatalog search engine to search Zope's own source code. Why? Well, I've begun to be productive in Zope, and would like to be more productive. The overwhelming bottleneck is availability of information. It struck me that Zope could use something like Mozilla's searchable, cross-referenced source viewer. Is there such a tool that groks Python syntax rather than C, I wondered?

Apparently not, and despite some helpful suggestions from folks on the Zope mailing list, I haven't solved that ZCatalog problem yet either. But despite the immaturity of Zope and its documentation, I've got to admit I've been bitten by the bug. I'm doing some really useful stuff with the product, and this week I decided it was time to give something back. If the major bottleneck is documentation, why not contribute some of my own? So here's a quick tutorial on Zope's Calendar Tag, which I hope will convey the flavor of Zope programming.

What is the Calendar Tag?The Calendar Tag is a piece of software, written in Python, that extends the DTML (Document Template Markup Language) which is Zope's equivalent to CFML (the Cold Fusion Markup Language), or ASP, or Java Server Pages. Probably the most powerful of Zope's built-in DTML tags is "<dtml-tree>," which renders an HTML tree control. In addition to Zope's own management interface, a surprising number of useful applications can be built on top of the tree control, partly because such a thing is just generally useful, and partly because Zope's built around an object database (ZODB) that works well with tree-structured data.

Since Calendar Tag isn't built in to Zope, you have to first install it. To do that, fetch the tarred and zipped file, and unzip and untar it into your Zope root directory. The result is a directory called Calendar under "ZOPE_HOME/lib/python/Products."

If you try to use the "<dtml-calendar>" tag at this point, you'll get an error. Why? Zope doesn't yet know about the Calendar Tag. To activate it, restart Zope.

I'm not going to try document everything about Calendar Tag. Refer to the Calendar Tag's README (a page of its management screen, under Products in the Zope management system), for a complete list of its arguments and properties.

What I want to do here is show how a custom DTML tag interacts with the Zope object system, with DTML scripts, and with Python scripts. To motivate the example, I'll show a minimalistic solution that binds Calendar Tag -- which is purely an HTML display mechanism -- to a store of calendar data. It's easy to imagine an SQL-backed calendar, but this example is not that fancy. It just stores calendar data in ZODB properties. When you manage a Zope server using the tree control, it looks like you're navigating a file system. But you're not, you're navigating ZODB. When you create a folder in ZODB, one of the objects that it will always contain is a DTML document called index_html. Conceptually, it corresponds to the default HTML page in a Web server directory, but it's not a file, it's an object, whose default behavior is to read a DTML template, process the tag-language constructs it contains, and renders an HTML result. In this example, the index_html method of a folder includes an instance of the <dtml-calendar> widget. Here's the calendar-related piece of index_html:

<dtml-calendar>
<dtml-call "setCalendar('valign','top')">
<dtml-let d="date.Date()"
hasdate="hasProperty(d)"
dprop="getProperty(d)">
<dtml-if "AUTHENTICATED_USER.has_role()=='Manager'">
<a href="index_html/editCalPropForm?prop=<dtml-var d>
&propval=<dtml-var dprop url_quote>">
<dtml-var "date.dd()">
</a>
<dtml-else>
<dtml-var "date.dd()">
</dtml-if>
<br>
<dtml-if "hasdate==1">
<dtml-var dprop>
</dtml-if>
</dtml-let>
</dtml-calendar>

Here's an image of the calendar display produced by this code:

The Calendar Tag code is called once per calendar cell, with the date object bound appropriately. Let's walk through what happens:

setCalendar(), one of the Calendar Tag's methods, sets up formatting for the cell. Within a <dtml-let> construct, which creates a local namespace:

d="date.Date()" stores a string like '2000/01/17' in the variable d

hasdate="hasProperty(d)" asks if the containing object -- that is, the index_html object -- has a property called "2000/01/17."

dprop="getProperty(d)" assigns the (possibly null) object that is that property to the variable dprop

A <dtml-if> tag checks whether the authenticated user is a Manager. If so, a reference to a property-editing form is wrapped around the DD representation of the date object. The <dtml-var> tag, used in several places, causes Python variables to be emitted into the HTML output. If the user isn't a Manager, only the two-digit day is emitted, without a surrounding property-editing link.

As shown above, the DTML code forms a link that an authenticated user can follow to post or edit a calendar entry. The link refers to the DTML document editCalPropForm, and passes it both the property name (2000/01/17) and the url-quoted property value. Here's editCalPropForm:

<dtml-var standard_html_header>

<form action="editCalendarProperty"> <input name="prop" type="hidden" value="<dtml-var prop>"> <b><dtml-var prop></b>: <br> <textarea name="propval" rows="15" cols="65" wrap="hard"> <dtml-var propval> </textarea> <br> <input type="submit" value="edit"> </form>

<dtml-var standard_html_footer>

It produces a form like the one shown in this image:

If the current day's property does not yet exist, the TEXTAREA will contain 'None'.

The action wired to this form is an external method, editCalendarProperty:

def editCalendarProperty(self,REQUEST,RESPONSE):
if ( self.hasProperty(REQUEST['prop']) ):
self.manage_changeProperties({ REQUEST['prop'] : REQUEST['propval'] })
else:
self.manage_addProperty(REQUEST['prop'], REQUEST['propval'], 'string')
return REQUEST.RESPONSE.redirect(REQUEST['BASE2'])

When the property exists, this method updates its value. Otherwise, this method creates the property and adds the new value.

What's an external method? It's a piece of pure Python code, which you place in the extensions subdirectory of your Zope installation, and bind into Zope by way of the management interface. Why use an external method? As it turns out, you don't have to in this case. You can do the same things in DTML, using <dtml-call> to invoke the Zope manage_ functions that add and modify index_html's properties. But as Perl hackers never tire of saying, "There's more than one way to do it." That's handy in the Zope world, too. DTML tag-language programming can be very effective in many situations, but it can also be cumbersome -- the <dtml-let> construct, for example, is awkward.

So, where's the beef? Well, the widget shown here, based on the Calendar Tag, is pretty useful. It's a simple matter to hook into Zope's role-based permission scheme. And because the calendar properties are stored in ZODB, various benefits accrue. One is that changes are logged, and can be undone. Another is that it should be easy to index and query on those properties. I say "should be" because, well, I haven't tried that yet. Like a lot of object-oriented systems, notably Smalltalk, Zope can give you tremendous leverage once you figure out how it's put together, but getting to that point can be a real challenge. I hope this example will help someone else get there a bit quicker than I did.

Byte Newsgroups:

What are your thoughts on Zope? Drop by the Byte.com newsgroups and let us know your thoughts.

Jon Udell (http://udell.roninhouse.com/) was Byte magazine's executive editor for new media, the architect of the original www.byte.com, and author of Byte's Web Project column. He's now an independent Web/Internet consultant, and is the author of Practical Internet Groupware, from O'Reilly and Associates. His recent Byte.com columns are archived at http://www.byte.com/index/threads

For more of Jon's Udell's columns, visit the Tangled In The Threads index page.