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

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

Fixed code according with pylint, pyflakes

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