This Blog continues on http://aliafshar.github.io/blog

Sunday, November 05, 2006

In search of a zope gui, why the zope web way might not be the best way

So in our last post, we saw how you could use the Zope interface architecture to adapt objects to views, and how Zope does it. The ultimate goal of this exercise is to have a gui way of representing Zope. This is all fine, but we have to consider that perhaps in a gui, one view per object may not be the best method.

Creating a gui repeatedly for the same type of object, just to change its contents is just wrong.

In our example, we had a multi-adapter for IObject, IRequest, which was a view object with a render method. No doubt perfect for web requests, but not for a gui which lives in a loop, is governed by events and can hide lots of stuff.

What it seems we actually need is an adapter for ITypeOfObject, which is creaeted without an object, and can then have the content set and reset.

Enter zope.schema

zope.schemas are interfaces and have all the interface properties. They do have additional niceties, in that you can define attributes as constrained field types, for example Int, and in zope all of these field types are adaptable into html form elements. In our gui case we want to adapts them to widgets. The schema can be the adapted object when the view is created, and the view can have some kind of content setter which is reposnible for updating the widgets in the view. It is worth noting that this approach may become more used with increasing developments in the Javascript world.

So, first we define ISchema, and an implementor of ISchema. It is important to note here that we have left how zope uses schemas. They are Interface subclasses, and although this has advantages, I fell slightly ill-at-ease with it. We will instantiate the Schema object and we will worry about iterating the fields in it later.

from zope.interface import implements, Interface, Attribute, classProvides
from zope.component import adapts, provideAdapter

class IAnimal(Interface):
name = Attribute("""The name of the Animal""")

class ISchema(Interface):
pass

class AnimalSchema(object):
"""This is a schema"""
implements(ISchema)
name = Text(title=u'The Name')


And now a something to implement the IAnimal/IAnimalSchema. The schema and the interface are essentially the same, and you might ask why not just use the interface. Well, simply because being an Interface subclass is a royal pain. It imposes lots of bizarre restrictions, and since we really want to push it a bit later on, we should depart from that now.

class Animal(object):
"""An animal"""
implements(IAnimal)
def __init__(self, name):
self.name = name

And now the view stuff. This view will be setup once, and then available to have its content changed.

class IView(Interface):

def render(self):
"""Render visually"""

def set_content(self, content):
"""Update the view to reflect the content"""

class AnimalView(object):

implements(IView)
adapts(AnimalSchema)

def __init__(self, schema):
self._schema = schema

def render(self):
print 'Our schema is %s' % self._schema

def set_content(self, content):
print 'we have new content %s' % content.name

provideAdapter(AnimalView)

Now let's run it.

if __name__ == '__main__':
view = IView(AnimalSchema())
view.render()
for i in map(Animal, [u'lion', u'tiger', u'leopard']):
view.set_content(i)

And you get:

Our schema is <__main__.animalschema>
we have new content lion
we have new content tiger
we have new content leopard

Although this is just a command line output, you can see that the stages have been fulfilled. The gui can be setup in the initial phase by drawing all the widgets, and then the values can be set at later points, asynchronously.