source: products/quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py @ 727

Last change on this file since 727 was 727, checked in by crchemist, 17 years ago

Added tests.

  • Property svn:eol-style set to native
File size: 38.5 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            # remove focus from name input
523            self.kss_blur(ksscore.getHtmlIdSelector('actname'))
524           
525            # issue portal message
526            kssplone.issuePortalMessage(_(u"'%s' action successfully added." % action.id), msgtype="info")
527           
528            # update page
529            self.updatePage(cat_name)
530        else:
531            # expand advanced section if there are errors in id or condition
532            if errors.has_key('id') or errors.has_key('available_expr'):
533                self.kss_toggleCollapsible(ksscore.getCssSelector('#addaction .headerAdvanced'), collapse='false')
534           
535            # send error message
536            kssplone.issuePortalMessage(_(u"Please correct the indicated errors."), msgtype="error")
537       
538        # update errors on client form
539        self.kss_issueErrors(errors)
540   
541    @kssaction
542    def kss_hideAddForm(self):
543        """"Hide Add form, reset it and remove error messages"""
544        # no server changes, only update client
545        ksscore = self.getCommandSet("core")
546       
547        # hide form itself
548        ksscore.removeClass(ksscore.getHtmlIdSelector('addaction'), 'adding')
549       
550        # collapse advanced section
551        self.kss_toggleCollapsible(
552            ksscore.getCssSelector('#addaction .headerAdvanced'),
553            collapse='true')
554       
555        # reset form inputs
556        self.kss_resetForm(ksscore.getHtmlIdSelector('addaction'))
557       
558        # set client state var 'plonetabs-addingTitle' to empty string for
559        # correct id autogeneration functionality
560        ksscore.setStateVar('plonetabs-addingTitle', '')
561       
562        # remove form errors if such exist
563        self.kss_issueErrors({})
564   
565    @kssaction
566    def kss_showEditForm(self, id, cat_name):
567        """Show edit form for given action"""
568        act_id, category, action = self.kss_validateAction(id, cat_name)
569       
570        # fetch data
571        action_info = self.copyAction(action)
572        action_info["editing"] = True
573       
574        # update client
575        ksscore = self.getCommandSet("core")
576        content = self.getActionsList(category=cat_name, tabs=[action_info,])
577        ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content)
578       
579        # focus name field
580        ksscore.focus(ksscore.getCssSelector("#%s input[name=title_%s]" % (id, act_id)))
581   
582    @kssaction
583    def kss_hideEditForm(self, id, cat_name):
584        """Hide edit form for given action"""
585        act_id, category, action = self.kss_validateAction(id, cat_name)
586       
587        # update client
588        ksscore = self.getCommandSet("core")
589        content = self.getActionsList(category=cat_name, tabs=[action,])
590        ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content)
591   
592    @kssaction
593    def kss_editAction(self):
594        """ Update action's properties """
595        id, cat_name, data = self.parseEditForm(self.request.form)
596       
597        # get category and action to edit
598        category = self.getActionCategory(cat_name)
599        action = category[id]
600       
601        # validate posted data
602        errors = self.validateActionFields(cat_name, data, allow_dup=(id == data['id']))
603       
604        html_id = '%s%s%s' % (self.prefix, id, self.sufix)
605        ksscore = self.getCommandSet('core')
606        kssplone = self.getCommandSet('plone')
607        if not errors:
608            action = self.updateAction(id, cat_name, data)
609           
610            # update client
611            # replace action item with updated one
612            content = self.getActionsList(category=cat_name, tabs=[action,])
613            ksscore.replaceHTML(ksscore.getHtmlIdSelector(html_id), content)
614           
615            # issue portal message
616            kssplone.issuePortalMessage(_(u"'%s' action saved." % action.id), msgtype="info")
617           
618            # update page
619            self.updatePage(cat_name)
620        else:
621            # issue error messages
622            self.kss_issueErrors(errors, editform=id)
623           
624            # expand advanced section if there are errors in id, action url or condition
625            if errors.has_key('id') or errors.has_key('available_expr') or errors.has_key('url_expr'):
626                self.kss_toggleCollapsible(ksscore.getCssSelector('#%s .headerAdvanced' % html_id), collapse='false')
627           
628            # send error message
629            kssplone.issuePortalMessage(_(u"Please correct the indicated errors."), msgtype="error")
630   
631    @kssaction
632    def kss_orderActions(self):
633        """ Update actions order in the given category """
634        form = self.request.form
635        cat_name = form['cat_name']
636        category = self.getActionCategory(cat_name)
637       
638        # decode URI components and collect ids from request
639        components = urllib.unquote(form['actions']).split('&')
640        if self.sufix == '':
641            ids = [component[len(self.prefix):] for component in components]
642        else:
643            ids = [component[len(self.prefix):-len(self.sufix)] for component in components]
644       
645        # do actual sorting
646        category.moveObjectsByDelta(ids, -len(category.objectIds()))
647       
648        # update client
649        self.updatePage(cat_name)
650   
651    #
652    # Utility Methods
653    #
654   
655    def fixExpression(self, expr):
656        """Fix expression appropriatly for tal format"""
657        if expr.find('/') == 0:
658            return 'string:${portal_url}%s' % expr
659        elif re.compile('^(ht|f)tps?\:', re.I).search(expr):
660            return 'string:%s' % expr
661        #elif re.compile('^(python:|string:|not:|exists:|nocall:|path:)', re.I).search(expr):
662            #return expr
663        elif expr.find(':') != -1:
664            return expr
665        else:
666            return 'string:${object_url}/%s' % expr
667   
668    def copyAction(self, action):
669        """ Copy action to dictionary """
670        action_info = {'description':action.description}
671        for attr in ACTION_ATTRS:
672            action_info[attr] = getattr(action, attr)
673        return action_info
674   
675    def validateActionFields(self, cat_name, data, allow_dup=False):
676        """ Check action fields on validity """
677        errors = {}
678       
679        if allow_dup:
680            category = ActionCategory(cat_name)           # create dummy category to avoid id duplication during action update
681        else:
682            category = self.getOrCreateCategory(cat_name) # get or create (if necessary) actions category
683       
684        # validate action id
685        chooser = INameChooser(category)
686        try:
687            chooser.checkName(data['id'], self.context)
688        except Exception, e:
689            errors['id'] = "%s" % str(e)
690       
691        # validate action name
692        if not data['title'].strip():
693            errors['title'] = 'Empty or invalid title specified'
694       
695        # validate condition expression
696        if data['available_expr']:
697            try:
698                Expression(data['available_expr'])
699            except Exception, e:
700                errors["available_expr"] = "%s" % str(e)
701       
702        # validate action expression
703        if data['url_expr']:
704            try:
705                Expression(data['url_expr'])
706            except Exception, e:
707                errors["url_expr"] = "%s" % str(e)
708       
709        return errors
710   
711    def processErrors(self, errors, prefix='', sufix=''):
712        """ Add prefixes, sufixes to error ids
713            This is necessary during edit form validation,
714            because every edit form on the page has it's own sufix (id) """
715        if not (prefix or sufix):
716            return errors
717       
718        result = {}
719        for key, value in errors.items():
720            result['%s%s%s' % (prefix, key, sufix)] = value
721       
722        return result
723   
724    def parseEditForm(self, form):
725        """ Extract all needed fields from edit form """
726        # get original id and category
727        info = {}
728        id = form['orig_id']
729        category = form['category']
730       
731        # preprocess 'visible' field (checkbox needs special checking)
732        if form.has_key('visible_%s' % id):
733            form['visible_%s' % id] = True
734        else:
735            form['visible_%s' % id] = False
736       
737        # collect all action fields
738        for attr in ACTION_ATTRS:
739            info[attr] = form['%s_%s' % (attr, id)]
740       
741        return (id, category, info)
742   
743    def parseAddForm(self, form):
744        """ Extract all needed fields from add form """
745        info = {}
746        id = form['id']
747        category = form['category']
748       
749        # preprocess 'visible' field (checkbox needs special checking)
750        if form.has_key('visible') and form['visible']:
751            form['visible'] = True
752        else:
753            form['visible'] = False
754       
755        # fix expression fields
756        form['url_expr'] = self.fixExpression(form['url_expr'])
757       
758        # collect all action fields
759        for attr in ACTION_ATTRS:
760            info[attr] = form[attr]
761       
762        return (id, category, info)
763   
764    def getActionCategory(self, name):
765        portal_actions = getToolByName(self.context, 'portal_actions')
766        return portal_actions[name]
767   
768    def getOrCreateCategory(self, name):
769        """ Get or create (if necessary) category """
770        portal_actions = getToolByName(self.context, 'portal_actions')
771        if name not in map(lambda x: x.id, filter(lambda x: IActionCategory.providedBy(x), portal_actions.objectValues())):
772            portal_actions._setObject(name, ActionCategory(name))
773        return self.getActionCategory(name)
774   
775    def setSiteProperties(self, **kw):
776        """ Change site_properties """
777        site_properties = getToolByName(self.context, "portal_properties").site_properties
778        site_properties.manage_changeProperties(**kw)
779        return True
780   
781    #
782    # Utility methods for the kss actions management
783    #
784   
785    def kss_hideStatusMessage(self, ksscore):
786        """Hide Portal Status Message"""
787        ksscore.setStyle(ksscore.getHtmlIdSelector('kssPortalMessage'), 'display', 'none')
788   
789    def kss_validateAction(self, id, cat_name):
790        """ Check whether action with given id exists in cat_name category """
791        try:
792            category = self.getActionCategory(cat_name)
793        except Exception:
794            raise KSSExplicitError, "'%s' action category does not exist" % cat_name
795       
796        # extract action id from given list item id on client
797        action_id = self.sufix and id[len(self.prefix):-len(self.sufix)] or id[len(self.prefix):]
798        try:
799            action = category[action_id]
800        except Exception:
801            raise KSSExplicitError, "No '%s' action in '%s' category." % (action_id, cat_name)
802       
803        return (action_id, category, action)
804   
805    def kss_issueErrors(self, errors, editform=False, fields=ACTION_ATTRS):
806        """ Display error messages on the client """
807        ksscore = self.getCommandSet('core')
808        for field in fields:
809            self.kss_issueFieldError(ksscore, field, errors.get(field, False), editform)
810   
811    def kss_issueFieldError(self, ksscore, name, error, editform):
812        """ Issue this error message for the field """
813        if editform:
814            id = '%s%s%s' % (self.prefix, editform, self.sufix)
815            field_selector = ksscore.getCssSelector('#%s .edit-field-%s' % (id, UI_ATTRS.get(name, name)))
816            field_error_selector = ksscore.getCssSelector('#%s .edit-field-%s .error-container' % (id, UI_ATTRS.get(name, name)))
817        else:
818            field_selector = ksscore.getCssSelector('#addaction .field-%s' % UI_ATTRS.get(name, name))
819            field_error_selector = ksscore.getCssSelector('#addaction .field-%s .error-container' % UI_ATTRS.get(name, name))
820
821        if error:
822            ksscore.replaceInnerHTML(field_error_selector, _(error))
823            ksscore.addClass(field_selector, 'error')
824        else:
825            ksscore.clearChildNodes(field_error_selector)
826            ksscore.removeClass(field_selector, 'error')
827   
828    def kss_toggleCollapsible(self, selector, collapsed=None, expanded=None, collapse=None):
829        """ KSS Server command to add plonetabs-toggleCollapsible client action to response """
830        command = self.commands.addCommand('plonetabs-toggleCollapsible', selector)
831        if collapsed is not None:
832            data = command.addParam('collapsed', collapsed)
833        if expanded is not None:
834            data = command.addParam('expanded', expanded)
835        if collapse is not None:
836            data = command.addParam('collapse', collapse)
837   
838    def kss_resetForm(self, selector):
839        """ KSS Server command to reset form on client """
840        command = self.commands.addCommand('plonetabs-resetForm', selector)
841   
842    def kss_blur(self, selector):
843        """ KSS Server command to remove focus from input """
844        command = self.commands.addCommand('plonetabs-blur', selector)
845   
846    def kss_replaceOrInsert(self, selector, parentSelector, html, withKssSetup='True', alternativeHTML='', selectorType='',
847                                  position='', positionSelector='', positionSelectorType=''):
848        """ KSS Server command to execute replaceOrInsert client action """
849        command = self.commands.addCommand('plonetabs-replaceOrInsert', selector)
850        data = command.addParam('selector', parentSelector)
851        data = command.addHtmlParam('html', html)
852        data = command.addParam('withKssSetup', withKssSetup)
853        if alternativeHTML:
854            data = command.addHtmlParam('alternativeHTML', alternativeHTML)
855        if selectorType:
856            data = command.addParam('selectorType', selectorType)
857        if position:
858            data = command.addParam('position', position)
859        if positionSelector:
860            data = command.addParam('positionSelector', positionSelector)
861        if positionSelectorType:
862            data = command.addParam('positionSelectorType', positionSelectorType)
863   
864    def renderViewlet(self, manager, name):
865        if isinstance(manager, basestring):
866            manager = getMultiAdapter((self.context, self.request, self,), IViewletManager, name=manager)
867        renderer = getMultiAdapter((self.context, self.request, self, manager), IViewlet, name=name)
868        renderer = renderer.__of__(self.context)
869        renderer.update()
870        return renderer.render()
871   
872    #
873    # Basic API to work with portal actions tool in a more pleasent way
874    #
875   
876    def addAction(self, cat_name, data):
877        """ Create and add new action to category with given name """
878        id = data.pop('id')
879        category = self.getOrCreateCategory(cat_name)
880        action = Action(id, **data)
881        category._setObject(id, action)
882        return action
883   
884    def updateAction(self, id, cat_name, data):
885        """ Update action with given id and category """
886        new_id = data.pop('id')
887        category = self.getActionCategory(cat_name)
888       
889        # rename action if necessary
890        if id != new_id:
891            category.manage_renameObject(id, new_id)
892       
893        # get action
894        action = category[new_id]
895       
896        # update action properties
897        for attr in data.keys():
898            if data.has_key(attr):
899                action._setPropValue(attr, data[attr])
900       
901        return action
902   
903    def deleteAction(self, id, cat_name):
904        """ Delete action with given id from given category """
905        category = self.getActionCategory(cat_name)
906        category.manage_delObjects(ids=[id,])
907        return True
908   
909    def moveAction(self, id, cat_name, steps=0):
910        """ Move action by a given steps """
911        if steps != 0:
912            category = self.getActionCategory(cat_name)
913            if steps > 0:
914                category.moveObjectsUp([id,], steps)
915            else:
916                category.moveObjectsDown([id,], abs(steps))
917            return True
918        return False
919   
920    #
921    # KSS Methods that are used to update different parts of the page
922    # accordingly to category
923    #
924   
925    def updatePage(self, category):
926        """ Seek for according method in class and calls it if found
927            Example of making up method's name:
928                portal_tabs => updatePortalTabsPageSection """
929        method_name = 'update%sPageSection' % ''.join(map(lambda x: x.capitalize(), category.split('_')))
930        if hasattr(self, method_name):
931            getattr(self, method_name)()
932   
933    def updatePortalTabsPageSection(self):
934        """ Method for updating global-sections on client """
935        ksscore = self.getCommandSet("core")
936        self.kss_replaceOrInsert(ksscore.getHtmlIdSelector("portal-header"),
937                                 "portal-globalnav",
938                                 self.sections_template(),
939                                 withKssSetup='False',
940                                 selectorType='htmlid')
941   
942    def updateSiteActionsPageSection(self):
943        """ Method for updating site action on client """
944        ksscore = self.getCommandSet("core")
945        self.kss_replaceOrInsert(ksscore.getHtmlIdSelector("portal-header"),
946                                 "portal-siteactions",
947                                 self.renderViewlet("plone.portalheader", "plone.site_actions"),
948                                 withKssSetup='False',
949                                 selectorType='htmlid',
950                                 position="before",
951                                 positionSelector="portal-searchbox",
952                                 positionSelectorType="htmlid")
953       
954        #ksszope = self.getCommandSet("zope")
955        #ksszope.refreshViewlet(
956            #self.getCommandSet("core").getHtmlIdSelector("portal-siteactions"),
957            #"plone.portalheader",
958            #"plone.site_actions")
959   
960    def updateUserPageSection(self):
961        """ Method for updating site action on client """
962        ksszope = self.getCommandSet("zope")
963        ksszope.refreshViewlet(
964            self.getCommandSet("core").getHtmlIdSelector("portal-personaltools-wrapper"),
965            "plone.portaltop",
966            "plone.personal_bar")
Note: See TracBrowser for help on using the repository browser.