source: products/quintagroup.plonetabs/branches/plone4/quintagroup/plonetabs/browser/plonetabs.py @ 3081

Last change on this file since 3081 was 3081, checked in by gotcha, 13 years ago

fix adding portal_tabs; whitespace

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