source: products/quintagroup.dropdownmenu/trunk/quintagroup/dropdownmenu/browser/viewlets.py

Last change on this file was 3707, checked in by kroman0, 11 years ago

pep8

  • Property svn:eol-style set to native
File size: 11.2 KB
RevLine 
[1194]1# -*- coding: utf-8 -*-
2from Acquisition import aq_inner
[1182]3
[3153]4from zope.component import getMultiAdapter
[1194]5
[1182]6from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
[1194]7from Products.CMFCore.utils import getToolByName
8from Products.CMFCore.interfaces import IAction, IActionCategory
9from Products.CMFCore.ActionInformation import ActionInfo
[2657]10from Products.CMFPlone.utils import versionTupleFromString
[1182]11
[1194]12from plone.memoize.instance import memoize
[3234]13from plone.memoize.compress import xhtmlslimmer
[1182]14from plone.app.layout.viewlets import common
[1197]15from plone.app.layout.navigation.navtree import buildFolderTree
16from plone.app.layout.navigation.interfaces import INavtreeStrategy
[1182]17
[1197]18from quintagroup.dropdownmenu.browser.menu import DropDownMenuQueryBuilder
[1319]19from quintagroup.dropdownmenu.util import getDropDownMenuSettings
[1182]20
[2775]21from time import time
22from plone.memoize import ram
23import copy
[1194]24
[3348]25
[3339]26def menu_cache_key(f, view):
27    # menu cache key conssits of:
[3461]28    # - path to selected item
[3347]29    # - site can be accessed on different domains
[3339]30    # - language is important for multilingua sites
[3235]31
[3239]32    portal_state = getMultiAdapter((view.context, view.request),
33                                   name=u'plone_portal_state')
34    site_len = len(portal_state.navigation_root_path().split('/'))
35    content_path = view.context.getPhysicalPath()[site_len:]
36    if view.conf.content_tabs_level > 0:
37        content_path = content_path[:view.conf.content_tabs_level]
[3339]38    path_key = view.request.physicalPathToURL(content_path)
39
40    language = portal_state.locale().getLocaleID()
41
42    # Cache for five minutes. Note that the HTTP RAM-cache
43    # typically purges entries after 60 minutes.
[3234]44    return view.__name__ + \
[3514]45        path_key + \
46        language + \
47        str(time() // (60 * 5))
[2775]48
[3347]49
50# we are caching the menu structure built out of portal_actions tool
[3339]51# this cache key does not take in account expressions and roles settings
[3234]52def tabs_cache_key(f, view, site_url):
[3702]53    portal_state = getMultiAdapter(
54        (view.context, view.request),
55        name=u'plone_portal_state',
56    )
57    language = portal_state.locale().getLocaleID()
58    return site_url + language + str(time() // (60 * 60))
[3234]59
[3347]60
[3239]61def dropdowncache(f):
62    def func(view):
[3339]63        portal_state = getMultiAdapter((view.context, view.request),
[3514]64                                       name=u'plone_portal_state')
[3347]65        # it is impossible to reliably cache entire rendered menu generated
[3339]66        # with potral actions strategy.
67        if not view.conf.enable_caching or view.conf.show_actions_tabs or \
[3514]68            (not portal_state.anonymous() and
[3339]69                view.conf.caching_strategy == 'anonymous'):
[3239]70            return f(view)
71        return ram.cache(menu_cache_key)(f)(view)
72    return func
[3234]73
[3239]74
[3347]75def tabscache(f):
76    def func(view, site_url):
77        if not view.conf.enable_caching:
78            return f(view, site_url)
79        return ram.cache(tabs_cache_key)(f)(view, site_url)
80    return func
81
82
[1182]83class GlobalSectionsViewlet(common.GlobalSectionsViewlet):
84    index = ViewPageTemplateFile('templates/sections.pt')
[1194]85    recurse = ViewPageTemplateFile('templates/sections_recurse.pt')
[3491]86    mobile = ViewPageTemplateFile('templates/sections_mobile.pt')
[1182]87
88    def update(self):
[1194]89        # we may need some previously defined variables
[3661]90        # super(GlobalSectionsViewlet, self).update()
[1194]91
92        # prepare to gather portal tabs
93        context = aq_inner(self.context)
[3236]94        self.conf = self._settings()
[1194]95        self.tool = getToolByName(context, 'portal_actions')
[3706]96        plone_portal_state = getMultiAdapter((self.context, self.request),
[3707]97                                             name="plone_portal_state")
[3706]98        self.site_url = plone_portal_state.navigation_root_url()
99        context_state = getMultiAdapter((self.context, self.request),
[3707]100                                        name="plone_context_state")
[3706]101        self.context_url = context_state.is_default_page() and \
[2768]102            '/'.join(self.context.absolute_url().split('/')[:-1]) or \
103            self.context.absolute_url()
[3151]104
105        self.cat_sufix = self.conf.nested_category_sufix or ''
[2772]106        self.cat_prefix = self.conf.nested_category_prefix or ''
[3234]107
108    def portal_tabs(self):
109        tabs = []
110
[1194]111        # fetch actions-based tabs?
[3234]112        if self.conf.show_actions_tabs:
[1194]113            tabs.extend(self._actions_tabs())
114
115        # fetch content structure-based tabs?
[3234]116        if self.conf.show_content_tabs:
[1194]117            # put content-based actions before content structure-based ones?
[3234]118            if self.conf.content_before_actions_tabs:
[1194]119                tabs = self._content_tabs() + tabs
120            else:
121                tabs.extend(self._content_tabs())
122
[3234]123        return tabs
[1194]124
125    def _actions_tabs(self):
126        """Return tree of tabs based on portal_actions tool configuration"""
127        conf = self.conf
128        tool = self.tool
[3514]129        url = self.context_url
130        starts = url.startswith
[1194]131
132        # check if we have required root actions category inside tool
133        if conf.actions_category not in tool.objectIds():
134            return []
[2775]135        listtabs = []
136        res, listtabs = self.prepare_tabs(self.site_url)
137        res = copy.deepcopy(res)
138        self.tabs = listtabs
[3151]139
[2775]140        # if there is no custom menu in portal tabs return
141        if not listtabs:
142            return []
[3151]143
[2774]144        current_item = -1
[2772]145        delta = 1000
[2775]146        for info in listtabs:
[3514]147            if starts(info['url']) and len(url) - len(info['url']) < delta:
[3151]148                delta = len(self.context_url) - len(info['url'])
149                current_item = listtabs.index(info)
150        self.id_chain = []
151
[3514]152        active = listtabs[current_item]['url'] == self.site_url
153        active = active and self.context_url == self.site_url
154        active = listtabs[current_item]['url'] != self.site_url or active
[3661]155        if current_item > -1 and current_item < len(listtabs) and active:
[3151]156            self.mark_active(listtabs[current_item]['id'],
157                             listtabs[current_item]['url'])
[2775]158        self._activate(res)
159        return res
[3151]160
[3347]161    @tabscache
[2775]162    def prepare_tabs(self, site_url):
[3151]163        def normalize_actions(category, object, level, parent_url=None):
[2775]164            """walk through the tabs dictionary and build list of all tabs"""
165            tabs = []
166            for info in self._actionInfos(category, object):
167                icon = info['icon'] and '<img src="%s" />' % info['icon'] or ''
168                children = []
169                bottomLevel = self.conf.actions_tabs_level
170                if bottomLevel < 1 or level < bottomLevel:
171                    # try to find out appropriate subcategory
172                    subcat_id = self.cat_prefix + info['id'] + self.cat_sufix
[3514]173                    in_category = subcat_id in category.objectIds()
174                    if subcat_id != info['id'] and in_category:
[2775]175                        subcat = category._getOb(subcat_id)
176                        if IActionCategory.providedBy(subcat):
[3151]177                            children = normalize_actions(subcat, object,
178                                                         level + 1,
179                                                         info['url'])
[2775]180
[3514]181                parent_id = category.getId()
182                parent_id = parent_id.replace(self.cat_prefix, '')
183                parent_id = parent_id.replace(self.cat_sufix, '')
[3151]184                tab = {'id': info['id'],
[3514]185                       'title': info['title'],
186                       'url': info['url'],
187                       'parent': (parent_id, parent_url)}
[2775]188                tabslist.append(tab)
[3151]189
190                tab = {'id': info['id'],
[2775]191                       'Title': info['title'],
192                       'Description': info['description'],
193                       'getURL': info['url'],
194                       'show_children': len(children) > 0,
195                       'children': children,
[3151]196                       'currentItem': False,
[2775]197                       'currentParent': False,
198                       'item_icon': {'html_tag': icon},
199                       'normalized_review_state': 'visible'}
200                tabs.append(tab)
[3151]201            return tabs
[2775]202        tabslist = []
[3151]203        tabs = normalize_actions(self.tool._getOb(self.conf.actions_category),
204                                 aq_inner(self.context), 0)
[2775]205        return tabs, tabslist
206
207    def _activate(self, res):
208        """Mark selected chain in the tabs dictionary"""
209        for info in res:
210            if info['getURL'] in self.id_chain:
211                info['currentItem'] = True
212                info['currentParent'] = True
213                if info['children']:
214                    self._activate(info['children'])
215
216    def mark_active(self, current_id, url):
[2772]217        for info in self.tabs:
[2775]218            if info['id'] == current_id and info['url'] == url:
[2773]219                self.mark_active(info['parent'][0], info['parent'][1])
[2775]220                self.id_chain.append(info['url'])
[1194]221
222    def _actionInfos(self, category, object, check_visibility=1,
223                     check_permissions=1, check_condition=1, max=-1):
224        """Return action infos for a given category"""
225        ec = self.tool._getExprContext(object)
226        actions = [ActionInfo(action, ec) for action in category.objectValues()
[3514]227                   if IAction.providedBy(action)]
[1194]228
229        action_infos = []
230        for ai in actions:
231            if check_visibility and not ai['visible']:
232                continue
233            if check_permissions and not ai['allowed']:
234                continue
235            if check_condition and not ai['available']:
236                continue
237            action_infos.append(ai)
238            if max + 1 and len(action_infos) >= max:
239                break
240        return action_infos
241
242    def _content_tabs(self):
243        """Return tree of tabs based on content structure"""
[1197]244        context = aq_inner(self.context)
[1194]245
[1197]246        queryBuilder = DropDownMenuQueryBuilder(context)
247        strategy = getMultiAdapter((context, None), INavtreeStrategy)
248        # XXX This works around a bug in plone.app.portlets which was
249        # fixed in http://dev.plone.org/svn/plone/changeset/18836
250        # When a release with that fix is made this workaround can be
251        # removed and the plone.app.portlets requirement in setup.py
252        # be updated.
253        if strategy.rootPath is not None and strategy.rootPath.endswith("/"):
254            strategy.rootPath = strategy.rootPath[:-1]
255
256        return buildFolderTree(context, obj=context, query=queryBuilder(),
257                               strategy=strategy).get('children', [])
258
[1194]259    @memoize
260    def _settings(self):
261        """Fetch dropdown menu settings registry"""
[1319]262        return getDropDownMenuSettings(self.context)
[1194]263
[3239]264    @dropdowncache
[3491]265    def createMenuMobile(self):
266        html = self.mobile(children=self.portal_tabs(), level=1)
[3628]267        return xhtmlslimmer.compress(html).strip(' \n')
[3491]268
269    @dropdowncache
[1194]270    def createMenu(self):
[3461]271        html = self.recurse(children=self.portal_tabs(), level=1)
[3628]272        return xhtmlslimmer.compress(html).strip(' \n')
[1194]273
[2658]274    @memoize
[2657]275    def is_plone_four(self):
276        """Indicates if we are in plone 4.
[3151]277
[2657]278        """
[3151]279        pm = getToolByName(aq_inner(self.context), 'portal_migration')
[2658]280        try:
281            version = versionTupleFromString(pm.getSoftwareVersion())
282        except AttributeError:
283            version = versionTupleFromString(pm.getFileSystemVersion())
284
[2657]285        if version:
286            return version[0] == 4
Note: See TracBrowser for help on using the repository browser.