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

Last change on this file since 798 was 798, checked in by chervol, 17 years ago

Installation procedure for Plone 2.1.x added

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