Traversing

Introduction

In Plone, all content is mapped to a single tree: content objects, user objects, templates, etc. Even almost all object methods are directly mapped to HTTP visible URIs.

Each object has a path depending on its location. Traversing is a method of getting handle of persistent object in ZODB object graph by its path.

Traversing can happen in two places

  • When HTTP request hits the server the object method, which will generate the HTTP response, is looked up using travering
  • You can manually traverse ZODB tree in your code to locate objects by their path

Object ids

Each content object has an id string which identifies the object in the parent container. Tee id string is visible in the browser address bar when you view the object. Ids are also visible in Zope Management interface.

Besides id strings, the content objects have Unique Identified, or UID, which does not change even if the object is moved or renamed.

Id should not contain spaces or slashes.

Path

Zope paths is the location of the object in object graph. It is a a sequence of id components from the parent to the child separated by slashes.

Example:

documentation/howTos/myHowTo

Exploring Zope application server

You can use Zope Management interface to explore the content of your Zope application server:

  • Sites
  • Folders within the sites
  • ...so on

ZMI does not expose individual attributes. It only exposes traversable content objects.

Attribute traversing

Zope exposes child objects as attributes.

Example:

# you have obtained the plone.org portal root object somehow and it's
# stored in local variable "portal"

documentation = portal.documentation
howTos = getattr(portal, "how-to") # note that we need use getattr because dash is invalid in syntax
myHowTo = getattr(howTos, "manipulating-plone-objects-programmatically")

Container traversing

Zope exposes child objects as container accessor.

Example:

# you have obtained the plone.org portal root object somehow and it's
# stored in local variable "portal"

documentation = portal["documentation"]
howTos = documentation["how-to"]
myHowTo = howTos["manipulating-plone-objects-programmatically"]

Traversing by full path

Any content object provides methods restrictedTraverse() and unrestrictedTraverse(). See Traversable.

Security warning: restrictedTraverse() uses priviledges of currently logged in user. Unauthorized exception is raised if the code tries to access object for which user lacks Access contents information and View permissions.

Example:

myHowTo = portal.restrictedTraverse("documentation/howTos/myHowTo")

# Bypass security
myHowTo = portal.unrestrictedTraverse("documentation/howTos/myHowTo")

Warning

restrictedTraverse()/unrestrictedTraverse() does not honor IPublishTraverse adapters. Read more about the issue in this discussion.

Getting object path

Object has two paths:

  • Physical path is the absolute location in the current ZODB object graph. This includes site instance name as the part of it.
  • Virtual path is the object location related to Plone site root

Path mangling warning: Always store paths as virtual paths or persistently stored paths will corrupt if you rename your site instance.

See Traversable.

Getting physical path

Use getPhysicalPath(). Example:

path = portal.getPhysicalPath() # returns "plone"

Getting virtual path

Map physical path to virtual path using HTTP request object physicalPathToVirtualPath(). Example:

request = self.request # HTTPRequest object

path = portal.document.getPhysicalPath()

virtual_path = request.physicalPathToVirtualPath(path) # returns "document"

Getting object URL

Use absolute_url(). See Traversable.

URL mangling warning: absolute_url() is sensitive to virtual host URL mappings. absolute_url() will return different results depending on if you access your site from URLs http://yourhost/ or http://yourhost:8080/Plone. Do not persistently store the result of absolute_url().

Example:

url = portal.absolute_url() # http://nohost/plone in unit tests

Getting the parent

Object parent is accessible is acquisition chain for the object is set.

Use aq_parent.

parent = object.aq_parent

Parent is defined as __parent__ attribute of the object instance.

object.__parent__ = object.aq_parent.

__parent__ is set when object’s __of__() method is called.

view = MyBrowserView(context, request)

view = view.__of__(context) # Inserts view into acquisition chain and acquistion functions become available

Getting all parents

Example:

def getAcquisitionChain(object):
    """
    @return: List of objects from context, its parents to the portal root

    Example::

        chain = getAcquisitionChain(self.context)
        print "I will look up objects:" + str(list(chain))

    @param object: Any content object
    @return: Iterable of all parents from the direct parent to the site root
    """

    # It is important to use inner to bootstrap the traverse,
    # or otherwise we might get surprising parents
    # E.g. the context of the view has the view as the parent
    # unless inner is used
    inner = object.aq_inner

    iter = inner

    while iter:
        yield iter

        if ISiteRoot.providedBy(iter):
           break

        if not hasattr(iter, "aq_parent"):
            raise RuntimeError("Parent traversing interrupted by object: " + str(parent))

        iter = iter.aq_parent

Getting the site root

You can resolve the site root if you have the handle to any context object.

Example:

from Products.CMFCore.utils import getToolByName

# you know some object which is refered as "context"
portal_url = getToolByName(context, "portal_url")
portal = portal_url.getPortalObject()

Site is also stored a thread local variable. In Zope each request is processed in its own thread. Site thread local is set when the request processing starts.

You can use this method even if you do not have the context object available, assuming that your code is called after Zope has traversed the context object once.

Example:

from zope.app.component.hooks import getSite

site = getSite() # returns portal root from thread local storage

Getting Zope application server handle

You can also access other sites within the same application server from your code.

Example:

app = context.restrictedTraverse('/') # Zope application server root
site = app["plone"] # your plone instance
site2 = app["mysiteid"] # another site

Acquisition effect

Sometimes traversing can give you attributes which actually do not exist on the object, but are inherited from the parent objects in the persistent object graph. See acquisition.

Custom traversing

You can create your own traversing hooks.

  • TODO

Warning

If AttributeError is risen inside traverse() function bad things happen, as Zope publisher specially handles this and raises NotFound exception.