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

Last change on this file since 162 was 162, checked in by mylan, 18 years ago

Add deleting discussion reply tests

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