After the basic code and interfaces, comes the trick of wiring the content objects into Zope 3. This is done via ZCML, an extensible configuration language that defines and describes components for use in Zope 3. ZCML may appear to be verbose, and I have not always been its biggest fan, but I have learned that it is almost always better to be explicit than implicit. Zope 2 often made many implicit assumptions. For the most part, this wasn't a problem. But when it was a problem, it was usually something hard to find and debug. When found, it was usually something in the system that was doing something clever and assumptive that the developer didn't expect it to be clever and assumptive about.
So now that our interfaces and content classes are in, it's time to wire them in. Using ZCML, we can define behavior and interfaces for objects outside of code. For example, declaring IAttributeAnnotatable support allows other Zope 3 components to add annotations. Annotations include things like Dublin Core support, which lets Zope 3 track title, description, modified time, etc, without the developer needing to code in support themselves like they did (or inherited) in Zope 2. Much of the security declarations that filled in Zope 2 python based product code is moved to ZCML. Other elements of ZCML might be recognized as similar to the Python code in the Product initialize(context): code:
<configure xmlns="http://namespaces.zope.org/zope">
<interface
interface=".interfaces.ITodoList"
type="zope.app.content.interfaces.IContentType"
/>
<content class="todo.TodoList">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
/>
<implements
interface="zope.app.container.interfaces.IContentContainer"
/>
<factory id="todo.TodoList" description="Todo List"/>
<require
permission="zope.ManageContent"
interface=".interfaces.ITodoList"
/>
</content>
<interface
interface=".interfaces.ITodo"
type="zope.app.content.interfaces.IContentType"
/>
<content class="todo.Todo">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
/>
<implements
interface="zope.app.container.interfaces.IContentContainer"
/>
<factory id="todo.Todo" description="Todo"/>
<require
permission="zope.ManageContent"
interface=".interfaces.ITodo"
/>
<require
permission="zope.ManageContent"
set_schema=".interfaces.ITodo"
/>
</content>
<include file="browser.zcml"/>
</configure>
The last line, include file="browser.zcml", I'll get to shortly. That is where the web interface is wired in. So far in developing this little project, I've only learned and done the basic add and edit forms, which are really easy in Zope 3. This is where the interface schema items will come into play. Similar, I assume, to how Ruby On Rails has Scaffolding.
But what we've done so far is setup the Model objects and wired them into the basic Zope 3 application. Next - standard views and controllers.
A significant design choice of Zope 3 is that it's interface driven. Interfaces are used not only to document, but to adapt. Interfaces are Zope 3's type system. Unlike class based types, anything can implement them or adapt to them, and all proper code in the Zope 3 system should ask for an interface implementation for a given object.
Interface design has been taken beyond basic method definitions to include object schema and even pre / post conditions. This data can be introspected to build user interfaces, which is how much of the standard Zope 3 management interface gets built. In fact, I'll show how this is done next in the series.
So the basic todo application uses the following interfaces. These are just the basic model objects. The Todo List is just a container that only contains items that implement the ITodo interface. A Todo doesn't do much beyond store some simple data. So here they are, from the file 'interfaces.py' in the package 'todo':
from zope.interface import Interface
import zope.schema
# These will aid in making a special "todo" container by specifying the
# constraint in the interface.
from zope.app.container.constraints import ItemTypePrecondition
from zope.app.container.interfaces import IContained, IContainer
class ITodo(Interface):
""" A todo item doesn"t need to do much... """
description = zope.schema.TextLine(
title=u"To Do",
required=True,
)
details = zope.schema.Text(
title=u"Details",
required=False,
)
done = zope.schema.Bool(title=u"Done")
class ITodoList(IContainer):
""" A todo list contains todo objects. """
def __setitem__(name, object):
""" Add an ITodo object. """
__setitem__.precondition = ItemTypePrecondition(ITodo)
The implementation is even simpler. Since this is a small application, I put the implementation in todo/__init__.py:
from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from persistent import Persistent
from interfaces import ITodoList, ITodo
class TodoList(BTreeContainer):
implements(ITodoList)
class Todo(Persistent):
implements(ITodo)
description = u""
details = u""
done = False
And that's it. Really! Now what needs to be done next is to wire these into Zope 3. But already, one can see that this is much simpler than what one might have to do to write some data objects for Zope 2.