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)

    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


Note that this is the regular view directive, not browser:view.

Update: Philipp von Weitershausen shows how Grok simplifies this (site disappeared; here's an Internet Archive link). Check out the Grok website.

Update: Added the missing __init__ method, thanks to Yuan Hong for noticing.