source: products/quintagroup.plonetabs/branches/tests/quintagroup/plonetabs/browser/plonetabs.py @ 3402

Last change on this file since 3402 was 3402, checked in by potar, 12 years ago

Fixing pep8

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