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

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

Updated HISTORY.txt.

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