Using z3c.traverser

Posted on November 21, 2009
One of the things which looked so hard, but reveiled using so easy was z3c.traverser. I’m sometimes a bit retarded reading doctests and applying them to my needs. For those who have the same problem, here a small example on how to use the traverser.

The Muffin Traverser

Motivation

Our web application catalogues muffin recipes. Every bloody muffin shop in town has a login to our muffin-application and publishes recipes. To view such a recipe the URLs look like this:
http://muffinsinthehouse.com/shop/recipes/bluemuffin
Now, the customer who operates the site comes to us with a wish: please make the URLs shorter. Each recipe is created with a unique ID and I like to use this id to look up recipes like this:
http://muffinsinthehouse.com/bluemuffin

Implementation

“No worries mate”, you say and use z3c.traverser and implement a custom traverser. The code below shows your custom traverser plugin:
import z3c.traverser.interfaces
import zope.interface
import zope.component
import zope.publisher.interfaces
import zope.app.catalog.interfaces

class MuffinTraverserPlugin(object):
    """Traverser which tries to lookup muffins in the database."""

    zope.interface.implements(
        z3c.traverser.interfaces.ITraverserPlugin)

    def __init__(self, context, request):
        self.context = context
        self.request = request

    def publishTraverse(self, request, name):
        catalog = zope.component.getUtility(
            zope.app.catalog.interfaces.ICatalog)
        result = catalog.searchResults(objectname=name, metatype='Recipe')
        if not result:
            raise zope.publisher.interfaces.NotFound(
                self.context, name, request)
        return result[-1].getObject()

There is nothing really special here, if you know how traversal works in Zope. The plugin is a Multiadapter (a view), which implements z3c.traverser.interfaces.ITraverserPlugin. The object lookup for your muffins is happening in the publishTraverse method. This method either returns the object or raises a NotFound exception if it couldn’t lookup the object. Easy as pie. The catalog is utilised to lookup the object and if its not found a NotFound error is raised.

You register the plug-in with the following ZCML directive:
<adapter
 factory="z3c.traverser.traverser.PluggableTraverser"
 for="zope.traversing.interfaces.IContainmentRoot
 zope.publisher.interfaces.IPublisherRequest"
 />

 <subscriber
 factory=".traversing.MuffinTraverserPlugin"
 for="zope.traversing.interfaces.IContainmentRoot
 zope.publisher.interfaces.IPublisherRequest"
 provides="z3c.traverser.interfaces.ITraverserPlugin"
 />

The first directive “enables” z3c.traverser, the second registers your plugin. After that, try out the custom URL which should work. But wait, if you try to look up other objects in the database, you’ll notice that your traverser deals with them too. That was not the plan, was it?

z3c.traverser provides other plug-ins to deal with folders attributes etc. Don’t lump all those object look-ups into your plug-in! You need to register them additionally:
<subscriber
 factory="z3c.traverser.traverser.ContainerTraverserPlugin"
 for="zope.traversing.interfaces.IContainmentRoot
 zope.publisher.interfaces.IPublisherRequest"
 provides="z3c.traverser.interfaces.ITraverserPlugin"
 />

Try again. This should satisfy the customer.

Comments welcome ;)