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

Last change on this file since 478 was 478, checked in by crchemist, 18 years ago

Make compatible with Konqueror browser.

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