Back to index

plone3  3.1.7
navigation.py
Go to the documentation of this file.
00001 from zope.interface import implements, Interface
00002 from zope.component import adapts, getMultiAdapter, queryUtility
00003 
00004 from plone.i18n.normalizer.interfaces import IIDNormalizer
00005 from plone.portlets.interfaces import IPortletDataProvider
00006 from plone.app.portlets.portlets import base
00007 
00008 from zope import schema
00009 from zope.formlib import form
00010 
00011 from plone.memoize.instance import memoize
00012 
00013 from Acquisition import aq_inner, aq_base, aq_parent
00014 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
00015 from Products.CMFCore.utils import getToolByName
00016 
00017 from Products.CMFPlone.interfaces import INonStructuralFolder, IBrowserDefault
00018 from Products.CMFPlone import utils
00019 from Products.CMFPlone import PloneMessageFactory as _
00020 
00021 from plone.app.layout.navigation.interfaces import INavtreeStrategy
00022 from plone.app.layout.navigation.interfaces import INavigationQueryBuilder
00023 
00024 from plone.app.layout.navigation.root import getNavigationRoot
00025 from plone.app.layout.navigation.navtree import buildFolderTree
00026 
00027 from plone.app.vocabularies.catalog import SearchableTextSourceBinder
00028 from plone.app.form.widgets.uberselectionwidget import UberSelectionWidget
00029 
00030 from Products.CMFPlone.browser.navtree import SitemapNavtreeStrategy
00031 
00032 class INavigationPortlet(IPortletDataProvider):
00033     """A portlet which can render the navigation tree
00034     """
00035     
00036     name = schema.TextLine(
00037             title=_(u"label_navigation_title", default=u"Title"),
00038             description=_(u"help_navigation_title",
00039                           default=u"The title of the navigation tree. Leave "
00040                                    "blank for the default, translated title."),
00041             default=u"",
00042             required=False)
00043     
00044     root = schema.Choice(
00045             title=_(u"label_navigation_root_path", default=u"Root node"),
00046             description=_(u'help_navigation_root',
00047                           default=u"You may search for and choose a folder "
00048                                     "to act as the root of the navigation tree. "
00049                                     "Leave blank to use the Plone site root."),
00050             required=False,
00051             source=SearchableTextSourceBinder({'is_folderish' : True},
00052                                               default_query='path:'))
00053                             
00054     includeTop = schema.Bool(
00055             title=_(u"label_include_top_node", default=u"Include top node"),
00056             description=_(u"help_include_top_node",
00057                           default=u"Whether or not to show the top, or 'root', "
00058                                    "node in the navigation tree. This is affected " 
00059                                    "by the 'Start level' setting."),
00060             default=False,
00061             required=False)
00062             
00063     currentFolderOnly = schema.Bool(
00064             title=_(u"label_current_folder_only",
00065                     default=u"Only show the contents of the current folder."),
00066             description=_(u"help_current_folder_only",
00067                           default=u"If selected, the navigation tree will "
00068                                    "only show the current folder and its "
00069                                    "children at all times."),
00070             default=False,
00071             required=False)
00072     
00073     topLevel = schema.Int(
00074             title=_(u"label_navigation_startlevel", default=u"Start level"),
00075             description=_(u"help_navigation_start_level",
00076                 default=u"An integer value that specifies the number of folder "
00077                          "levels below the site root that must be exceeded "
00078                          "before the navigation tree will display. 0 means "
00079                          "that the navigation tree should be displayed "
00080                          "everywhere including pages in the root of the site. "
00081                          "1 means the tree only shows up inside folders "
00082                          "located in the root and downwards, never showing "
00083                          "at the top level."),
00084             default=1,
00085             required=False)
00086     
00087     bottomLevel = schema.Int(
00088             title=_(u"label_navigation_tree_depth",
00089                     default=u"Navigation tree depth"),
00090             description=_(u"help_navigation_tree_depth",
00091                           default=u"How many folders should be included "
00092                                    "before the navigation tree stops. 0 "
00093                                    "means no limit. 1 only includes the "
00094                                    "root folder."),
00095             default=0,
00096             required=False)
00097 
00098 class Assignment(base.Assignment):
00099     implements(INavigationPortlet)
00100 
00101     title = _(u'Navigation')
00102     
00103     name = u""
00104     root = None
00105     currentFolderOnly = False
00106     includeTop = False
00107     topLevel = 1
00108     bottomLevel = 0
00109     
00110     def __init__(self, name=u"", root=None, currentFolderOnly=False, includeTop=False, topLevel=1, bottomLevel=0):
00111         self.name = name
00112         self.root = root
00113         self.currentFolderOnly = currentFolderOnly
00114         self.includeTop = includeTop
00115         self.topLevel = topLevel
00116         self.bottomLevel = bottomLevel
00117 
00118 class Renderer(base.Renderer):
00119 
00120     def __init__(self, context, request, view, manager, data):
00121         base.Renderer.__init__(self, context, request, view, manager, data)
00122         
00123         self.properties = getToolByName(context, 'portal_properties').navtree_properties
00124         self.urltool = getToolByName(context, 'portal_url')
00125 
00126     def title(self):
00127         return self.data.name or self.properties.name
00128 
00129     @property
00130     def available(self):
00131         tree = self.getNavTree()
00132         root = self.getNavRoot()
00133         return (root is not None and len(tree['children']) > 0)
00134 
00135     def include_top(self):
00136         return getattr(self.data, 'includeTop', self.properties.includeTop)
00137 
00138     def navigation_root(self):
00139         return self.getNavRoot()
00140 
00141     def root_type_name(self):
00142         root = self.getNavRoot()
00143         return queryUtility(IIDNormalizer).normalize(root.portal_type)
00144 
00145     def root_item_class(self):
00146         context = aq_inner(self.context)
00147         root = self.getNavRoot()
00148         if (aq_base(root) is aq_base(context) or
00149                 (aq_base(root) is aq_base(aq_parent(aq_inner(context))) and
00150                 utils.isDefaultPage(context, self.request, context))):
00151             return 'navTreeCurrentItem'
00152         else:
00153             return ''
00154             
00155     def root_icon(self):
00156         ploneview = getMultiAdapter((self.context, self.request), name=u'plone')
00157         icon = ploneview.getIcon(self.getNavRoot())
00158         return icon.url
00159             
00160     def root_is_portal(self):
00161         root = self.getNavRoot()
00162         return aq_base(root) is aq_base(self.urltool.getPortalObject())
00163 
00164     def createNavTree(self):
00165         data = self.getNavTree()
00166         
00167         bottomLevel = self.data.bottomLevel or self.properties.getProperty('bottomLevel', 0)
00168 
00169         return self.recurse(children=data.get('children', []), level=1, bottomLevel=bottomLevel)
00170 
00171     # Cached lookups
00172 
00173     @memoize
00174     def getNavRoot(self, _marker=[]):
00175         portal = self.urltool.getPortalObject()
00176 
00177         currentFolderOnly = self.data.currentFolderOnly or self.properties.getProperty('currentFolderOnlyInNavtree', False)
00178         topLevel = self.data.topLevel or self.properties.getProperty('topLevel', 0)
00179         
00180         rootPath = getRootPath(self.context, currentFolderOnly, topLevel, str(self.data.root))
00181         
00182         if rootPath == self.urltool.getPortalPath():
00183             return portal
00184         else:
00185             try:
00186                 return portal.unrestrictedTraverse(rootPath)
00187             except (AttributeError, KeyError,):
00188                 return portal
00189 
00190     @memoize
00191     def getNavTree(self, _marker=[]):
00192         context = aq_inner(self.context)
00193         
00194         # Special case - if the root is supposed to be pruned, we need to
00195         # abort here
00196 
00197         queryBuilder = getMultiAdapter((context, self.data), INavigationQueryBuilder)
00198         strategy = getMultiAdapter((context, self.data), INavtreeStrategy)
00199 
00200         return buildFolderTree(context, obj=context, query=queryBuilder(), strategy=strategy)
00201 
00202     def update(self):
00203         pass
00204 
00205     def render(self):
00206         return self._template()
00207 
00208     _template = ViewPageTemplateFile('navigation.pt')
00209     recurse = ViewPageTemplateFile('navigation_recurse.pt')
00210 
00211 class AddForm(base.AddForm):
00212     form_fields = form.Fields(INavigationPortlet)
00213     form_fields['root'].custom_widget = UberSelectionWidget
00214     label = _(u"Add Navigation Portlet")
00215     description = _(u"This portlet display a navigation tree.")
00216 
00217     def create(self, data):
00218         return Assignment(name=data.get('name', u""),
00219                           root=data.get('root', u""),
00220                           currentFolderOnly=data.get('currentFolderOnly', False),
00221                           includeTop=data.get('includeTop', False),
00222                           topLevel=data.get('topLevel', 0),
00223                           bottomLevel=data.get('bottomLevel', 0))
00224 
00225 class EditForm(base.EditForm):
00226     form_fields = form.Fields(INavigationPortlet)
00227     form_fields['root'].custom_widget = UberSelectionWidget
00228     label = _(u"Edit Navigation Portlet")
00229     description = _(u"This portlet display a navigation tree.")
00230     
00231 class QueryBuilder(object):
00232     """Build a navtree query based on the settings in navtree_properties
00233     and those set on the portlet.
00234     """
00235     implements(INavigationQueryBuilder)
00236     adapts(Interface, INavigationPortlet)
00237 
00238     def __init__(self, context, portlet):
00239         self.context = context
00240         self.portlet = portlet
00241         
00242         portal_properties = getToolByName(context, 'portal_properties')
00243         navtree_properties = getattr(portal_properties, 'navtree_properties')
00244         
00245         portal_url = getToolByName(context, 'portal_url')
00246         
00247         # Acquire a custom nav query if available
00248         customQuery = getattr(context, 'getCustomNavQuery', None)
00249         if customQuery is not None and utils.safe_callable(customQuery):
00250             query = customQuery()
00251         else:
00252             query = {}
00253 
00254         # Construct the path query
00255 
00256         rootPath = getNavigationRoot(context, relativeRoot=portlet.root)
00257         currentPath = '/'.join(context.getPhysicalPath())
00258 
00259         # If we are above the navigation root, a navtree query would return
00260         # nothing (since we explicitly start from the root always). Hence,
00261         # use a regular depth-1 query in this case.
00262 
00263         if not currentPath.startswith(rootPath):
00264             query['path'] = {'query' : rootPath, 'depth' : 1}
00265         else:
00266             query['path'] = {'query' : currentPath, 'navtree' : 1}
00267 
00268         topLevel = portlet.topLevel or navtree_properties.getProperty('topLevel', 0)
00269         if topLevel and topLevel > 0:
00270              query['path']['navtree_start'] = topLevel + 1
00271 
00272         # XXX: It'd make sense to use 'depth' for bottomLevel, but it doesn't
00273         # seem to work with EPI.
00274 
00275         # Only list the applicable types
00276         query['portal_type'] = utils.typesToList(context)
00277 
00278         # Apply the desired sort
00279         sortAttribute = navtree_properties.getProperty('sortAttribute', None)
00280         if sortAttribute is not None:
00281             query['sort_on'] = sortAttribute
00282             sortOrder = navtree_properties.getProperty('sortOrder', None)
00283             if sortOrder is not None:
00284                 query['sort_order'] = sortOrder
00285 
00286         # Filter on workflow states, if enabled
00287         if navtree_properties.getProperty('enable_wf_state_filtering', False):
00288             query['review_state'] = navtree_properties.getProperty('wf_states_to_show', ())
00289 
00290         self.query = query
00291 
00292     def __call__(self):
00293         return self.query
00294         
00295 class NavtreeStrategy(SitemapNavtreeStrategy):
00296     """The navtree strategy used for the default navigation portlet
00297     """
00298     implements(INavtreeStrategy)
00299     adapts(Interface, INavigationPortlet)
00300 
00301     def __init__(self, context, portlet):
00302         SitemapNavtreeStrategy.__init__(self, context, portlet)
00303         portal_properties = getToolByName(context, 'portal_properties')
00304         navtree_properties = getattr(portal_properties, 'navtree_properties')
00305         
00306         # XXX: We can't do this with a 'depth' query to EPI...
00307         self.bottomLevel = portlet.bottomLevel or navtree_properties.getProperty('bottomLevel', 0)
00308 
00309         currentFolderOnly = portlet.currentFolderOnly or navtree_properties.getProperty('currentFolderOnlyInNavtree', False)
00310         topLevel = portlet.topLevel or navtree_properties.getProperty('topLevel', 0)
00311         self.rootPath = getRootPath(context, currentFolderOnly, topLevel, portlet.root)
00312 
00313     def subtreeFilter(self, node):
00314         sitemapDecision = SitemapNavtreeStrategy.subtreeFilter(self, node)
00315         if sitemapDecision == False:
00316             return False
00317         depth = node.get('depth', 0)
00318         if depth > 0 and self.bottomLevel > 0 and depth >= self.bottomLevel:
00319             return False
00320         else:
00321             return True
00322             
00323 def getRootPath(context, currentFolderOnly, topLevel, root):
00324     """Helper function to calculate the real root path
00325     """
00326     context = aq_inner(context)
00327     if currentFolderOnly:
00328         folderish = getattr(aq_base(context), 'isPrincipiaFolderish', False) and not INonStructuralFolder.providedBy(context)
00329         parent = aq_parent(context)
00330         
00331         is_default_page = False
00332         browser_default = IBrowserDefault(parent, None)
00333         if browser_default is not None:
00334             is_default_page = (browser_default.getDefaultPage() == context.getId())
00335         
00336         if not folderish or is_default_page:
00337             return '/'.join(parent.getPhysicalPath())
00338         else:
00339             return '/'.join(context.getPhysicalPath())
00340 
00341     rootPath = getNavigationRoot(context, relativeRoot=root)
00342 
00343     # Adjust for topLevel
00344     if topLevel > 0:
00345         contextPath = '/'.join(context.getPhysicalPath())
00346         if not contextPath.startswith(rootPath):
00347             return None
00348         contextSubPathElements = contextPath[len(rootPath)+1:]
00349         if contextSubPathElements:
00350             contextSubPathElements = contextSubPathElements.split('/')
00351             if len(contextSubPathElements) < topLevel:
00352                 return None
00353             rootPath = rootPath + '/' + '/'.join(contextSubPathElements[:topLevel])
00354         else:
00355             return None
00356     
00357     return rootPath