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

Last change on this file since 775 was 750, checked in by piv, 17 years ago

branche for product with js implementation of stats for text fields

  • Property svn:eol-style set to native
File size: 42.3 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 adding form
442        self.kss_hideAddForm()
443       
444        # issue portal status message
445        self.kss_issueMessage(_(u"Category changed successfully."))
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            prefix = 'Generated'
453            if field == 'disable_nonfolderish_sections':
454                prefix += ' not folderish'
455            message = _(u"%s tabs switched on." % prefix)
456        else:
457            self.setSiteProperties(**{field: True})
458            prefix = 'Generated'
459            if field == 'disable_nonfolderish_sections':
460                prefix += ' not folderish'
461            message = _(u"%s tabs switched off." % prefix)
462       
463        # update client
464        ksscore = self.getCommandSet("core")
465        content = self.getGeneratedTabs()
466        ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector('roottabs'), content)
467       
468        # update global-sections viewlet
469        self.updatePortalTabsPageSection()
470       
471        # issue portal status message
472        self.kss_issueMessage(message)
473   
474    @kssaction
475    def kss_toggleRootsVisibility(self, id, checked='0'):
476        """Toggle visibility for portal root objects (exclude_from_nav)"""
477        portal = getMultiAdapter((aq_inner(self.context), self.request),
478            name='plone_portal_state').portal()
479       
480        # remove prefix, added for making ids on configlet unique ("roottabs_")
481        obj_id = id[len("roottabs_"):]
482       
483        if obj_id not in portal.objectIds():
484            raise KSSExplicitError, \
485                  "Object with %s id doesn't exist in portal root" % obj_id
486       
487        if checked == '1':
488            checked = True
489        else:
490            checked = False
491       
492        portal[obj_id].update(excludeFromNav=not checked)
493       
494        # update client
495        ksscore = self.getCommandSet("core")
496        if checked:
497            ksscore.removeClass(ksscore.getHtmlIdSelector(id),
498                value="invisible")
499            message = _(u"'${id}' object was included into navigation.",
500                        mapping={'id':obj_id})
501        else:
502            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
503            message = _(u"'${id}' object was excluded from navigation.",
504                        mapping={'id':obj_id})
505       
506        # update global-sections viewlet
507        self.updatePortalTabsPageSection()
508       
509        # issue portal status message
510        self.kss_issueMessage(message)
511   
512    @kssaction
513    def kss_toggleActionsVisibility(self, id, checked='0', cat_name=None):
514        """Toggle visibility for portal actions"""
515        # validate input
516        act_id, category, action = self.kss_validateAction(id, cat_name)
517        self.updateAction(act_id, cat_name,
518            {'id': act_id, 'visible': (checked == '1') or False})
519       
520        # update client
521        ksscore = self.getCommandSet("core")
522        if checked == '1':
523            ksscore.removeClass(ksscore.getHtmlIdSelector(id),
524                value="invisible")
525            message = _(u"'${id}' action is now visible.",
526                        mapping={'id':act_id})
527        else:
528            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
529            message = _(u"'${id}' action is now invisible.",
530                        mapping={'id':act_id})
531        self.updatePage(cat_name)
532   
533        # issue portal status message
534        self.kss_issueMessage(message)
535   
536    @kssaction
537    def kss_deleteAction(self, id, cat_name):
538        """Delete portal action with given id & category"""
539        # validate input
540        act_id, category, action = self.kss_validateAction(id, cat_name)
541        self.deleteAction(act_id, cat_name)
542       
543        # update client
544        ksscore = self.getCommandSet("core")
545        # XXX TODO: fade effect during removing, to do this
546        # we need kukit js action/command plugin
547        ksscore.deleteNode(ksscore.getHtmlIdSelector(id))
548       
549        # update different sections of page depending on actions category
550        self.updatePage(cat_name)
551       
552        # issue portal status message
553        self.kss_issueMessage(_(u"'${id}' action successfully removed.",
554                                mapping={'id':act_id}))
555   
556    @kssaction
557    def kss_addAction(self, cat_name):
558        """KSS method to add new portal action"""
559        # extract posted data
560        id, ie7bad_category, data = self.parseAddForm(self.request.form)
561       
562        # validate posted data
563        errors = self.validateActionFields(cat_name, data)
564       
565        # if not errors find (or create) category and set action to it
566        ksscore = self.getCommandSet('core')
567        kssplone = self.getCommandSet('plone')
568        if not errors:
569            action = self.addAction(cat_name, data)
570           
571            # update client
572            # add one more action to actions list
573            content = self.getActionsList(category=cat_name, tabs=[action,])
574            ksscore.insertHTMLAsLastChild(ksscore.getHtmlIdSelector('tabslist'),
575                 content)
576           
577            # update reorder controls
578            #self.kss_checkReorderControls(cat_name)
579           
580            # hide adding form
581            ksscore.removeClass(ksscore.getHtmlIdSelector('addaction'),
582                'adding')
583            self.kss_toggleCollapsible(
584                ksscore.getCssSelector('form[name=addaction_form] '
585                                       '.headerAdvanced'), collapse='true')
586           
587            # set client state var 'plonetabs-addingTitle' to empty
588            # string for correct id autogeneration functionality
589            ksscore.setStateVar('plonetabs-addingTitle', '')
590           
591            # reset adding form
592            self.kss_resetForm(ksscore.getHtmlIdSelector('addaction'))
593           
594            # remove focus from name input
595            self.kss_blur(ksscore.getHtmlIdSelector('actname'))
596           
597            message = _(u"'${id}' action successfully added.",
598                        mapping={'id':action.id})
599            msgtype = "info"
600           
601            # update page
602            self.updatePage(cat_name)
603        else:
604            # expand advanced section if there are errors in id or condition
605            if errors.has_key('id') or errors.has_key('available_expr'):
606                self.kss_toggleCollapsible(
607                    ksscore.getCssSelector('form[name=addaction_form] '
608                                           '.headerAdvanced'), collapse='false')
609           
610            message = _(u"Please correct the indicated errors.")
611            msgtype = "error"
612       
613        # update errors on client form
614        self.kss_issueErrors(errors)
615       
616        # issue portal status message
617        self.kss_issueMessage(message, msgtype)
618   
619    @kssaction
620    def kss_hideAddForm(self):
621        """"Hide Add form, reset it and remove error messages"""
622        # no server changes, only update client
623        ksscore = self.getCommandSet("core")
624       
625        # hide form itself
626        ksscore.removeClass(ksscore.getHtmlIdSelector('addaction'), 'adding')
627       
628        # collapse advanced section
629        self.kss_toggleCollapsible(
630            ksscore.getCssSelector('form[name=addaction_form] .headerAdvanced'),
631            collapse='true')
632       
633        # reset form inputs
634        self.kss_resetForm(ksscore.getHtmlIdSelector('addaction'))
635       
636        # set client state var 'plonetabs-addingTitle' to empty string for
637        # correct id autogeneration functionality
638        ksscore.setStateVar('plonetabs-addingTitle', '')
639       
640        # remove form errors if such exist
641        self.kss_issueErrors({})
642       
643        # issue portal status message
644        self.kss_issueMessage(_(u"Adding canceled."))
645   
646    @kssaction
647    def kss_showEditForm(self, id, cat_name):
648        """Show edit form for given action"""
649        act_id, category, action = self.kss_validateAction(id, cat_name)
650       
651        # fetch data
652        action_info = self.copyAction(action)
653        action_info["editing"] = True
654       
655        # update client
656        ksscore = self.getCommandSet("core")
657        content = self.getActionsList(category=cat_name, tabs=[action_info,])
658        ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content)
659       
660        # focus name field
661        ksscore.focus(
662            ksscore.getCssSelector("#%s input[name=title_%s]" % (id, act_id)))
663       
664        # issue portal status message
665        self.kss_issueMessage(_(u"Fill in required fields and click on Add."))
666   
667    @kssaction
668    def kss_hideEditForm(self, id, cat_name):
669        """Hide edit form for given action"""
670        act_id, category, action = self.kss_validateAction(id, cat_name)
671       
672        # update client
673        ksscore = self.getCommandSet("core")
674        content = self.getActionsList(category=cat_name, tabs=[action,])
675        ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content)
676       
677        # issue portal status message
678        self.kss_issueMessage(_(u"Changes discarded."))
679   
680    @kssaction
681    def kss_editAction(self):
682        """Update action's properties"""
683        id, cat_name, data = self.parseEditForm(self.request.form)
684       
685        # get category and action to edit
686        category = self.getActionCategory(cat_name)
687        action = category[id]
688       
689        # validate posted data
690        errors = self.validateActionFields(cat_name, data,
691            allow_dup=(id == data['id']))
692       
693        html_id = '%s%s%s' % (self.prefix, id, self.sufix)
694        ksscore = self.getCommandSet('core')
695        kssplone = self.getCommandSet('plone')
696        if not errors:
697            action = self.updateAction(id, cat_name, data)
698           
699            # update client
700            # replace action item with updated one
701            content = self.getActionsList(category=cat_name, tabs=[action,])
702            ksscore.replaceHTML(ksscore.getHtmlIdSelector(html_id), content)
703           
704            message = _(u"'${id}' action successfully updated.",
705                        mapping={'id':action.id})
706            msgtype = "info"
707           
708            # update page
709            self.updatePage(cat_name)
710        else:
711            # issue error messages
712            self.kss_issueErrors(errors, editform=id)
713           
714            # expand advanced section if there are errors in id,
715            # action url or condition
716            if errors.has_key('id') or errors.has_key('available_expr') or \
717                errors.has_key('url_expr'):
718                self.kss_toggleCollapsible(
719                    ksscore.getCssSelector('#%s .headerAdvanced' % html_id),
720                    collapse='false')
721           
722            message = _(u"Please correct the indicated errors.")
723            msgtype = "error"
724       
725        # issue portal status message
726        self.kss_issueMessage(message, msgtype)
727   
728    @kssaction
729    def kss_orderActions(self):
730        """Update actions order in the given category"""
731        form = self.request.form
732        cat_name = form['cat_name']
733        category = self.getActionCategory(cat_name)
734       
735        # decode URI components and collect ids from request
736        components = urllib.unquote(form['actions']).split('&')
737        if self.sufix == '':
738            ids = [component[len(self.prefix):] for component in components]
739        else:
740            ids = [component[len(self.prefix):-len(self.sufix)]
741                for component in components]
742       
743        # do actual sorting
744        category.moveObjectsByDelta(ids, -len(category.objectIds()))
745       
746        # update client
747        self.updatePage(cat_name)
748       
749        # issue portal status message
750        self.kss_issueMessage(_(u"Actions successfully sorted."))
751   
752    #
753    # Utility Methods
754    #
755   
756    def fixExpression(self, expr):
757        """Fix expression appropriatly for tal format"""
758        if expr.find('/') == 0:
759            return 'string:${portal_url}%s' % expr
760        elif re.compile('^(ht|f)tps?\:', re.I).search(expr):
761            return 'string:%s' % expr
762        #elif re.compile('^(python:|string:|not:|exists:|nocall:|path:)',
763                        #re.I).search(expr):
764            #return expr
765        elif expr.find(':') != -1:
766            return expr
767        else:
768            return 'string:${object_url}/%s' % expr
769   
770    def copyAction(self, action):
771        """Copy action to dictionary"""
772        action_info = {'description':action.description}
773        for attr in ACTION_ATTRS:
774            action_info[attr] = getattr(action, attr)
775        return action_info
776   
777    def validateActionFields(self, cat_name, data, allow_dup=False):
778        """Check action fields on validity"""
779        errors = {}
780       
781        if allow_dup:
782            # create dummy category to avoid id
783            # duplication during action update
784            category = ActionCategory(cat_name)
785        else:
786            # get or create (if necessary) actions category
787            category = self.getOrCreateCategory(cat_name)
788       
789        # validate action id
790        chooser = INameChooser(category)
791        try:
792            chooser.checkName(data['id'], self.context)
793        except Exception, e:
794            errors['id'] = "%s" % str(e)
795       
796        # validate action name
797        if not data['title'].strip():
798            errors['title'] = 'Empty or invalid title specified'
799       
800        # validate condition expression
801        if data['available_expr']:
802            try:
803                Expression(data['available_expr'])
804            except Exception, e:
805                errors["available_expr"] = "%s" % str(e)
806       
807        # validate action expression
808        if data['url_expr']:
809            try:
810                Expression(data['url_expr'])
811            except Exception, e:
812                errors["url_expr"] = "%s" % str(e)
813       
814        return errors
815   
816    def processErrors(self, errors, prefix='', sufix=''):
817        """Add prefixes, sufixes to error ids
818        This is necessary during edit form validation,
819        because every edit form on the page has it's own sufix (id)
820        """
821        if not (prefix or sufix):
822            return errors
823       
824        result = {}
825        for key, value in errors.items():
826            result['%s%s%s' % (prefix, key, sufix)] = value
827       
828        return result
829   
830    def parseEditForm(self, form):
831        """Extract all needed fields from edit form"""
832        # get original id and category
833        info = {}
834        id = form['orig_id']
835        category = form['category']
836       
837        # preprocess 'visible' field (checkbox needs special checking)
838        if form.has_key('visible_%s' % id):
839            form['visible_%s' % id] = True
840        else:
841            form['visible_%s' % id] = False
842       
843        # collect all action fields
844        for attr in ACTION_ATTRS:
845            info[attr] = form['%s_%s' % (attr, id)]
846       
847        return (id, category, info)
848   
849    def parseAddForm(self, form):
850        """Extract all needed fields from add form"""
851        info = {}
852        id = form['id']
853        category = form['category']
854       
855        # preprocess 'visible' field (checkbox needs special checking)
856        if form.has_key('visible') and form['visible']:
857            form['visible'] = True
858        else:
859            form['visible'] = False
860       
861        # fix expression fields
862        form['url_expr'] = self.fixExpression(form['url_expr'])
863       
864        # collect all action fields
865        for attr in ACTION_ATTRS:
866            info[attr] = form[attr]
867       
868        return (id, category, info)
869   
870    def getActionCategory(self, name):
871        portal_actions = getToolByName(self.context, 'portal_actions')
872        return portal_actions[name]
873   
874    def getOrCreateCategory(self, name):
875        """Get or create (if necessary) category"""
876        portal_actions = getToolByName(self.context, 'portal_actions')
877        if name not in map(lambda x: x.id,
878                           filter(lambda x: IActionCategory.providedBy(x),
879                                  portal_actions.objectValues())):
880            portal_actions._setObject(name, ActionCategory(name))
881        return self.getActionCategory(name)
882   
883    def setSiteProperties(self, **kw):
884        """Change site_properties"""
885        site_properties = getToolByName(self.context,
886            "portal_properties").site_properties
887        site_properties.manage_changeProperties(**kw)
888        return True
889   
890    #
891    # Utility methods for the kss actions management
892    #
893   
894    def kss_validateAction(self, id, cat_name):
895        """Check whether action with given id exists in cat_name category"""
896        try:
897            category = self.getActionCategory(cat_name)
898        except Exception:
899            raise KSSExplicitError, \
900                  "'%s' action category does not exist" % cat_name
901       
902        # extract action id from given list item id on client
903        action_id = self.sufix and id[len(self.prefix):-len(self.sufix)] or \
904                    id[len(self.prefix):]
905        try:
906            action = category[action_id]
907        except Exception:
908            raise KSSExplicitError, \
909                  "No '%s' action in '%s' category." % (action_id, cat_name)
910       
911        return (action_id, category, action)
912   
913    def kss_issueErrors(self, errors, editform=False, fields=ACTION_ATTRS):
914        """Display error messages on the client"""
915        ksscore = self.getCommandSet('core')
916        for field in fields:
917            self.kss_issueFieldError(ksscore, field,
918                                     errors.get(field, False), editform)
919   
920    def kss_issueFieldError(self, ksscore, name, error, editform):
921        """Issue this error message for the field"""
922        if editform:
923            id = '%s%s%s' % (self.prefix, editform, self.sufix)
924            field_selector = ksscore.getCssSelector('#%s .edit-field-%s' % (id,
925                UI_ATTRS.get(name, name)))
926            field_error_selector = ksscore.getCssSelector('#%s .edit-field-%s '
927                '.error-container' % (id, UI_ATTRS.get(name, name)))
928        else:
929            field_selector = ksscore.getCssSelector('form[name=addaction_form] '
930                '.field-%s' % UI_ATTRS.get(name, name))
931            field_error_selector = ksscore.getCssSelector('form[name='
932                'addaction_form] .field-%s '
933                '.error-container' % UI_ATTRS.get(name, name))
934
935        if error:
936            ksscore.replaceInnerHTML(field_error_selector, _(error))
937            ksscore.addClass(field_selector, 'error')
938        else:
939            ksscore.clearChildNodes(field_error_selector)
940            ksscore.removeClass(field_selector, 'error')
941   
942    def kss_toggleCollapsible(self, selector, collapsed=None,
943                              expanded=None, collapse=None):
944        """KSS Server command to add plonetabs-toggleCollapsible client
945        action to response
946        """
947        command = self.commands.addCommand('plonetabs-toggleCollapsible',
948                                           selector)
949        if collapsed is not None:
950            data = command.addParam('collapsed', collapsed)
951        if expanded is not None:
952            data = command.addParam('expanded', expanded)
953        if collapse is not None:
954            data = command.addParam('collapse', collapse)
955   
956    def kss_resetForm(self, selector):
957        """KSS Server command to reset form on client"""
958        command = self.commands.addCommand('plonetabs-resetForm', selector)
959   
960    def kss_blur(self, selector):
961        """KSS Server command to remove focus from input"""
962        command = self.commands.addCommand('plonetabs-blur', selector)
963   
964    def kss_replaceOrInsert(self, selector, parentSelector, html,
965                            withKssSetup='True', alternativeHTML='',
966                            selectorType='', position='', positionSelector='',
967                            positionSelectorType=''):
968        """KSS Server command to execute replaceOrInsert client action"""
969        command = self.commands.addCommand('plonetabs-replaceOrInsert',
970            selector)
971        data = command.addParam('selector', parentSelector)
972        data = command.addHtmlParam('html', html)
973        data = command.addParam('withKssSetup', withKssSetup)
974        if alternativeHTML:
975            data = command.addHtmlParam('alternativeHTML', alternativeHTML)
976        if selectorType:
977            data = command.addParam('selectorType', selectorType)
978        if position:
979            data = command.addParam('position', position)
980        if positionSelector:
981            data = command.addParam('positionSelector', positionSelector)
982        if positionSelectorType:
983            data = command.addParam('positionSelectorType',
984                                    positionSelectorType)
985   
986    def kss_issueMessage(self, message, msgtype="info"):
987        """"Issues portal status message and removes it afte 10 seconds"""
988        ksscore = self.getCommandSet('core')
989        self.getCommandSet('plone').issuePortalMessage(message, msgtype=msgtype)
990        self.kss_timeout(
991            ksscore.getHtmlIdSelector('kssPortalMessage'),
992            delay='10000', repeat='false',
993            cmd_name='setStyle', name='display', value='none'
994        )
995   
996    def kss_timeout(self, selector, **kw):
997        """KSS Server command to execute plonetabs-timeout client action"""
998        command = self.commands.addCommand('plonetabs-timeout', selector, **kw)
999   
1000    def renderViewlet(self, manager, name):
1001        if isinstance(manager, basestring):
1002            manager = getMultiAdapter((self.context, self.request, self,),
1003                                      IViewletManager, name=manager)
1004        renderer = getMultiAdapter((self.context, self.request, self, manager),
1005                                   IViewlet, name=name)
1006        renderer = renderer.__of__(self.context)
1007        renderer.update()
1008        return renderer.render()
1009   
1010    #
1011    # Basic API to work with portal actions tool in a more pleasent way
1012    #
1013   
1014    def addAction(self, cat_name, data):
1015        """Create and add new action to category with given name"""
1016        id = data.pop('id')
1017        category = self.getOrCreateCategory(cat_name)
1018        action = Action(id, **data)
1019        category._setObject(id, action)
1020        return action
1021   
1022    def updateAction(self, id, cat_name, data):
1023        """Update action with given id and category"""
1024        new_id = data.pop('id')
1025        category = self.getActionCategory(cat_name)
1026       
1027        # rename action if necessary
1028        if id != new_id:
1029            category.manage_renameObject(id, new_id)
1030       
1031        # get action
1032        action = category[new_id]
1033       
1034        # update action properties
1035        for attr in data.keys():
1036            if data.has_key(attr):
1037                action._setPropValue(attr, data[attr])
1038       
1039        return action
1040   
1041    def deleteAction(self, id, cat_name):
1042        """Delete action with given id from given category"""
1043        category = self.getActionCategory(cat_name)
1044        category.manage_delObjects(ids=[id,])
1045        return True
1046   
1047    def moveAction(self, id, cat_name, steps=0):
1048        """Move action by a given steps"""
1049        if steps != 0:
1050            category = self.getActionCategory(cat_name)
1051            if steps > 0:
1052                category.moveObjectsUp([id,], steps)
1053            else:
1054                category.moveObjectsDown([id,], abs(steps))
1055            return True
1056        return False
1057   
1058    #
1059    # KSS Methods that are used to update different parts of the page
1060    # accordingly to category
1061    #
1062   
1063    def updatePage(self, category):
1064        """Seek for according method in class and calls it if found
1065        Example of making up method's name:
1066            portal_tabs => updatePortalTabsPageSection
1067        """
1068        method_name = 'update%sPageSection' % ''.join(
1069            map(lambda x: x.capitalize(), category.split('_')))
1070        if hasattr(self, method_name):
1071            getattr(self, method_name)()
1072   
1073    def updatePortalTabsPageSection(self):
1074        """Method for updating global-sections on client"""
1075        ksscore = self.getCommandSet("core")
1076        self.kss_replaceOrInsert(ksscore.getHtmlIdSelector("portal-header"),
1077                                 "portal-globalnav",
1078                                 self.sections_template(),
1079                                 withKssSetup='False',
1080                                 selectorType='htmlid')
1081   
1082    def updateSiteActionsPageSection(self):
1083        """Method for updating site action on client"""
1084        ksscore = self.getCommandSet("core")
1085        self.kss_replaceOrInsert(ksscore.getHtmlIdSelector("portal-header"),
1086                                 "portal-siteactions",
1087                                 self.renderViewlet("plone.portalheader",
1088                                                    "plone.site_actions"),
1089                                 withKssSetup='False',
1090                                 selectorType='htmlid',
1091                                 position="before",
1092                                 positionSelector="portal-searchbox",
1093                                 positionSelectorType="htmlid")
1094       
1095        #ksszope = self.getCommandSet("zope")
1096        #ksszope.refreshViewlet(
1097            #self.getCommandSet("core").getHtmlIdSelector("portal-siteactions"),
1098            #"plone.portalheader",
1099            #"plone.site_actions")
1100   
1101    def updateUserPageSection(self):
1102        """Method for updating site action on client"""
1103        ksszope = self.getCommandSet("zope")
1104        ksszope.refreshViewlet(
1105            self.getCommandSet("core").getHtmlIdSelector(
1106                "portal-personaltools-wrapper"),
1107            "plone.portaltop",
1108            "plone.personal_bar")
Note: See TracBrowser for help on using the repository browser.