Back to index

plone3  3.1.7
Classes | Functions
plone.app.layout.navigation.navtree Namespace Reference

Classes

class  NavtreeStrategyBase

Functions

def buildFolderTree

Function Documentation

def plone.app.layout.navigation.navtree.buildFolderTree (   context,
  obj = None,
  query = {},
  strategy = NavtreeStrategyBase() 
)
Create a tree structure representing a navigation tree. By default,
it will create a full "sitemap" tree, rooted at the portal, ordered
by explicit folder order. If the 'query' parameter contains a 'path'
key, this can be used to override this. To create a navtree rooted
at the portal root, set query['path'] to:

    {'query' : '/'.join(context.getPhysicalPath()),
     'navtree' : 1}

to start this 1 level below the portal root, set query['path'] to:

    {'query' : '/'.join(obj.getPhysicalPath()),
     'navtree' : 1,
     'navtree_start' : 1}

to create a sitemap with depth limit 3, rooted in the portal:

    {'query' : '/'.join(obj.getPhysicalPath()),
     'depth' : 3}

The parameters:

- 'context' is the acquisition context, from which tools will be acquired
- 'obj' is the current object being displayed.
- 'query' is a catalog query to apply to find nodes in the tree.
- 'strategy' is an object that can affect how the generation works. It
    should be derived from NavtreeStrategyBase, if given, and contain:

        rootPath -- a string property; the physical path to the root node.

        If not given, it will default to any path set in the query, or the
        portal root. Note that in a navtree query, the root path will
        default to the portal only, possibly adjusted for any navtree_start
        set. If rootPath points to something not returned by the query by
        the query, a dummy node containing only an empty 'children' list
        will be returned.

        showAllParents -- a boolean property; if true and obj is given,
            ensure that all parents of the object, including any that would
            normally be filtered out are included in the tree.

        nodeFilter(node) -- a method returning a boolean; if this returns
            False, the given node will not be inserted in the tree

        subtreeFilter(node) -- a method returning a boolean; if this returns
            False, the given (folderish) node will not be expanded (its
            children will be pruned off)

        decoratorFactory(node) -- a method returning a dict; this can inject
            additional keys in a node being inserted.
            
        showChildrenOf(object) -- a method returning True if children of
            the given object (normally the root) should be returned

Returns tree where each node is represented by a dict:

    item            -   A catalog brain of this item
    depth           -   The depth of this item, relative to the startAt level
    currentItem     -   True if this is the current item
    currentParent   -   True if this is a direct parent of the current item
    children        -   A list of children nodes of this node

Note: Any 'decoratorFactory' specified may modify this list, but
the 'children' property is guaranteed to be there.

Note: If the query does not return the root node itself, the root
element of the tree may contain *only* the 'children' list.

Note: Folder default-pages are not included in the returned result.
If the 'obj' passed in is a default-page, its parent folder will be
used for the purposes of selecting the 'currentItem'.

Definition at line 36 of file navtree.py.

00036 
00037 def buildFolderTree(context, obj=None, query={}, strategy=NavtreeStrategyBase()):
00038     """Create a tree structure representing a navigation tree. By default,
00039     it will create a full "sitemap" tree, rooted at the portal, ordered
00040     by explicit folder order. If the 'query' parameter contains a 'path'
00041     key, this can be used to override this. To create a navtree rooted
00042     at the portal root, set query['path'] to:
00043 
00044         {'query' : '/'.join(context.getPhysicalPath()),
00045          'navtree' : 1}
00046 
00047     to start this 1 level below the portal root, set query['path'] to:
00048 
00049         {'query' : '/'.join(obj.getPhysicalPath()),
00050          'navtree' : 1,
00051          'navtree_start' : 1}
00052 
00053     to create a sitemap with depth limit 3, rooted in the portal:
00054 
00055         {'query' : '/'.join(obj.getPhysicalPath()),
00056          'depth' : 3}
00057 
00058     The parameters:
00059 
00060     - 'context' is the acquisition context, from which tools will be acquired
00061     - 'obj' is the current object being displayed.
00062     - 'query' is a catalog query to apply to find nodes in the tree.
00063     - 'strategy' is an object that can affect how the generation works. It
00064         should be derived from NavtreeStrategyBase, if given, and contain:
00065 
00066             rootPath -- a string property; the physical path to the root node.
00067 
00068             If not given, it will default to any path set in the query, or the
00069             portal root. Note that in a navtree query, the root path will
00070             default to the portal only, possibly adjusted for any navtree_start
00071             set. If rootPath points to something not returned by the query by
00072             the query, a dummy node containing only an empty 'children' list
00073             will be returned.
00074 
00075             showAllParents -- a boolean property; if true and obj is given,
00076                 ensure that all parents of the object, including any that would
00077                 normally be filtered out are included in the tree.
00078 
00079             nodeFilter(node) -- a method returning a boolean; if this returns
00080                 False, the given node will not be inserted in the tree
00081 
00082             subtreeFilter(node) -- a method returning a boolean; if this returns
00083                 False, the given (folderish) node will not be expanded (its
00084                 children will be pruned off)
00085 
00086             decoratorFactory(node) -- a method returning a dict; this can inject
00087                 additional keys in a node being inserted.
00088                 
00089             showChildrenOf(object) -- a method returning True if children of
00090                 the given object (normally the root) should be returned
00091 
00092     Returns tree where each node is represented by a dict:
00093 
00094         item            -   A catalog brain of this item
00095         depth           -   The depth of this item, relative to the startAt level
00096         currentItem     -   True if this is the current item
00097         currentParent   -   True if this is a direct parent of the current item
00098         children        -   A list of children nodes of this node
00099 
00100     Note: Any 'decoratorFactory' specified may modify this list, but
00101     the 'children' property is guaranteed to be there.
00102 
00103     Note: If the query does not return the root node itself, the root
00104     element of the tree may contain *only* the 'children' list.
00105 
00106     Note: Folder default-pages are not included in the returned result.
00107     If the 'obj' passed in is a default-page, its parent folder will be
00108     used for the purposes of selecting the 'currentItem'.
00109     """
00110 
00111     portal_url = getToolByName(context, 'portal_url')
00112     portal_catalog = getToolByName(context, 'portal_catalog')
00113 
00114     showAllParents = strategy.showAllParents
00115     rootPath = strategy.rootPath
00116 
00117     request = getattr(context, 'REQUEST', {})
00118     
00119     # Find the object's path. Use parent folder if context is a default-page
00120 
00121     objPath = None
00122     objPhysicalPath = None
00123     if obj is not None:
00124         objPhysicalPath = obj.getPhysicalPath()
00125         if utils.isDefaultPage(obj, request):
00126             objPhysicalPath = objPhysicalPath[:-1]
00127         objPath = '/'.join(objPhysicalPath)
00128 
00129     portalPath = portal_url.getPortalPath()
00130     portalObject = portal_url.getPortalObject()
00131     
00132     # Calculate rootPath from the path query if not set.
00133 
00134     if 'path' not in query:
00135         if rootPath is None:
00136             rootPath = portalPath
00137         query['path'] = rootPath
00138     elif rootPath is None:
00139         pathQuery = query['path']
00140         if type(pathQuery) == StringType:
00141             rootPath = pathQuery
00142         else:
00143             # Adjust for the fact that in a 'navtree' query, the actual path
00144             # is the path of the current context
00145             if pathQuery.get('navtree', False):
00146                 navtreeLevel = pathQuery.get('navtree_start', 1)
00147                 if navtreeLevel > 1:
00148                     navtreeContextPath = pathQuery['query']
00149                     navtreeContextPathElements = navtreeContextPath[len(portalPath)+1:].split('/')
00150                     # Short-circuit if we won't be able to find this path
00151                     if len(navtreeContextPathElements) < (navtreeLevel - 1):
00152                         return {'children' : []}
00153                     rootPath = portalPath + '/' + '/'.join(navtreeContextPathElements[:navtreeLevel-1])
00154                 else:
00155                     rootPath = portalPath
00156             else:
00157                 rootPath = pathQuery['query']
00158 
00159     rootDepth = len(rootPath.split('/'))
00160 
00161     # Determine if we need to prune the root (but still force the path to)
00162     # the parent if necessary
00163     
00164     pruneRoot = False
00165     if strategy is not None:
00166         rootObject = portalObject.unrestrictedTraverse(rootPath, None)
00167         if rootObject is not None:
00168             pruneRoot = not strategy.showChildrenOf(rootObject)
00169 
00170     # Default sorting and threatment of default-pages
00171 
00172     if 'sort_on' not in query:
00173         query['sort_on'] = 'getObjPositionInParent'
00174 
00175     if 'is_default_page' not in query:
00176         query['is_default_page'] = False
00177 
00178     results = portal_catalog.searchResults(query)
00179 
00180     # We keep track of a dict of item path -> node, so that we can easily
00181     # find parents and attach children. If a child appears before its
00182     # parent, we stub the parent node.
00183 
00184     # This is necessary because whilst the sort_on parameter will ensure
00185     # that the objects in a folder are returned in the right order relative
00186     # to each other, we don't know the relative order of objects from
00187     # different folders. So, if /foo comes before /bar, and /foo/a comes
00188     # before /foo/b, we may get a list like (/bar/x, /foo/a, /foo/b, /foo,
00189     # /bar,).
00190 
00191     itemPaths = {}
00192     
00193     # Add an (initially empty) node for the root
00194     itemPaths[rootPath] = {'children' : []}
00195     
00196     # If we need to "prune" the parent (but still allow showAllParent to 
00197     # force some children), do so now
00198     if pruneRoot:
00199         itemPaths[rootPath]['_pruneSubtree'] = True
00200 
00201     def insertElement(itemPaths, item, forceInsert=False):
00202         """Insert the given 'item' brain into the tree, which is kept in
00203         'itemPaths'. If 'forceInsert' is True, ignore node- and subtree-
00204         filters, otherwise any node- or subtree-filter set will be allowed to
00205         block the insertion of a node.
00206         """
00207         itemPath = item.getPath()
00208         itemInserted = (itemPaths.get(itemPath, {}).get('item', None) is not None)
00209 
00210         # Short-circuit if we already added this item. Don't short-circuit
00211         # if we're forcing the insert, because we may have inserted but
00212         # later pruned off the node
00213         if not forceInsert and itemInserted:
00214             return
00215 
00216         itemPhysicalPath = itemPath.split('/')
00217         parentPath = '/'.join(itemPhysicalPath[:-1])
00218         parentPruned = (itemPaths.get(parentPath, {}).get('_pruneSubtree', False))
00219 
00220         # Short-circuit if we know we're pruning this item's parent
00221 
00222         # XXX: We could do this recursively, in case of parent of the
00223         # parent was being pruned, but this may not be a great trade-off
00224 
00225         # There is scope for more efficiency improvement here: If we knew we
00226         # were going to prune the subtree, we would short-circuit here each time.
00227         # In order to know that, we'd have to make sure we inserted each parent
00228         # before its children, by sorting the catalog result set (probably
00229         # manually) to get a breadth-first search.
00230 
00231         if not forceInsert and parentPruned:
00232             return
00233 
00234         isCurrent = isCurrentParent = False
00235         if objPath is not None:
00236             if objPath == itemPath:
00237                 isCurrent = True
00238             elif objPath.startswith(itemPath + '/') and len(objPhysicalPath) > len(itemPhysicalPath):
00239                 isCurrentParent = True
00240 
00241         relativeDepth = len(itemPhysicalPath) - rootDepth
00242 
00243         newNode = {'item'          : item,
00244                    'depth'         : relativeDepth,
00245                    'currentItem'   : isCurrent,
00246                    'currentParent' : isCurrentParent,}
00247 
00248         insert = True
00249         if not forceInsert and strategy is not None:
00250             insert = strategy.nodeFilter(newNode)
00251         if insert:
00252 
00253             if strategy is not None:
00254                 newNode = strategy.decoratorFactory(newNode)
00255 
00256             # Tell parent about this item, unless an earlier subtree filter
00257             # told us not to. If we're forcing the insert, ignore the
00258             # pruning, but avoid inserting the node twice
00259             if itemPaths.has_key(parentPath):
00260                 itemParent = itemPaths[parentPath]
00261                 if forceInsert:
00262                     nodeAlreadyInserted = False
00263                     for i in itemParent['children']:
00264                         if i['item'].getPath() == itemPath:
00265                             nodeAlreadyInserted = True
00266                             break
00267                     if not nodeAlreadyInserted:
00268                         itemParent['children'].append(newNode)
00269                 elif not itemParent.get('_pruneSubtree', False):
00270                     itemParent['children'].append(newNode)
00271             else:
00272                 itemPaths[parentPath] = {'children': [newNode]}
00273 
00274             # Ask the subtree filter (if any), if we should be expanding this node
00275             if strategy.showAllParents and isCurrentParent:
00276                 # If we will be expanding this later, we can't prune off children now
00277                 expand = True
00278             else:
00279                 expand = getattr(item, 'is_folderish', True)
00280             if expand and (not forceInsert and strategy is not None):
00281                 expand = strategy.subtreeFilter(newNode)
00282 
00283             children = newNode.setdefault('children',[])
00284             if expand:
00285                 # If we had some orphaned children for this node, attach
00286                 # them
00287                 if itemPaths.has_key(itemPath):
00288                     children.extend(itemPaths[itemPath]['children'])
00289             else:
00290                 newNode['_pruneSubtree'] = True
00291 
00292             itemPaths[itemPath] = newNode
00293 
00294     # Add the results of running the query
00295     for r in results:
00296         insertElement(itemPaths, r)
00297 
00298     # If needed, inject additional nodes for the direct parents of the
00299     # context. Note that we use an unrestricted query: things we don't normally
00300     # have permission to see will be included in the tree.
00301     if strategy.showAllParents and objPath is not None:
00302         objSubPathElements = objPath[len(rootPath)+1:].split('/')
00303         parentPaths = []
00304 
00305         haveNode = (itemPaths.get(rootPath, {}).get('item', None) is None)
00306         if not haveNode:
00307             parentPaths.append(rootPath)
00308 
00309         parentPath = rootPath
00310         for i in range(len(objSubPathElements)):
00311             nodePath = rootPath + '/' + '/'.join(objSubPathElements[:i+1])
00312             node = itemPaths.get(nodePath, None)
00313 
00314             # If we don't have this node, we'll have to get it, if we have it
00315             # but it wasn't connected, re-connect it
00316             if node is None or 'item' not in node:
00317                 parentPaths.append(nodePath)
00318             else:
00319                 nodeParent = itemPaths.get(parentPath, None)
00320                 if nodeParent is not None:
00321                     nodeAlreadyInserted = False
00322                     for i in nodeParent['children']:
00323                         if i['item'].getPath() == nodePath:
00324                             nodeAlreadyInserted = True
00325                             break
00326                     if not nodeAlreadyInserted:
00327                         nodeParent['children'].append(node)
00328 
00329             parentPath = nodePath
00330 
00331         # If we were outright missing some nodes, find them again
00332         if len(parentPaths) > 0:
00333             query = {'path' : {'query' : parentPaths, 'depth' : 0}}
00334             results = portal_catalog.unrestrictedSearchResults(query)
00335 
00336             for r in results:
00337                 insertElement(itemPaths, r, forceInsert=True)
00338 
00339     # Return the tree starting at rootPath as the root node.
00340     return itemPaths[rootPath]

Here is the call graph for this function:

Here is the caller graph for this function: