source: products/qPloneTabs/branches/quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py @ 138

Last change on this file since 138 was 138, checked in by chervol, 18 years ago

config constants pathced

  • Property svn:eol-style set to native
File size: 26.6 KB
Line 
1import copy, sys
2from Acquisition import aq_inner
3from OFS.CopySupport import CopyError
4
5from zope.interface import implements
6from zope.component import getUtility, getMultiAdapter
7from zope.i18n import translate
8from zope.schema.interfaces import IVocabularyFactory
9from zope.exceptions import UserError
10from zope.app.container.interfaces import INameChooser
11
12from Products.CMFCore.utils import getToolByName
13from Products.CMFCore.interfaces import IAction, IActionCategory
14from Products.CMFCore.ActionInformation import Action, ActionCategory
15from Products.CMFCore.Expression import Expression
16from Products.CMFPlone import PloneMessageFactory as _
17from Products.CMFPlone import utils
18from Products.CMFPlone.browser.navigation import get_view_url
19from Products.Five.browser import BrowserView
20from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
21from Products.statusmessages.interfaces import IStatusMessage
22
23from plone.app.layout.navigation.root import getNavigationRoot
24from plone.app.kss.plonekssview import PloneKSSView
25from plone.app.workflow.remap import remap_workflow
26from plone.memoize.instance import memoize
27from kss.core import kssaction, KSSExplicitError
28
29from quintagroup.plonetabs.config import *
30from interfaces import IPloneTabsControlPanel
31
32ACTION_ATTRS = ["id", "title", "url_expr", "available_expr", "visible"]
33
34class PloneTabsControlPanel(PloneKSSView):
35   
36    implements(IPloneTabsControlPanel)
37   
38    template = ViewPageTemplateFile("templates/plonetabs.pt")
39    actionslist_template = ViewPageTemplateFile("templates/actionslist.pt")
40    autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt")
41   
42    # custom templates used to update page sections
43    sections_template = ViewPageTemplateFile("templates/sections.pt")
44   
45    def __call__(self):
46        """ Perform the update and redirect if necessary, or render the page """
47        postback = True
48        errors = {}
49        context = aq_inner(self.context)
50       
51        form = self.request.form
52        action = form.get("action", "")
53        submitted = form.get('form.submitted', False)
54       
55        # action handler def handler(self, form)
56        if submitted:
57            if form.has_key('add.add'):
58                postback = self.manage_addAction(form, errors)
59            elif form.has_key("edit.save"):
60                postback = self.manage_editAction(form, errors)
61            elif form.has_key("edit.delete"):
62                postback = self.manage_deleteAction(form, errors)
63            elif form.has_key("edit.moveup"):
64                postback = self.manage_moveUpAction(form, errors)
65            elif form.has_key("edit.movedown"):
66                postback = self.manage_moveDownAction(form, errors)
67            elif form.has_key("autogenerated.save"):
68                postback = self.manage_setAutogeneration(form, errors)
69            else:
70                postback = True
71       
72        if postback:
73            return self.template(errors=errors)
74   
75    ########################################
76    # Methods for processing configlet posts
77    ########################################
78   
79    def manage_setAutogeneration(self, form, errors):
80        """ Process managing autogeneration settings """
81       
82        # set excludeFromNav property for root objects
83        portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal()
84        generated_tabs = form.get("generated_tabs", '0')
85        nonfolderish_tabs = form.get("nonfolderish_tabs", '0')
86       
87        for item in self.getRootTabs():
88            obj = getattr(portal, item['id'], None)
89            if obj is not None:
90                checked = form.get(item['id'], None)
91                if checked == '1':
92                    obj.update(excludeFromNav=False)
93                else:
94                    obj.update(excludeFromNav=True)
95
96        # set disable_folder_sections property
97        changeProperties = getToolByName(self.context, "portal_properties").site_properties.manage_changeProperties
98       
99        if int(generated_tabs) == 1:
100            changeProperties(disable_folder_sections=False)
101        else:
102            changeProperties(disable_folder_sections=True)
103       
104        # set disable_nonfolderish_sections property
105        if int(nonfolderish_tabs) == 1:
106            changeProperties(disable_nonfolderish_sections=False)
107        else:
108            changeProperties(disable_nonfolderish_sections=True)
109       
110        IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"), type="info")
111       
112        self.redirect()
113       
114        return False
115   
116    def manage_addAction(self, form, errs):
117        """ Manage method to add a new action to given category,
118            if category doesn't exist, create it """
119        cat_name = form['category']
120       
121        # validate posted data
122        errors = self.validateActionFields(cat_name, form)
123       
124        # if not errors find (or create) category and set action to it
125        if not errors:
126            action = self.addAction(cat_name, form)
127            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action successfully added." % action.id), type="info")
128            self.redirect(search="category=%s" % cat_name)
129            return False
130        else:
131            errs.update(errors)
132            IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error")
133            return True
134   
135    def manage_editAction(self, form, errs):
136        """ Manage Method to update action """
137        # extract posted data
138        id, cat_name, data = self.parseForm(form)
139       
140        # get category and action to edit
141        category = self.getActionCategory(cat_name)
142        action = category[id]
143       
144        # validate posted data
145        errors = self.validateActionFields(cat_name, data, allow_dup=True)
146       
147        if not errors:
148            action = self.updateAction(id, cat_name, data)
149            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action saved." % action.id), type="info")
150            self.redirect(search="category=%s" % cat_name)
151            return False
152        else:
153            errs.update(self.processErrors(errors, sufix='_%s' % id)) # add edit form sufix to error ids
154            IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error")
155            return True
156   
157    def manage_deleteAction(self, form, errs):
158        """ Manage Method to delete action """
159        # extract posted data
160        id, cat_name, data = self.parseForm(form)
161       
162        # get category and action to delete
163        category = self.getActionCategory(cat_name)
164        if id in category.objectIds():
165            self.deleteAction(id, cat_name)
166            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action in '%s' category deleted." % (id, cat_name)), type="info")
167            self.redirect(search="category=%s" % cat_name)
168            return False
169        else:
170            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
171            return True
172   
173    def manage_moveUpAction(self, form, errors):
174        """ Move up given action by one position """
175        portal_actions = getToolByName(self.context, "portal_actions")
176       
177        id = form.get("orig_id", "")
178        category = form.get("category", "")
179       
180        if category not in portal_actions.objectIds():
181            IStatusMessage(self.request).addStatusMessage(_(u"Unexistent root portal actions category '%s'" % category), type="error")
182        else:
183            cat_container = portal_actions[category]
184            if id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())):
185                IStatusMessage(self.request).addStatusMessage(_(u"'%s' action does not exist in '%s' category" % (id, category)), type="error")
186            else:
187                cat_container.moveObjectsUp([id,], 1)
188                IStatusMessage(self.request).addStatusMessage(_(u"'%s' action in '%s' category moved up" % (id, category)), type="info")
189               
190                # redirect to form after successfull deletion
191                self.redirect(search="category=%s" % category)
192                return False
193       
194        # return form with errors
195        return True
196   
197    def manage_moveDownAction(self, form, errors):
198        """ Move up given action by one position """
199        portal_actions = getToolByName(self.context, "portal_actions")
200       
201        id = form.get("orig_id", "")
202        category = form.get("category", "")
203       
204        if category not in portal_actions.objectIds():
205            IStatusMessage(self.request).addStatusMessage(_(u"Unexistent root portal actions category '%s'" % category), type="error")
206        else:
207            cat_container = portal_actions[category]
208            if id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())):
209                IStatusMessage(self.request).addStatusMessage(_(u"'%s' action does not exist in '%s' category" % (id, category)), type="error")
210            else:
211                cat_container.moveObjectsDown([id,], 1)
212                IStatusMessage(self.request).addStatusMessage(_(u"'%s' action in '%s' category moved down" % (id, category)), type="info")
213               
214                # redirect to form after successfull deletion
215                self.redirect(search="category=%s" % category)
216                return False
217       
218        # return form with errors
219        return True
220   
221    def redirect(self, url="", search="", hash=""):
222        """ Redirect to @@plonetabs-controlpanel configlet """
223       
224        if url == "":
225            portal_url =  getMultiAdapter((self.context, self.request), name=u"plone_portal_state").portal_url()
226            url = "%s/%s" % (portal_url, "@@plonetabs-controlpanel")
227       
228        if search != "":
229            search = "?%s" % search
230       
231        if hash != "":
232            hash = "#%s" % hash
233       
234        self.request.response.redirect("%s%s%s" % (url, search, hash))
235   
236    ###################################
237    #
238    #  Methods - providers for templates
239    #
240    ###################################
241   
242    def getPageTitle(self, category="portal_tabs"):
243        """ See interface """
244        portal_props = getToolByName(self.context, "portal_properties")
245        default_title = "Plone '%s' Configuration" % category
246       
247        if not hasattr(portal_props, PROPERTY_SHEET):
248            return default_title
249       
250        sheet = getattr(portal_props, PROPERTY_SHEET)
251        if not hasattr(sheet, FIELD_NAME):
252            return default_title
253       
254        field = sheet.getProperty(FIELD_NAME)
255        dict = {}
256        for line in field:
257            cat, title = line.split("|", 2)
258            dict[cat] = title
259       
260        return dict.get(category, None) or default_title
261   
262    def hasActions(self, category="portal_tabs"):
263        """ See interface """
264        return len(getToolByName(self.context, "portal_actions").listActions(categories=[category,])) > 0
265   
266    def getPortalActions(self, category="portal_tabs"):
267        """ See interface """
268        portal_actions = getToolByName(self.context, "portal_actions")
269       
270        if category not in portal_actions.objectIds():
271            return []
272       
273        actions = []
274        for item in portal_actions[category].objectValues():
275            if IAction.providedBy(item):
276                actions.append(item)
277       
278        return actions
279   
280    def isGeneratedTabs(self):
281        """ See interface """
282        site_properties = getToolByName(self.context, "portal_properties").site_properties
283        return not site_properties.getProperty("disable_folder_sections", False)
284   
285    def isNotFoldersGenerated(self):
286        """ See interface """
287        site_properties = getToolByName(self.context, "portal_properties").site_properties
288        return not site_properties.getProperty("disable_nonfolderish_sections", False)
289   
290    def getActionsList(self, category="portal_tabs", errors={}):
291        """ See interface """
292        return self.actionslist_template(category=category, errors=errors)
293   
294    def getGeneratedTabs(self):
295        """ See interface """
296        return self.autogenerated_template()
297   
298    def getRootTabs(self):
299        """ See interface """
300        context = aq_inner(self.context)
301       
302        portal_catalog = getToolByName(context, 'portal_catalog')
303        portal_properties = getToolByName(context, 'portal_properties')
304        navtree_properties = getattr(portal_properties, 'navtree_properties')
305       
306        # Build result dict
307        result = []
308       
309        # check whether we only want actions
310        if not self.isGeneratedTabs():
311            return result
312       
313        query = {}
314       
315        rootPath = getNavigationRoot(context)
316        query['path'] = {'query' : rootPath, 'depth' : 1}
317        query['portal_type'] = utils.typesToList(context)
318       
319        sortAttribute = navtree_properties.getProperty('sortAttribute', None)
320        if sortAttribute is not None:
321            query['sort_on'] = sortAttribute
322           
323            sortOrder = navtree_properties.getProperty('sortOrder', None)
324            if sortOrder is not None:
325                query['sort_order'] = sortOrder
326       
327        if navtree_properties.getProperty('enable_wf_state_filtering', False):
328            query['review_state'] = navtree_properties.getProperty('wf_states_to_show', [])
329       
330        query['is_default_page'] = False
331
332        if not self.isNotFoldersGenerated():
333            query['is_folderish'] = True
334
335        # Get ids not to list and make a dict to make the search fast
336        idsNotToList = navtree_properties.getProperty('idsNotToList', ())
337        excludedIds = {}
338        for id in idsNotToList:
339            excludedIds[id]=1
340
341        rawresult = portal_catalog.searchResults(**query)
342
343        # now add the content to results
344        for item in rawresult:
345            if not excludedIds.has_key(item.getId):
346                id, item_url = get_view_url(item)
347                data = {'name'       : utils.pretty_title_or_id(context, item),
348                        'id'         : id,
349                        'url'        : item_url,
350                        'description': item.Description,
351                        'exclude_from_nav' : item.exclude_from_nav}
352                result.append(data)
353       
354        return result
355   
356    def getCategories(self):
357        """ See interface """
358        portal_actions = getToolByName(self.context, "portal_actions")
359        return portal_actions.objectIds()
360   
361    #
362    # Methods to make this class looks like global sections viewlet
363    #
364   
365    def test(self, condition, ifTrue, ifFalse):
366        """ See interface """
367        if condition:
368            return ifTrue
369        else:
370            return ifFalse
371   
372    # methods for rendering global-sections viewlet via kss,
373    # due to bug in macroContent when global-section list is empty,
374    # ul have condition
375    def portal_tabs(self):
376        """ See global-sections viewlet """
377        actions = context_state = getMultiAdapter((self.context, self.request), name=u"plone_context_state").actions()
378        portal_tabs_view = getMultiAdapter((self.context, self.request), name="portal_tabs_view")
379       
380        return portal_tabs_view.topLevelTabs(actions=actions)
381   
382    def selected_portal_tab(self):
383        """ See global-sections viewlet """
384        selectedTabs = self.context.restrictedTraverse('selectedTabs')
385        selected_tabs = selectedTabs('index_html', self.context, self.portal_tabs())
386       
387        return selected_tabs['portal']
388   
389    ##########################
390    #
391    # KSS Server Actions
392    #
393    ##########################
394   
395    def validateAction(self, id, category, prefix="tabslist_"):
396        """ If action with given id and category doesn't exist - raise kss exception """
397        portal_actions = getToolByName(self.context, "portal_actions")
398       
399        # remove prefix, added for making ids on configlet unique ("tabslist_")
400        act_id = id[len("tabslist_"):]
401       
402        if category not in portal_actions.objectIds():
403            raise KSSExplicitError, "Unexistent root portal actions category %s" % category
404       
405        cat_container = portal_actions[category]
406        if act_id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())):
407            raise KSSExplicitError, "%s action does not exist in %s category" % (act_id, category)
408       
409        return (cat_container, act_id)
410   
411    @kssaction
412    def toggleGeneratedTabs(self, field, checked='0'):
413        """ Toggle autogenaration setting on configlet """
414       
415        changeProperties = getToolByName(self.context, "portal_properties").site_properties.manage_changeProperties
416        if checked == '1':
417            changeProperties(**{field : False})
418        else:
419            changeProperties(**{field : True})
420       
421        ksscore = self.getCommandSet("core")
422        replace_id = "roottabs"
423        content = self.getGeneratedTabs()
424       
425        ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector(replace_id), content, withKssSetup="True")
426       
427        # update global-sections viewlet
428        self.updatePortalTabs()
429   
430    @kssaction
431    def toggleActionsVisibility(self, id, checked='0', category=None):
432        """ Toggle visibility for portal actions """
433        portal_actions = getToolByName(self.context, "portal_actions")
434        cat_container, act_id = self.validateAction(id, category)
435       
436        if checked == '1':
437            checked = True
438        else:
439            checked = False
440       
441        cat_container[act_id].visible = checked
442       
443        ksscore = self.getCommandSet("core")
444        if checked:
445            ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible")
446        else:
447            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
448       
449        self.updatePage(category)
450   
451    @kssaction
452    def toggleRootsVisibility(self, id, checked='0'):
453        """ Toggle visibility for portal root objects (exclude_from_nav) """
454        portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal()
455       
456        # remove prefix, added for making ids on configlet unique ("roottabs_")
457        obj_id = id[len("roottabs_"):]
458       
459        if obj_id not in portal.objectIds():
460            raise KSSExplicitError, "Object with %s id doesn't exist in portal root" % obj_id
461       
462        if checked == '1':
463            checked = True
464        else:
465            checked = False
466       
467        portal[obj_id].update(excludeFromNav=not checked)
468       
469        ksscore = self.getCommandSet("core")
470        if checked:
471            ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible")
472        else:
473            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
474       
475        # update global-sections viewlet
476        self.updatePortalTabs()
477   
478    @kssaction
479    def kss_deleteAction(self, id, category):
480        """ Delete portal action with given id & category """
481        portal_actions = getToolByName(self.context, "portal_actions")
482        cat_container, act_id = self.validateAction(id, category)
483       
484        cat_container.manage_delObjects(ids=[act_id,])
485       
486        # update action list on client
487        ksscore = self.getCommandSet("core")
488       
489        ksscore.deleteNode(ksscore.getHtmlIdSelector(id))
490       
491        # add "noitems" class to Reorder controls to hide it
492        if not filter(lambda x: IAction.providedBy(x), cat_container.objectValues()):
493            ksscore.addClass(ksscore.getHtmlIdSelector("reorder"), value="noitems")
494       
495        # XXX TODO: fade effect during removing, for this kukit js action/command plugin needed
496       
497        self.updatePage(category)
498   
499    @kssaction
500    def oldAddAction(self, id, name, action='', category='portal_tabs', condition='', visible=False):
501        pass
502   
503    @kssaction
504    def editAction(self, id, category):
505        """ Show edit form for given action """
506        cat_container, act_id = self.validateAction(id, category)
507       
508        # collect data
509        action_info = self.copyAction(cat_container[act_id])
510        action_info["editing"] = True
511       
512        ksscore = self.getCommandSet("core")
513        content = self.actionslist_template(tabs=[action_info,])
514        replace_id = id
515       
516        ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), content)
517       
518        # focus name field
519        ksscore.focus(ksscore.getCssSelector("#%s input[name=name_%s]" % (id, act_id)))
520   
521    @kssaction
522    def editCancel(self, id, category):
523        """ Hide edit form for given action """
524        cat_container, act_id = self.validateAction(id, category)
525       
526        ksscore = self.getCommandSet("core")
527        content = self.actionslist_template(tabs=[cat_container[act_id],])
528        replace_id = id
529       
530        ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), content)
531   
532    #
533    # Utility Methods
534    #
535   
536    def copyAction(self, action):
537        """ Copyt action to dictionary """
538        action_info = {}
539        for attr in ACTION_ATTRS:
540            action_info[attr] = getattr(action, attr)
541        return action_info
542   
543    def validateActionFields(self, cat_name, data, allow_dup=False):
544        """ Check action fields on validity """
545        errors = {}
546       
547        if allow_dup:
548            category = ActionCategory(cat_name)           # create dummy category to avoid id duplication during action update
549        else:
550            category = self.getOrCreateCategory(cat_name) # get or create (if necessary) actions category
551       
552        # validate action id
553        chooser = INameChooser(category)
554        try:
555            chooser.checkName(data['id'], self.context)
556        except Exception, e:
557            errors['id'] = "%s" % str(e)
558       
559        # validate action name
560        if not data['title'].strip():
561            errors['title'] = 'Empty or invalid title specified'
562       
563        # validate condition expression
564        if data['available_expr']:
565            try:
566                Expression(data['available_expr'])
567            except Exception, e:
568                errors["available_expr"] = "%s" % str(e)
569       
570        # validate action expression
571        if data['url_expr']:
572            try:
573                Expression(data['url_expr'])
574            except Exception, e:
575                errors["url_expr"] = "%s" % str(e)
576       
577        return errors
578   
579    def processErrors(self, errors, prefix='', sufix=''):
580        """ Add prefixes, sufixes to error ids
581            This is necessary during edit form validation,
582            because every edit form on the page has it's own sufix (id) """
583        if not (prefix or sufix):
584            return errors
585       
586        result = {}
587        for key, value in errors.items():
588            result['%s%s%s' % (prefix, key, sufix)] = value
589       
590        return result
591   
592    def parseForm(self, form):
593        """ Extract all needed fields """
594        # get original id and category
595        info = {}
596        id = form['orig_id']
597        category = form['category']
598       
599        # preprocess 'visible' field (checkbox needs special checking)
600        if form.has_key('visible_%s' % id):
601            form['visible_%s' % id] = True
602        else:
603            form['visible_%s' % id] = False
604       
605        # collect all action fields
606        for attr in ACTION_ATTRS:
607            info[attr] = form['%s_%s' % (attr, id)]
608       
609        return (id, category, info)
610   
611    def getActionCategory(self, name):
612        portal_actions = getToolByName(self.context, 'portal_actions')
613        return portal_actions[name]
614   
615    def getOrCreateCategory(self, name):
616        """ Get or create (if necessary) category """
617        portal_actions = getToolByName(self.context, 'portal_actions')
618        if name not in map(lambda x: x.id, filter(lambda x: IActionCategory.providedBy(x), portal_actions.objectValues())):
619            portal_actions._setObject(name, ActionCategory(name))
620        return self.getActionCategory(name)
621   
622    def addAction(self, cat_name, data):
623        """ Create and add new action to category with given name """
624        id = data.pop('id')
625        category = self.getOrCreateCategory(cat_name)
626        action = Action(id, **data)
627        category._setObject(id, action)
628        return action
629   
630    def updateAction(self, id, cat_name, data):
631        """ Update action with given id and category """
632        new_id = data.pop('id')
633        category = self.getActionCategory(cat_name)
634       
635        # rename action if necessary
636        if id != new_id:
637            category.manage_renameObject(id, new_id)
638       
639        # get action
640        action = category[new_id]
641       
642        # update action properties
643        for attr in ACTION_ATTRS:
644            if data.has_key(attr):
645                action._setPropValue(attr, data[attr])
646       
647        return action
648   
649    def deleteAction(self, id, cat_name):
650        """ Delete action with given id from given category """
651        category = self.getActionCategory(cat_name)
652        category.manage_delObjects(ids=[id,])
653        return True
654   
655    #
656    # KSS Methods that are used to update different parts of the page
657    # according to category
658    #
659   
660    def updatePage(self, category):
661        """ Seek for according method in class and calls it if found
662            Example of making up method's name:
663                portal_tabs => updatePortalTabs """
664        method_name = 'update%sPageSection' % ''.join(map(lambda x: x.capitalize(), category.split('_')))
665        if hasattr(self, method_name):
666            getattr(self, method_name)()
667   
668    def updatePortalTabsPageSection(self):
669        """ Method for updating global-sections on client """
670        ksscore = self.getCommandSet("core")
671        ksscore.replaceHTML(
672            ksscore.getHtmlIdSelector("portal-globalnav"),
673            self.sections_template(),
674            withKssSetup="False")
675   
676    def updateSiteActionsPageSection(self):
677        """ Method for updating site action on client """
678        ksszope = self.getCommandSet("zope")
679        ksszope.refreshViewlet(
680            self.getCommandSet("core").getHtmlIdSelector("portal-siteactions"),
681            "plone.portalheader",
682            "plone.site_actions")
683   
684    def updateUserPageSection(self):
685        """ Method for updating site action on client """
686        ksszope = self.getCommandSet("zope")
687        ksszope.refreshViewlet(
688            self.getCommandSet("core").getHtmlIdSelector("portal-personaltools-wrapper"),
689            "plone.portaltop",
690            "plone.personal_bar")
691
692
Note: See TracBrowser for help on using the repository browser.