Using z3c.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/bluemuffinNow, 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 ;)