Certain things are not quite obvious in Zope 3. Custom traversal is one of those: I always have to go and look at an example when I need it. Here's the example:
Say, you have a content object that provides IMySite and is exposed to the web at /mysite. You want to implement custom traversal for names under it, e.g. have /mysite/mycalendar return some object specific to the user that's currently logged in.
You need to provide an IBrowserPublisher adapter for (IMySite, IBrowserRequest):
from zope.component import adapts, queryMultiAdapter from zope.interface import implements from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserPublisher from mypackage.interfaces import IMySite class MySiteTraverser(object): """Browser traverser for IMySite.""" adapts(IMySite, IBrowserRequest) implements(IBrowserPublisher) def __init__(self, context, request): self.context = context self.request = request def browserDefault(self, request): """Return the default view of /mysite.""" # XXX: use getDefaultViewName instead of assuming it's index.html return self.context, ('index.html', ) def publishTraverse(self, request, name): """Traverse to /mysite/$name.""" if name == 'mycalendar': mycalendar = ... # TODO: do something to get the appropriate object return mycalendar # if self.context is a container of some sort, # you'll have to add traversal to items here manually. # fall back to views view = queryMultiAdapter((self.context, request), name=name) if view is not None: return view # give up and return a 404 Not Found error page raise NotFound(self.context, name, request)
Now register it in ZCML with
<view for="mypackage.interfaces.IMySite" type="zope.publisher.interfaces.browser.IBrowserRequest" provides="zope.publisher.interfaces.browser.IBrowserPublisher" factory="mypackage.mymodule.MySiteTraverser" permission="zope.Public" />
Note that this is the regular view directive, not browser:view.
Update: Added the missing __init__ method, thanks to Yuan Hong for noticing.