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

Last change on this file since 160 was 160, checked in by mylan, 18 years ago

Create DiscussionManager? group in portal_group with DiscussionManager? role

  • Property svn:eol-style set to native
File size: 34.8 KB
Line 
1import copy
2import sys
3import urllib
4
5from Acquisition import aq_inner
6from OFS.CopySupport import CopyError
7
8from zope.interface import implements
9from zope.component import getUtility, getMultiAdapter
10from zope.i18n import translate
11from zope.schema.interfaces import IVocabularyFactory
12from zope.exceptions import UserError
13from zope.app.container.interfaces import INameChooser
14
15from Products.CMFCore.utils import getToolByName
16from Products.CMFCore.interfaces import IAction, IActionCategory
17from Products.CMFCore.ActionInformation import Action, ActionCategory
18from Products.CMFCore.Expression import Expression
19from Products.CMFPlone import PloneMessageFactory as _
20from Products.CMFPlone import utils
21from Products.CMFPlone.browser.navigation import get_view_url
22from Products.Five.browser import BrowserView
23from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
24from Products.statusmessages.interfaces import IStatusMessage
25
26from plone.app.layout.navigation.root import getNavigationRoot
27from plone.app.kss.plonekssview import PloneKSSView
28from plone.app.workflow.remap import remap_workflow
29from plone.memoize.instance import memoize
30from kss.core import kssaction, KSSExplicitError
31
32from quintagroup.plonetabs.config import *
33from interfaces import IPloneTabsControlPanel
34
35ACTION_ATTRS = ["id", "title", "url_expr", "available_expr", "visible"]
36UI_ATTRS = {"id": "id",
37            "title": "name",
38            "url_expr": "action",
39            "available_expr": "condition",
40            "visible": "visible"}
41
42bad_id = re.compile(r'[^a-zA-Z0-9-_~,.$\(\)# @]').search
43
44class PloneTabsControlPanel(PloneKSSView):
45   
46    implements(IPloneTabsControlPanel)
47   
48    template = ViewPageTemplateFile("templates/plonetabs.pt")
49    actionslist_template = ViewPageTemplateFile("templates/actionslist.pt")
50    autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt")
51   
52    # custom templates used to update page sections
53    sections_template = ViewPageTemplateFile("templates/sections.pt")
54   
55    # configuration variables
56    prefix = "tabslist_"
57    sufix = ""
58   
59    def __call__(self):
60        """ Perform the update and redirect if necessary, or render the page """
61        postback = True
62        errors = {}
63        context = aq_inner(self.context)
64       
65        form = self.request.form
66        action = form.get("action", "")
67        submitted = form.get('form.submitted', False)
68       
69        # action handler def handler(self, form)
70        if submitted:
71            if form.has_key('add.add'):
72                postback = self.manage_addAction(form, errors)
73            elif form.has_key("edit.save"):
74                postback = self.manage_editAction(form, errors)
75            elif form.has_key("edit.delete"):
76                postback = self.manage_deleteAction(form, errors)
77            elif form.has_key("edit.moveup"):
78                postback = self.manage_moveUpAction(form, errors)
79            elif form.has_key("edit.movedown"):
80                postback = self.manage_moveDownAction(form, errors)
81            elif form.has_key("autogenerated.save"):
82                postback = self.manage_setAutogeneration(form, errors)
83            else:
84                postback = True
85       
86        if postback:
87            return self.template(errors=errors)
88   
89    ########################################
90    # Methods for processing configlet posts
91    ########################################
92   
93    def manage_setAutogeneration(self, form, errors):
94        """ Process managing autogeneration settings """
95       
96        # set excludeFromNav property for root objects
97        portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal()
98        generated_tabs = form.get("generated_tabs", '0')
99        nonfolderish_tabs = form.get("nonfolderish_tabs", '0')
100       
101        for item in self.getRootTabs():
102            obj = getattr(portal, item['id'], None)
103            if obj is not None:
104                checked = form.get(item['id'], None)
105                if checked == '1':
106                    obj.update(excludeFromNav=False)
107                else:
108                    obj.update(excludeFromNav=True)
109
110        # set disable_folder_sections property
111        if int(generated_tabs) == 1:
112            self.setSiteProperties(disable_folder_sections=False)
113        else:
114            self.setSiteProperties(disable_folder_sections=True)
115       
116        # set disable_nonfolderish_sections property
117        if int(nonfolderish_tabs) == 1:
118            self.setSiteProperties(disable_nonfolderish_sections=False)
119        else:
120            self.setSiteProperties(disable_nonfolderish_sections=True)
121       
122        # after successfull form processing make redirect with good message
123        IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"), type="info")
124        self.redirect()
125        return False
126   
127    def manage_addAction(self, form, errs):
128        """ Manage method to add a new action to given category,
129            if category doesn't exist, create it """
130        # extract posted data
131        id, cat_name, data = self.parseAddForm(form)
132       
133        # validate posted data
134        errors = self.validateActionFields(cat_name, data)
135       
136        # if not errors find (or create) category and set action to it
137        if not errors:
138            action = self.addAction(cat_name, data)
139            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action successfully added." % action.id), type="info")
140            self.redirect(search="category=%s" % cat_name)
141            return False
142        else:
143            errs.update(errors)
144            IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error")
145            return True
146   
147    def manage_editAction(self, form, errs):
148        """ Manage Method to update action """
149        # extract posted data
150        id, cat_name, data = self.parseEditForm(form)
151       
152        # get category and action to edit
153        category = self.getActionCategory(cat_name)
154        action = category[id]
155       
156        # validate posted data
157        errors = self.validateActionFields(cat_name, data, allow_dup=True)
158       
159        if not errors:
160            action = self.updateAction(id, cat_name, data)
161            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action saved." % action.id), type="info")
162            self.redirect(search="category=%s" % cat_name)
163            return False
164        else:
165            errs.update(self.processErrors(errors, sufix='_%s' % id)) # add edit form sufix to error ids
166            IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error")
167            return True
168   
169    def manage_deleteAction(self, form, errs):
170        """ Manage Method to delete action """
171        # extract posted data
172        id, cat_name, data = self.parseEditForm(form)
173       
174        # get category and action to delete
175        category = self.getActionCategory(cat_name)
176        if id in category.objectIds():
177            self.deleteAction(id, cat_name)
178            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action deleted." % id), type="info")
179            self.redirect(search="category=%s" % cat_name)
180            return False
181        else:
182            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
183            return True
184   
185    def manage_moveUpAction(self, form, errs):
186        """ Manage Method for moving up given action by one position """
187        # extract posted data
188        id, cat_name, data = self.parseEditForm(form)
189       
190        # get category and action to move
191        category = self.getActionCategory(cat_name)
192        if id in category.objectIds():
193            self.moveAction(id, cat_name, steps=1)
194            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action moved up." % id), type="info")
195            self.redirect(search="category=%s" % cat_name)
196            return False
197        else:
198            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
199            return True
200   
201    def manage_moveDownAction(self, form, errs):
202        """ Manage Method for moving down given action by one position """
203        # extract posted data
204        id, cat_name, data = self.parseEditForm(form)
205       
206        # get category and action to move
207        category = self.getActionCategory(cat_name)
208        if id in category.objectIds():
209            self.moveAction(id, cat_name, steps=-1)
210            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action moved down." % id), type="info")
211            self.redirect(search="category=%s" % cat_name)
212            return False
213        else:
214            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
215            return True
216   
217    def redirect(self, url="", search="", url_hash=""):
218        """ Redirect to @@plonetabs-controlpanel configlet """
219        portal_url =  getMultiAdapter((self.context, self.request), name=u"plone_portal_state").portal_url()
220        url = (url == "") and "%s/%s" % (portal_url, "@@plonetabs-controlpanel") or url
221        search = (search != "") and "?%s" % search or search
222        url_hash = (url_hash != "") and "#%s" % url_hash or url_hash
223        self.request.response.redirect("%s%s%s" % (url, search, url_hash))
224   
225    ###################################
226    #
227    #  Methods - providers for templates
228    #
229    ###################################
230   
231    def getPageTitle(self, category="portal_tabs"):
232        """ See interface """
233        portal_props = getToolByName(self.context, "portal_properties")
234        default_title = "Plone '%s' Configuration" % category
235       
236        if not hasattr(portal_props, PROPERTY_SHEET):
237            return default_title
238       
239        sheet = getattr(portal_props, PROPERTY_SHEET)
240        if not hasattr(sheet, FIELD_NAME):
241            return default_title
242       
243        field = sheet.getProperty(FIELD_NAME)
244        dict = {}
245        for line in field:
246            cat, title = line.split("|", 2)
247            dict[cat] = title
248       
249        return dict.get(category, None) or default_title
250   
251    def hasActions(self, category="portal_tabs"):
252        """ See interface """
253        return len(getToolByName(self.context, "portal_actions").listActions(categories=[category,])) > 0
254   
255    def getPortalActions(self, category="portal_tabs"):
256        """ See interface """
257        portal_actions = getToolByName(self.context, "portal_actions")
258       
259        if category not in portal_actions.objectIds():
260            return []
261       
262        actions = []
263        for item in portal_actions[category].objectValues():
264            if IAction.providedBy(item):
265                actions.append(item)
266       
267        return actions
268   
269    def isGeneratedTabs(self):
270        """ See interface """
271        site_properties = getToolByName(self.context, "portal_properties").site_properties
272        return not site_properties.getProperty("disable_folder_sections", False)
273   
274    def isNotFoldersGenerated(self):
275        """ See interface """
276        site_properties = getToolByName(self.context, "portal_properties").site_properties
277        return not site_properties.getProperty("disable_nonfolderish_sections", False)
278   
279    def getActionsList(self, category="portal_tabs", errors={}, tabs=[]):
280        """ See interface """
281        kw = {'category': category, 'errors': errors}
282        if tabs:
283            kw['tabs'] = tabs
284        return self.actionslist_template(**kw)
285   
286    def getGeneratedTabs(self):
287        """ See interface """
288        return self.autogenerated_template()
289   
290    def getRootTabs(self):
291        """ See interface """
292        context = aq_inner(self.context)
293       
294        portal_catalog = getToolByName(context, 'portal_catalog')
295        portal_properties = getToolByName(context, 'portal_properties')
296        navtree_properties = getattr(portal_properties, 'navtree_properties')
297       
298        # Build result dict
299        result = []
300       
301        # check whether tabs autogeneration is turned on
302        if not self.isGeneratedTabs():
303            return result
304       
305        query = {}
306        rootPath = getNavigationRoot(context)
307        query['path'] = {'query' : rootPath, 'depth' : 1}
308        query['portal_type'] = utils.typesToList(context)
309       
310        sortAttribute = navtree_properties.getProperty('sortAttribute', None)
311        if sortAttribute is not None:
312            query['sort_on'] = sortAttribute
313           
314            sortOrder = navtree_properties.getProperty('sortOrder', None)
315            if sortOrder is not None:
316                query['sort_order'] = sortOrder
317       
318        if navtree_properties.getProperty('enable_wf_state_filtering', False):
319            query['review_state'] = navtree_properties.getProperty('wf_states_to_show', [])
320       
321        query['is_default_page'] = False
322
323        if not self.isNotFoldersGenerated():
324            query['is_folderish'] = True
325
326        # Get ids not to list and make a dict to make the search fast
327        idsNotToList = navtree_properties.getProperty('idsNotToList', ())
328        excludedIds = {}
329        for id in idsNotToList:
330            excludedIds[id]=1
331
332        rawresult = portal_catalog.searchResults(**query)
333
334        # now add the content to results
335        for item in rawresult:
336            if not excludedIds.has_key(item.getId):
337                id, item_url = get_view_url(item)
338                data = {'name'       : utils.pretty_title_or_id(context, item),
339                        'id'         : id,
340                        'url'        : item_url,
341                        'description': item.Description,
342                        'exclude_from_nav' : item.exclude_from_nav}
343                result.append(data)
344       
345        return result
346   
347    def getCategories(self):
348        """ See interface """
349        portal_actions = getToolByName(self.context, "portal_actions")
350        return portal_actions.objectIds()
351   
352    #
353    # Methods to make this class looks like global sections viewlet
354    #
355   
356    def test(self, condition, ifTrue, ifFalse):
357        """ See interface """
358        if condition:
359            return ifTrue
360        else:
361            return ifFalse
362   
363    # methods for rendering global-sections viewlet via kss,
364    # due to bug in macroContent when global-section list is empty,
365    # ul have condition
366    def portal_tabs(self):
367        """ See global-sections viewlet """
368        actions = context_state = getMultiAdapter((self.context, self.request), name=u"plone_context_state").actions()
369        portal_tabs_view = getMultiAdapter((self.context, self.request), name="portal_tabs_view")
370       
371        return portal_tabs_view.topLevelTabs(actions=actions)
372   
373    def selected_portal_tab(self):
374        """ See global-sections viewlet """
375        selectedTabs = self.context.restrictedTraverse('selectedTabs')
376        selected_tabs = selectedTabs('index_html', self.context, self.portal_tabs())
377       
378        return selected_tabs['portal']
379   
380    ##########################
381    #
382    # KSS Server Actions
383    #
384    ##########################
385   
386    @kssaction
387    def kss_toggleGeneratedTabs(self, field, checked='0'):
388        """ Toggle autogenaration setting on configlet """
389       
390        changeProperties = getToolByName(self.context, "portal_properties").site_properties.manage_changeProperties
391        if checked == '1':
392            changeProperties(**{field : False})
393        else:
394            changeProperties(**{field : True})
395       
396        ksscore = self.getCommandSet("core")
397        replace_id = "roottabs"
398        content = self.getGeneratedTabs()
399       
400        ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector(replace_id), content, withKssSetup="True")
401       
402        # update global-sections viewlet
403        self.updatePortalTabs()
404   
405    @kssaction
406    def kss_toggleRootsVisibility(self, id, checked='0'):
407        """ Toggle visibility for portal root objects (exclude_from_nav) """
408        portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal()
409       
410        # remove prefix, added for making ids on configlet unique ("roottabs_")
411        obj_id = id[len("roottabs_"):]
412       
413        if obj_id not in portal.objectIds():
414            raise KSSExplicitError, "Object with %s id doesn't exist in portal root" % obj_id
415       
416        if checked == '1':
417            checked = True
418        else:
419            checked = False
420       
421        portal[obj_id].update(excludeFromNav=not checked)
422       
423        ksscore = self.getCommandSet("core")
424        if checked:
425            ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible")
426        else:
427            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
428       
429        # update global-sections viewlet
430        self.updatePortalTabs()
431   
432    @kssaction
433    def kss_toggleActionsVisibility(self, id, checked='0', cat_name=None):
434        """ Toggle visibility for portal actions """
435        # validate input
436        act_id, category, action = self.kss_validateAction(id, cat_name)
437        self.updateAction(act_id, cat_name, {'id': act_id, 'visible': (checked == '1') or False})
438       
439        # update client
440        ksscore = self.getCommandSet("core")
441        if checked == '1':
442            ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible")
443        else:
444            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
445        self.updatePage(cat_name)
446   
447    @kssaction
448    def kss_deleteAction(self, id, cat_name):
449        """ Delete portal action with given id & category """
450        # validate input
451        act_id, category, action = self.kss_validateAction(id, cat_name)
452        self.deleteAction(act_id, cat_name)
453       
454        # update client
455        ksscore = self.getCommandSet("core")
456        # XXX TODO: fade effect during removing, to do this we need kukit js action/command plugin
457        ksscore.deleteNode(ksscore.getHtmlIdSelector(id))
458       
459        # check reorder controls, whether we should hide them
460        #self.kss_checkReorderControls(cat_name)
461       
462        # issue portal message
463        self.getCommandSet('plone').issuePortalMessage(_(u"'%s' action successfully deleted." % act_id), msgtype="info")
464       
465        # update different sections of page depending on actions category
466        self.updatePage(cat_name)
467   
468    @kssaction
469    def kss_addAction(self):
470        """ KSS method to add new portal action """
471        # extract posted data
472        id, cat_name, data = self.parseAddForm(self.request.form)
473       
474        # validate posted data
475        errors = self.validateActionFields(cat_name, data)
476       
477        # if not errors find (or create) category and set action to it
478        ksscore = self.getCommandSet('core')
479        kssplone = self.getCommandSet('plone')
480        if not errors:
481            action = self.addAction(cat_name, data)
482           
483            # update client
484            # add one more action to actions list
485            content = self.getActionsList(category=cat_name, tabs=[action,])
486            ksscore.insertHTMLAsLastChild(ksscore.getHtmlIdSelector('tabslist'), content)
487           
488            # update reorder controls
489            #self.kss_checkReorderControls(cat_name)
490           
491            # hide adding form
492            ksscore.removeClass(ksscore.getHtmlIdSelector('addaction'), 'adding')
493            self.kss_toggleCollapsible(ksscore.getCssSelector('#addaction .collapseAdvanced .headerAdvanced'), collapse='true')
494           
495            # set client state var 'plonetabs-addingTitle' to empty string for correct id autogeneration functionality
496            ksscore.setStateVar('plonetabs-addingTitle', '')
497           
498            # reset adding form
499            self.kss_resetForm(ksscore.getHtmlIdSelector('addaction'))
500           
501            # issue portal message
502            kssplone.issuePortalMessage(_(u"'%s' action successfully added." % action.id), msgtype="info")
503           
504            # update page
505            self.updatePage(cat_name)
506        else:
507            # expand advanced section if there are errors in id or condition
508            if errors.has_key('id') or errors.has_key('available_expr'):
509                self.kss_toggleCollapsible(ksscore.getCssSelector('#addaction .collapseAdvanced .headerAdvanced'), collapse='false')
510           
511            # send error message
512            kssplone.issuePortalMessage(_(u"Please correct the indicated errors."), msgtype="error")
513       
514        # update errors on client form
515        self.kss_issueErrors(errors)
516   
517    @kssaction
518    def kss_showEditForm(self, id, cat_name):
519        """ Show edit form for given action """
520        act_id, category, action = self.kss_validateAction(id, cat_name)
521       
522        # fetch data
523        action_info = self.copyAction(action)
524        action_info["editing"] = True
525       
526        # update client
527        ksscore = self.getCommandSet("core")
528        content = self.actionslist_template(tabs=[action_info,])
529        ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content)
530       
531        # focus name field
532        ksscore.focus(ksscore.getCssSelector("#%s input[name=title_%s]" % (id, act_id)))
533   
534    @kssaction
535    def kss_hideEditForm(self, id, cat_name):
536        """ Hide edit form for given action """
537        act_id, category, action = self.kss_validateAction(id, cat_name)
538       
539        # update client
540        ksscore = self.getCommandSet("core")
541        content = self.actionslist_template(tabs=[action,])
542        ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content)
543   
544    @kssaction
545    def kss_editAction(self):
546        """ Update action's properties """
547        id, cat_name, data = self.parseEditForm(self.request.form)
548       
549        # get category and action to edit
550        category = self.getActionCategory(cat_name)
551        action = category[id]
552       
553        # validate posted data
554        errors = self.validateActionFields(cat_name, data, allow_dup=True)
555       
556        html_id = '%s%s%s' % (self.prefix, id, self.sufix)
557        ksscore = self.getCommandSet('core')
558        kssplone = self.getCommandSet('plone')
559        if not errors:
560            action = self.updateAction(id, cat_name, data)
561           
562            # update client
563            # replace action item with updated one
564            content = self.getActionsList(category=cat_name, tabs=[action,])
565            ksscore.replaceHTML(ksscore.getHtmlIdSelector(html_id), content)
566           
567            # issue portal message
568            kssplone.issuePortalMessage(_(u"'%s' action saved." % action.id), msgtype="info")
569           
570            # update page
571            self.updatePage(cat_name)
572        else:
573            # issue error messages
574            self.kss_issueErrors(errors, editform=id)
575           
576            # expand advanced section if there are errors in id, action url or condition
577            if errors.has_key('id') or errors.has_key('available_expr') or errors.has_key('url_expr'):
578                self.kss_toggleCollapsible(ksscore.getCssSelector('#%s .headerAdvanced' % html_id), collapse='false')
579           
580            # send error message
581            kssplone.issuePortalMessage(_(u"Please correct the indicated errors."), msgtype="error")
582   
583    @kssaction
584    def kss_orderActions(self):
585        """ Update actions order in the given category """
586        form = self.request.form
587        cat_name = form['cat_name']
588        category = self.getActionCategory(cat_name)
589       
590        # decode URI components and collect ids from request
591        components = urllib.unquote(form['actions']).split('&')
592        if self.sufix == '':
593            ids = [component[len(self.prefix):] for component in components]
594        else:
595            ids = [component[len(self.prefix):-len(self.sufix)] for component in components]
596       
597        # do actual sorting
598        category.moveObjectsByDelta(ids, -len(category.objectIds()))
599       
600        # update client
601        self.updatePage(cat_name)
602   
603    #
604    # Utility Methods
605    #
606   
607    def copyAction(self, action):
608        """ Copyt action to dictionary """
609        action_info = {'description':action.description}
610        for attr in ACTION_ATTRS:
611            action_info[attr] = getattr(action, attr)
612        return action_info
613   
614    def validateActionFields(self, cat_name, data, allow_dup=False):
615        """ Check action fields on validity """
616        errors = {}
617       
618        if allow_dup:
619            category = ActionCategory(cat_name)           # create dummy category to avoid id duplication during action update
620        else:
621            category = self.getOrCreateCategory(cat_name) # get or create (if necessary) actions category
622       
623        # validate action id
624        chooser = INameChooser(category)
625        try:
626            chooser.checkName(data['id'], self.context)
627        except Exception, e:
628            errors['id'] = "%s" % str(e)
629       
630        # validate action name
631        if not data['title'].strip():
632            errors['title'] = 'Empty or invalid title specified'
633       
634        # validate condition expression
635        if data['available_expr']:
636            try:
637                Expression(data['available_expr'])
638            except Exception, e:
639                errors["available_expr"] = "%s" % str(e)
640       
641        # validate action expression
642        if data['url_expr']:
643            try:
644                Expression(data['url_expr'])
645            except Exception, e:
646                errors["url_expr"] = "%s" % str(e)
647       
648        return errors
649   
650    def processErrors(self, errors, prefix='', sufix=''):
651        """ Add prefixes, sufixes to error ids
652            This is necessary during edit form validation,
653            because every edit form on the page has it's own sufix (id) """
654        if not (prefix or sufix):
655            return errors
656       
657        result = {}
658        for key, value in errors.items():
659            result['%s%s%s' % (prefix, key, sufix)] = value
660       
661        return result
662   
663    def parseEditForm(self, form):
664        """ Extract all needed fields from edit form """
665        # get original id and category
666        info = {}
667        id = form['orig_id']
668        category = form['category']
669       
670        # preprocess 'visible' field (checkbox needs special checking)
671        if form.has_key('visible_%s' % id):
672            form['visible_%s' % id] = True
673        else:
674            form['visible_%s' % id] = False
675       
676        # collect all action fields
677        for attr in ACTION_ATTRS:
678            info[attr] = form['%s_%s' % (attr, id)]
679       
680        return (id, category, info)
681   
682    def parseAddForm(self, form):
683        """ Extract all needed fields from add form """
684        info = {}
685        id = form['id']
686        category = form['category']
687       
688        # preprocess 'visible' field (checkbox needs special checking)
689        if form.has_key('visible') and form['visible']:
690            form['visible'] = True
691        else:
692            form['visible'] = False
693       
694        # collect all action fields
695        for attr in ACTION_ATTRS:
696            info[attr] = form[attr]
697       
698        return (id, category, info)
699   
700    def getActionCategory(self, name):
701        portal_actions = getToolByName(self.context, 'portal_actions')
702        return portal_actions[name]
703   
704    def getOrCreateCategory(self, name):
705        """ Get or create (if necessary) category """
706        portal_actions = getToolByName(self.context, 'portal_actions')
707        if name not in map(lambda x: x.id, filter(lambda x: IActionCategory.providedBy(x), portal_actions.objectValues())):
708            portal_actions._setObject(name, ActionCategory(name))
709        return self.getActionCategory(name)
710   
711    def setSiteProperties(self, **kw):
712        """ Change site_properties """
713        site_properties = getToolByName(self.context, "portal_properties").site_properties
714        site_properties.manage_changeProperties(**kw)
715        return True
716   
717    #
718    # Utility methods for the kss actions management
719    #
720   
721    def kss_checkReorderControls(self, cat_name):
722        """ Add "noitems" class to Reorder controls to hide them if category is empty """
723        ksscore = self.getCommandSet('core')
724        selector = ksscore.getHtmlIdSelector("reorder")
725        category = self.getActionCategory(cat_name)
726        if filter(lambda x: IAction.providedBy(x), category.objectValues()):
727            ksscore.removeClass(selector, value="noitems")
728        else:
729            ksscore.addClass(selector, value="noitems")
730   
731    def kss_validateAction(self, id, cat_name):
732        """ Check whether action with given id exists in cat_name category """
733        try:
734            category = self.getActionCategory(cat_name)
735        except Exception:
736            raise KSSExplicitError, "'%s' action category does not exist" % cat_name
737       
738        # extract action id from given list item id on client
739        action_id = self.sufix and id[len(self.prefix):-len(self.sufix)] or id[len(self.prefix):]
740        try:
741            action = category[action_id]
742        except Exception:
743            raise KSSExplicitError, "No '%s' action in '%s' category." % (action_id, cat_name)
744       
745        return (action_id, category, action)
746   
747    def kss_issueErrors(self, errors, editform=False, fields=ACTION_ATTRS):
748        """ Display error messages on the client """
749        ksscore = self.getCommandSet('core')
750        for field in fields:
751            self.kss_issueFieldError(ksscore, field, errors.get(field, False), editform)
752   
753    def kss_issueFieldError(self, ksscore, name, error, editform):
754        """ Issue this error message for the field """
755        if editform:
756            id = '%s%s%s' % (self.prefix, editform, self.sufix)
757            field_selector = ksscore.getCssSelector('#%s .edit-field-%s' % (id, UI_ATTRS.get(name, name)))
758            field_error_selector = ksscore.getCssSelector('#%s .edit-field-%s .error-container' % (id, UI_ATTRS.get(name, name)))
759        else:
760            field_selector = ksscore.getCssSelector('#addaction .field-%s' % UI_ATTRS.get(name, name))
761            field_error_selector = ksscore.getCssSelector('#addaction .field-%s .error-container' % UI_ATTRS.get(name, name))
762
763        if error:
764            ksscore.replaceInnerHTML(field_error_selector, _(error))
765            ksscore.addClass(field_selector, 'error')
766        else:
767            ksscore.clearChildNodes(field_error_selector)
768            ksscore.removeClass(field_selector, 'error')
769   
770    def kss_toggleCollapsible(self, selector, collapsed=None, expanded=None, collapse=None):
771        """ KSS Server command to add plonetabs-toggleCollapsible client action to response """
772        command = self.commands.addCommand('plonetabs-toggleCollapsible', selector)
773        if collapsed is not None:
774            data = command.addParam('collapsed', collapsed)
775        if expanded is not None:
776            data = command.addParam('expanded', expanded)
777        if collapse is not None:
778            data = command.addParam('collapse', collapse)
779   
780    def kss_resetForm(self, selector):
781        """ KSS Server command to reset form on client """
782        command = self.commands.addCommand('plonetabs-resetForm', selector)
783   
784    def validateAction(self, id, category, prefix="tabslist_"):
785        """ If action with given id and category doesn't exist - raise kss exception """
786        portal_actions = getToolByName(self.context, "portal_actions")
787       
788        # remove prefix, added for making ids on configlet unique self.prefix
789        act_id = id[len(self.prefix):]
790       
791        if category not in portal_actions.objectIds():
792            raise KSSExplicitError, "Unexistent root portal actions category %s" % category
793       
794        cat_container = portal_actions[category]
795        if act_id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())):
796            raise KSSExplicitError, "%s action does not exist in %s category" % (act_id, category)
797       
798        return (cat_container, act_id)
799   
800    #
801    # Basic API to work with portal actions tool in a more pleasent way
802    #
803   
804    def addAction(self, cat_name, data):
805        """ Create and add new action to category with given name """
806        id = data.pop('id')
807        category = self.getOrCreateCategory(cat_name)
808        action = Action(id, **data)
809        category._setObject(id, action)
810        return action
811   
812    def updateAction(self, id, cat_name, data):
813        """ Update action with given id and category """
814        new_id = data.pop('id')
815        category = self.getActionCategory(cat_name)
816       
817        # rename action if necessary
818        if id != new_id:
819            category.manage_renameObject(id, new_id)
820       
821        # get action
822        action = category[new_id]
823       
824        # update action properties
825        for attr in data.keys():
826            if data.has_key(attr):
827                action._setPropValue(attr, data[attr])
828       
829        return action
830   
831    def deleteAction(self, id, cat_name):
832        """ Delete action with given id from given category """
833        category = self.getActionCategory(cat_name)
834        category.manage_delObjects(ids=[id,])
835        return True
836   
837    def moveAction(self, id, cat_name, steps=0):
838        """ Move action by a given steps """
839        if steps != 0:
840            category = self.getActionCategory(cat_name)
841            if steps > 0:
842                category.moveObjectsUp([id,], steps)
843            else:
844                category.moveObjectsDown([id,], abs(steps))
845            return True
846        return False
847   
848    #
849    # KSS Methods that are used to update different parts of the page
850    # accordingly to category
851    #
852   
853    def updatePage(self, category):
854        """ Seek for according method in class and calls it if found
855            Example of making up method's name:
856                portal_tabs => updatePortalTabs """
857        method_name = 'update%sPageSection' % ''.join(map(lambda x: x.capitalize(), category.split('_')))
858        if hasattr(self, method_name):
859            getattr(self, method_name)()
860   
861    def updatePortalTabsPageSection(self):
862        """ Method for updating global-sections on client """
863        ksscore = self.getCommandSet("core")
864        ksscore.replaceHTML(
865            ksscore.getHtmlIdSelector("portal-globalnav"),
866            self.sections_template(),
867            withKssSetup="False")
868   
869    def updateSiteActionsPageSection(self):
870        """ Method for updating site action on client """
871        ksszope = self.getCommandSet("zope")
872        ksszope.refreshViewlet(
873            self.getCommandSet("core").getHtmlIdSelector("portal-siteactions"),
874            "plone.portalheader",
875            "plone.site_actions")
876   
877    def updateUserPageSection(self):
878        """ Method for updating site action on client """
879        ksszope = self.getCommandSet("zope")
880        ksszope.refreshViewlet(
881            self.getCommandSet("core").getHtmlIdSelector("portal-personaltools-wrapper"),
882            "plone.portaltop",
883            "plone.personal_bar")
884
885
Note: See TracBrowser for help on using the repository browser.