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
Line 
1# -*- coding: utf-8 -*-
2from Acquisition import aq_inner
3
4from zope.component import getMultiAdapter
5
6from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
7from Products.CMFCore.utils import getToolByName
8from Products.CMFCore.interfaces import IAction, IActionCategory
9from Products.CMFCore.ActionInformation import ActionInfo
10from Products.CMFPlone.utils import versionTupleFromString
11
12from plone.memoize.instance import memoize
13from plone.memoize.compress import xhtmlslimmer
14from plone.app.layout.viewlets import common
15from plone.app.layout.navigation.navtree import buildFolderTree
16from plone.app.layout.navigation.interfaces import INavtreeStrategy
17
18from quintagroup.dropdownmenu.browser.menu import DropDownMenuQueryBuilder
19from quintagroup.dropdownmenu.util import getDropDownMenuSettings
20
21from time import time
22from plone.memoize import ram
23import copy
24
25
26def menu_cache_key(f, view):
27    # menu cache key conssits of:
28    # - path to selected item
29    # - site can be accessed on different domains
30    # - language is important for multilingua sites
31
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]
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.
44    return view.__name__ + \
45        path_key + \
46        language + \
47        str(time() // (60 * 5))
48
49
50# we are caching the menu structure built out of portal_actions tool
51# this cache key does not take in account expressions and roles settings
52def tabs_cache_key(f, view, site_url):
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))
59
60
61def dropdowncache(f):
62    def func(view):
63        portal_state = getMultiAdapter((view.context, view.request),
64                                       name=u'plone_portal_state')
65        # it is impossible to reliably cache entire rendered menu generated
66        # with potral actions strategy.
67        if not view.conf.enable_caching or view.conf.show_actions_tabs or \
68            (not portal_state.anonymous() and
69                view.conf.caching_strategy == 'anonymous'):
70            return f(view)
71        return ram.cache(menu_cache_key)(f)(view)
72    return func
73
74
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
83class GlobalSectionsViewlet(common.GlobalSectionsViewlet):
84    index = ViewPageTemplateFile('templates/sections.pt')
85    recurse = ViewPageTemplateFile('templates/sections_recurse.pt')
86    mobile = ViewPageTemplateFile('templates/sections_mobile.pt')
87
88    def update(self):
89        # we may need some previously defined variables
90        # super(GlobalSectionsViewlet, self).update()
91
92        # prepare to gather portal tabs
93        context = aq_inner(self.context)
94        self.conf = self._settings()
95        self.tool = getToolByName(context, 'portal_actions')
96        plone_portal_state = getMultiAdapter((self.context, self.request),
97                                             name="plone_portal_state")
98        self.site_url = plone_portal_state.navigation_root_url()
99        context_state = getMultiAdapter((self.context, self.request),
100                                        name="plone_context_state")
101        self.context_url = context_state.is_default_page() and \
102            '/'.join(self.context.absolute_url().split('/')[:-1]) or \
103            self.context.absolute_url()
104
105        self.cat_sufix = self.conf.nested_category_sufix or ''
106        self.cat_prefix = self.conf.nested_category_prefix or ''
107
108    def portal_tabs(self):
109        tabs = []
110
111        # fetch actions-based tabs?
112        if self.conf.show_actions_tabs:
113            tabs.extend(self._actions_tabs())
114
115        # fetch content structure-based tabs?
116        if self.conf.show_content_tabs:
117            # put content-based actions before content structure-based ones?
118            if self.conf.content_before_actions_tabs:
119                tabs = self._content_tabs() + tabs
120            else:
121                tabs.extend(self._content_tabs())
122
123        return tabs
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
129        url = self.context_url
130        starts = url.startswith
131
132        # check if we have required root actions category inside tool
133        if conf.actions_category not in tool.objectIds():
134            return []
135        listtabs = []
136        res, listtabs = self.prepare_tabs(self.site_url)
137        res = copy.deepcopy(res)
138        self.tabs = listtabs
139
140        # if there is no custom menu in portal tabs return
141        if not listtabs:
142            return []
143
144        current_item = -1
145        delta = 1000
146        for info in listtabs:
147            if starts(info['url']) and len(url) - len(info['url']) < delta:
148                delta = len(self.context_url) - len(info['url'])
149                current_item = listtabs.index(info)
150        self.id_chain = []
151
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
155        if current_item > -1 and current_item < len(listtabs) and active:
156            self.mark_active(listtabs[current_item]['id'],
157                             listtabs[current_item]['url'])
158        self._activate(res)
159        return res
160
161    @tabscache
162    def prepare_tabs(self, site_url):
163        def normalize_actions(category, object, level, parent_url=None):
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
173                    in_category = subcat_id in category.objectIds()
174                    if subcat_id != info['id'] and in_category:
175                        subcat = category._getOb(subcat_id)
176                        if IActionCategory.providedBy(subcat):
177                            children = normalize_actions(subcat, object,
178                                                         level + 1,
179                                                         info['url'])
180
181                parent_id = category.getId()
182                parent_id = parent_id.replace(self.cat_prefix, '')
183                parent_id = parent_id.replace(self.cat_sufix, '')
184                tab = {'id': info['id'],
185                       'title': info['title'],
186                       'url': info['url'],
187                       'parent': (parent_id, parent_url)}
188                tabslist.append(tab)
189
190                tab = {'id': info['id'],
191                       'Title': info['title'],
192                       'Description': info['description'],
193                       'getURL': info['url'],
194                       'show_children': len(children) > 0,
195                       'children': children,
196                       'currentItem': False,
197                       'currentParent': False,
198                       'item_icon': {'html_tag': icon},
199                       'normalized_review_state': 'visible'}
200                tabs.append(tab)
201            return tabs
202        tabslist = []
203        tabs = normalize_actions(self.tool._getOb(self.conf.actions_category),
204                                 aq_inner(self.context), 0)
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):
217        for info in self.tabs:
218            if info['id'] == current_id and info['url'] == url:
219                self.mark_active(info['parent'][0], info['parent'][1])
220                self.id_chain.append(info['url'])
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()
227                   if IAction.providedBy(action)]
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"""
244        context = aq_inner(self.context)
245
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
259    @memoize
260    def _settings(self):
261        """Fetch dropdown menu settings registry"""
262        return getDropDownMenuSettings(self.context)
263
264    @dropdowncache
265    def createMenuMobile(self):
266        html = self.mobile(children=self.portal_tabs(), level=1)
267        return xhtmlslimmer.compress(html).strip(' \n')
268
269    @dropdowncache
270    def createMenu(self):
271        html = self.recurse(children=self.portal_tabs(), level=1)
272        return xhtmlslimmer.compress(html).strip(' \n')
273
274    @memoize
275    def is_plone_four(self):
276        """Indicates if we are in plone 4.
277
278        """
279        pm = getToolByName(aq_inner(self.context), 'portal_migration')
280        try:
281            version = versionTupleFromString(pm.getSoftwareVersion())
282        except AttributeError:
283            version = versionTupleFromString(pm.getFileSystemVersion())
284
285        if version:
286            return version[0] == 4
Note: See TracBrowser for help on using the repository browser.