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

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

fixed fields distplay list, moved the CSV generation method into qTopic

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