(Traversing)

はじめに

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 a handle of a persistent object in ZODB object graph by its path.

Traversing can happen in two places

  • When a HTTP request hits the server the object method, which will generate the HTTP response, is looked up using traversing
  • You can manually traverse the 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. The id string is visible in the browser address bar when you view the object. Ids are also visible in the Zope Management interface.

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

Id should not contain spaces or slashes.

Path

The Zope path is the location of the object in the object graph. It is a sequence of id components from the parent node(s) to the child separated by slashes.

Example:

documentation/howTos/myHowTo

Exploring Zope application server

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

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

The 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 a local variable "portal"

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

Traversing by full path

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

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

Example:

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

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

警告

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

Getting object path

An object has two paths:

  • Physical path is the absolute location in the current ZODB object graph. This includes the site instance name as part of it.
  • Virtual path is the object location related to the 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"

ノート

Virtual path is not necessarily path relative to the site root, depending on the virtual host configuration.

Getting item path relative to the site root

There is no a direct, easy, way to accomplish this.

Example:

def getSiteRootRelativePath(context, request):
    """ Get site root relative path to an item

    @param context: Content item which path is resolved

    @param request: HTTP request object

    @return: Path to the context object, relative to site root, prefixed with a slash.
    """

    portal_state = getMultiAdapter((context, request), name=u'plone_portal_state')
    site = portal_state.portal()

    # Both of these are tuples
    site_path = site.getPhysicalPath();
    context_path = context.getPhysicalPath()

    relative_path = context_path[len(site_path)+1:]

    return "/" + "/".join(relative_path)

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 is not None:
        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

警告

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