SelectionTool.py 80 KB
Newer Older
1
# -*- coding: utf-8 -*-
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2 3
##############################################################################
#
4
# Copyright (c) 2002,2007 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
6 7
#
# WARNING: This program as such is intended to be used by professional
8
# programmers who take the whole responsibility of assessing all potential
Jean-Paul Smets's avatar
Jean-Paul Smets committed
9 10
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
11
# guarantees and support are strongly adviced to contract a Free Software
Jean-Paul Smets's avatar
Jean-Paul Smets committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

30 31
"""
  ERP5 portal_selection tool.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33 34
"""

from OFS.SimpleItem import SimpleItem
35
from Products.ERP5Type.Globals import InitializeClass, DTMLFile, PersistentMapping, get_request
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36
from AccessControl import ClassSecurityInfo
37
from Products.ERP5Type.Tool.BaseTool import BaseTool
Jean-Paul Smets's avatar
Jean-Paul Smets committed
38
from Products.ERP5Type import Permissions as ERP5Permissions
39
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40
from Products.ERP5Form import _dtmldir
41
from Products.ERP5Form.Selection import Selection, DomainSelection
42
from ZPublisher.HTTPRequest import FileUpload
43
from hashlib import md5
44
import string, re
45
from urlparse import urlsplit, urlunsplit
46
from zLOG import LOG, INFO, WARNING
47
from Acquisition import aq_base
48
from Products.ERP5Type.Message import translateString
49
import warnings
50

51

52 53
_MARKER = []

Jean-Paul Smets's avatar
Jean-Paul Smets committed
54 55 56
class SelectionError( Exception ):
    pass

57
class SelectionTool( BaseTool, SimpleItem ):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
58 59 60 61 62 63 64 65
    """
      The SelectionTool object is the place holder for all
      methods and algorithms related to persistent selections
      in ERP5.
    """

    id              = 'portal_selections'
    meta_type       = 'ERP5 Selections'
66
    portal_type     = 'Selection Tool'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
67 68 69 70 71 72 73
    security = ClassSecurityInfo()

    #
    #   ZMI methods
    #
    manage_options = ( ( { 'label'      : 'Overview'
                         , 'action'     : 'manage_overview'
74 75
                         },
                         { 'label'      : 'View Selections'
76
                         , 'action'     : 'manage_viewSelections'
77 78 79
                         },
                         { 'label'      : 'Configure'
                         , 'action'     : 'manage_configure'
80
                         } ))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
81 82 83

    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_overview' )
84
    manage_overview = DTMLFile( 'explainSelectionTool', _dtmldir )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
85

86
    security.declareProtected( ERP5Permissions.ManagePortal
87 88
                             , 'manage_viewSelections' )
    manage_viewSelections = DTMLFile( 'SelectionTool_manageViewSelections', _dtmldir )
89

90 91 92 93
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_configure' )
    manage_configure = DTMLFile( 'SelectionTool_configure', _dtmldir )

94 95 96 97 98 99 100 101 102 103 104
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_deleteSelectionForUser' )
    def manage_deleteSelectionForUser(self, selection_name, user_id, REQUEST=None):
      """
        Delete a specified selection
      """
      self._deleteSelectionForUserFromContainer(selection_name, user_id)
      if REQUEST is not None:
        return REQUEST.RESPONSE.redirect('%s/%s' %
                (self.absolute_url(), 'manage_viewSelections'))

105 106 107 108 109 110 111 112 113 114 115
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_deleteSelection' )
    def manage_deleteSelection(self, selection_name, REQUEST=None):
      """
        Relete a specified selection
      """
      self._deleteSelectionFromContainer(selection_name)
      if REQUEST is not None:
        return REQUEST.RESPONSE.redirect('%s/%s' %
                (self.absolute_url(), 'manage_viewSelections'))

116 117 118 119 120 121 122 123 124 125 126
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_deleteGlobalSelection' )
    def manage_deleteGlobalSelection(self, selection_name, REQUEST=None):
      """
        Relete a specified selection
      """
      self._deleteGlobalSelectionFromContainer(selection_name)
      if REQUEST is not None:
        return REQUEST.RESPONSE.redirect('%s/%s' %
                (self.absolute_url(), 'manage_viewSelections'))

127
    # storages of SelectionTool
128 129 130 131 132 133 134 135 136 137 138
    security.declareProtected(ERP5Permissions.ManagePortal
                              , 'getStorageItemList')
    def getStorageItemList(self):
      """Return the list of available storages
      """
      #storage_item_list = [('Persistent Mapping', 'selection_data',)]
      #list of tuple may fail dtml code: zope/documenttemplate/dt_in.py +578
      storage_item_list = [['Persistent Mapping', 'selection_data']]
      memcached_plugin_list = self.portal_memcached.contentValues(portal_type='Memcached Plugin', sort_on='int_index')
      storage_item_list.extend([['/'.join((mp.getParentValue().getTitle(), mp.getTitle(),)), mp.getRelativeUrl()] for mp in memcached_plugin_list])
      return storage_item_list
139

140
    security.declareProtected(ERP5Permissions.ModifyPortalContent, 'clearCachedContainer')
141 142 143 144 145 146 147 148 149 150 151 152 153
    def clearCachedContainer(self, is_anonymous=False):
      """
      Clear Container currently being used for Selection Tool because either the
      storage has changed or the its settings
      """
      if is_anonymous:
        container_id = '_v_anonymous_selection_container'
      else:
        container_id = '_v_selection_container'

      if getattr(aq_base(self), container_id, None):
        delattr(self, container_id)

154
    security.declareProtected( ERP5Permissions.ManagePortal, 'setStorage')
155
    def setStorage(self, storage, anonymous_storage=None, RESPONSE=None):
156 157 158
      """
        Set the storage of Selection Tool.
      """
159 160
      if storage in [item[1] for item in self.getStorageItemList()]:
        self.storage = storage
161
        self.clearCachedContainer()
162
      else:
163 164 165 166
        raise ValueError, 'Given storage type (%s) is now supported.' % (storage,)
      anonymous_storage = anonymous_storage or None
      if anonymous_storage in [item[1] for item in self.getStorageItemList()] + [None]:
        self.anonymous_storage = anonymous_storage
167
        self.clearCachedContainer(is_anonymous=True)
168 169
      else:
        raise ValueError, 'Given storage type (%s) is now supported.' % (anonymous_storage,)
170 171 172
      if RESPONSE is not None:
        RESPONSE.redirect('%s/manage_configure' % (self.absolute_url()))

173
    security.declareProtected( ERP5Permissions.ManagePortal, 'getStorage')
174
    def getStorage(self, default='selection_data'):
175 176
      """return the selected storage
      """
177
      storage = getattr(aq_base(self), 'storage', default)
178
      if storage is not default:
179 180 181 182 183
        #Backward compatibility
        if storage == 'Persistent Mapping':
          storage = 'selection_data'
        elif storage == 'Memcached Tool':
          memcached_plugin_list = self.portal_memcached.contentValues(portal_type='Memcached Plugin', sort_on='int_index')
184 185 186 187
          if len(memcached_plugin_list):
            storage = memcached_plugin_list[0].getRelativeUrl()
          else:
            storage = 'selection_data'
188 189
      return storage

190 191 192 193 194 195
    security.declareProtected( ERP5Permissions.ManagePortal, 'getAnonymousStorage')
    def getAnonymousStorage(self, default=None):
      """return the selected storage
      """
      return getattr(aq_base(self), 'anonymous_storage', default)

Vincent Pelletier's avatar
Vincent Pelletier committed
196 197
    def _redirectToOriginalForm(self, REQUEST=None, form_id=None, dialog_id=None,
                                query_string=None,
198
                                no_reset=False, no_report_depth=False):
Vincent Pelletier's avatar
Vincent Pelletier committed
199 200
      """Redirect to the original form or dialog, using the information given
         as parameters.
201 202
         (Actually does not redirect  in the HTTP meaning because of URL
         limitation problems.)
Vincent Pelletier's avatar
Vincent Pelletier committed
203 204 205

         DEPRECATED parameters :
         query_string is used to transmit parameters from caller to callee.
206
         If no_reset is True, replace reset parameters with noreset.
Vincent Pelletier's avatar
Vincent Pelletier committed
207 208
         If no_report_depth is True, replace report_depth parameters with
         noreport_depth.
209 210 211 212
      """
      if REQUEST is None:
        return

Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
213 214 215 216 217 218 219
      form = REQUEST.form
      if no_reset and form.has_key('reset'):
        form['noreset'] = form['reset'] # Kept for compatibility - might no be used anymore
        del form['reset']
      if no_report_depth and form.has_key('report_depth'):
        form['noreport_depth'] = form['report_depth'] # Kept for compatibility - might no be used anymore
        del form['report_depth']
Vincent Pelletier's avatar
Vincent Pelletier committed
220

221
      if query_string is not None:
222 223
        warnings.warn('DEPRECATED: _redirectToOriginalForm got called with a query_string. The variables must be passed in REQUEST.form.',
                      DeprecationWarning)
224
      context = REQUEST['PARENTS'][0]
Vincent Pelletier's avatar
Vincent Pelletier committed
225
      form_id = dialog_id or REQUEST.get('dialog_id', None) or form_id or REQUEST.get('form_id', 'view')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
226
      return getattr(context, form_id)()
227

228 229
    security.declareProtected(ERP5Permissions.View, 'getSelectionNameList')
    def getSelectionNameList(self, context=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
230 231 232
      """
        Returns the selection names of the current user.
      """
233
      return sorted(self._getSelectionNameListFromContainer())
234

235 236 237 238 239 240 241 242
    # backward compatibility
    security.declareProtected(ERP5Permissions.View, 'getSelectionNames')
    def getSelectionNames(self, context=None, REQUEST=None):
      warnings.warn("getSelectionNames() is deprecated.\n"
                    "Please use getSelectionNameList() instead.",
                    DeprecationWarning)
      return self.getSelectionNameList(context, REQUEST)

243
    security.declareProtected(ERP5Permissions.View, 'callSelectionFor')
244
    def callSelectionFor(self, selection_name, method=None, context=None,
245 246 247
                                               REQUEST=None, params=None):
      """
      Calls the selection and return the list of selected documents
248
      or objects. Seledction method, context and parameters may be
249 250 251 252 253 254 255 256 257
      overriden in a non persistent way.

      selection_name -- the name of the selectoin (string)

      method -- optional method (callable) or method path (string)
                to use instead of the persistent selection method

      context -- optional context to call the selection method on

258
      REQUEST -- optional REQUEST parameters (not used, only to
259 260 261 262 263 264 265 266
                 provide API compatibility)

      params -- optional parameters which can be used to override
                default params

      TODO: is it acceptable to keep method in the API at this level
            for security reasons (XXX-JPS)
      """
267 268 269 270
      if context is None: context = self
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is None:
        return None
271
      return selection(method=method, context=context, REQUEST=REQUEST, params=params)
272

273 274 275 276 277 278 279 280 281 282 283
    def _getRequest(self, REQUEST=None):
      if REQUEST is None:
        REQUEST = getattr(self, 'REQUEST', None)
      return REQUEST

    def _getSelectionKeyFromRequest(self, selection_name, REQUEST):
      REQUEST = self._getRequest(REQUEST=REQUEST)
      if REQUEST is not None:
        return REQUEST.get('%s_selection_key' % selection_name, None) or \
          REQUEST.get('selection_key', None)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
284 285 286 287 288
    security.declareProtected(ERP5Permissions.View, 'getSelectionFor')
    def getSelectionFor(self, selection_name, REQUEST=None):
      """
        Returns the selection instance for a given selection_name
      """
289 290
      if isinstance(selection_name, (tuple, list)):
        selection_name = selection_name[0]
291 292
      if not selection_name:
        return None
293
      selection = self._getSelectionFromContainer(selection_name)
294
      if selection is None and self.isAnonymous():
295 296 297 298
        selection_key = self._getSelectionKeyFromRequest(selection_name, REQUEST)
        if selection_key is not None:
          selection = self.getAnonymousSelection(selection_key, selection_name)
          self._setSelectionToContainer(selection_name, selection)
299 300
      if selection is not None:
        return selection.__of__(self)
301

302 303 304
    def __getitem__(self, key):
        return self.getSelectionParamsFor(key)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
305 306 307 308 309
    security.declareProtected(ERP5Permissions.View, 'setSelectionFor')
    def setSelectionFor(self, selection_name, selection_object, REQUEST=None):
      """
        Sets the selection instance for a given selection_name
      """
310 311
      if not selection_name:
        return
312 313 314 315 316 317
      if not (selection_object is None or
              selection_name == selection_object.name):
        LOG('SelectionTool', WARNING,
            "Selection not set: new Selection name ('%s') differs from existing one ('%s')" % \
            (selection_name,
             selection_object.name))
318
      elif self.getSelectionFor(selection_name, REQUEST=REQUEST) != selection_object:
319
        self._setSelectionToContainer(selection_name, selection_object)
320
      if selection_object is None and self.isAnonymous():
321 322 323 324 325 326
        REQUEST = self._getRequest(REQUEST=REQUEST)
        for key in ('%s_selection_key' % selection_name, 'selection_key'):
          try:
            del REQUEST.form[key]
          except KeyError:
            pass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
327

328 329
    security.declareProtected(ERP5Permissions.View, 'getSelectionParamsFor')
    def getSelectionParamsFor(self, selection_name, params=None, REQUEST=None):
330 331 332
      """
        Returns the params in the selection
      """
333 334
      if params is None:
        params = {}
335 336
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
337
        if selection.params:
338
          return selection.getParams()
339
      return params
340 341

    # backward compatibility
342 343
    security.declareProtected(ERP5Permissions.View, 'getSelectionParams')
    getSelectionParams = getSelectionParamsFor
344

Jean-Paul Smets's avatar
Jean-Paul Smets committed
345 346 347 348 349 350
    security.declareProtected(ERP5Permissions.View, 'setSelectionParamsFor')
    def setSelectionParamsFor(self, selection_name, params, REQUEST=None):
      """
        Sets the selection params for a given selection_name
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
351
      if selection_object is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
352 353
        selection_object.edit(params=params)
      else:
354
        selection_object = Selection(selection_name, params=params)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
355 356
      self.setSelectionFor(selection_name, selection_object, REQUEST)

357
    security.declareProtected(ERP5Permissions.View, 'getSelectionDomainDictFor')
358 359 360 361 362 363 364
    def getSelectionDomainDictFor(self, selection_name, REQUEST=None):
      """
        Returns the Domain dict for a given selection_name
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        try:
365
          return selection.getDomain().asDomainDict()
366 367 368
        except AttributeError:
          return {}

369
    security.declareProtected(ERP5Permissions.View, 'getSelectionReportDictFor')
370 371 372 373 374 375 376
    def getSelectionReportDictFor(self, selection_name, REQUEST=None):
      """
        Returns the Report dict for a given selection_name
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        try:
377
          return selection.getReport().asDomainDict()
378 379 380
        except AttributeError:
          return {}

Jean-Paul Smets's avatar
Jean-Paul Smets committed
381 382 383
    security.declareProtected(ERP5Permissions.View, 'setSelectionCheckedUidsFor')
    def setSelectionCheckedUidsFor(self, selection_name, checked_uids, REQUEST=None):
      """
384
        Sets the checked uids for a given selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
385 386 387 388 389
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
        selection_object.edit(checked_uids=checked_uids)
      else:
390
        selection_object = Selection(selection_name, checked_uids=checked_uids)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
391 392
      self.setSelectionFor(selection_name, selection_object, REQUEST)

393
    security.declareProtected(ERP5Permissions.View, 'updateSelectionCheckedUidList')
394 395
    def updateSelectionCheckedUidList(self, selection_name, listbox_uid, uids, REQUEST=None):
      """
396 397
        Updates the unchecked uids(listbox_uids) and checked uids (uids)
        for a given selection_name
398 399 400 401 402 403 404 405
      """
      if listbox_uid is None:
        listbox_uid = []
      if uids is None:
        uids = []
      self.uncheckAll(selection_name,listbox_uid,REQUEST=REQUEST)
      self.checkAll(selection_name,uids,REQUEST=REQUEST)

406 407 408
    security.declareProtected(ERP5Permissions.View, 'getSelectionCheckedUidsFor')
    def getSelectionCheckedUidsFor(self, selection_name, REQUEST=None):
      """
409
        Returns the checked uids for a given selection_name
410 411 412
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
413
        return selection_object.getCheckedUids()
414 415 416
      return []

    security.declareProtected(ERP5Permissions.View, 'checkAll')
417
    def checkAll(self, list_selection_name, listbox_uid=[], REQUEST=None,
418
                 query_string=None, form_id=None):
419
      """
420
        Check uids in a given listbox_uid list for a given list_selection_name
421
      """
422
      selection_object = self.getSelectionFor(list_selection_name, REQUEST)
423 424
      if selection_object:
        selection_uid_dict = {}
425
        for uid in selection_object.checked_uids:
426 427
          selection_uid_dict[uid] = 1
        for uid in listbox_uid:
428 429
          try:
            selection_uid_dict[int(uid)] = 1
430
          except (ValueError, TypeError):
431
            selection_uid_dict[uid] = 1
432
        self.setSelectionCheckedUidsFor(list_selection_name, selection_uid_dict.keys(), REQUEST=REQUEST)
433 434 435
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
436 437

    security.declareProtected(ERP5Permissions.View, 'uncheckAll')
438
    def uncheckAll(self, list_selection_name, listbox_uid=[], REQUEST=None,
439
                   query_string=None, form_id=None):
440
      """
441
        Uncheck uids in a given listbox_uid list for a given list_selection_name
442
      """
443
      selection_object = self.getSelectionFor(list_selection_name, REQUEST)
444 445
      if selection_object:
        selection_uid_dict = {}
446
        for uid in selection_object.checked_uids:
447 448
          selection_uid_dict[uid] = 1
        for uid in listbox_uid:
449 450
          try:
            if selection_uid_dict.has_key(int(uid)): del selection_uid_dict[int(uid)]
451
          except (ValueError, TypeError):
452
            if selection_uid_dict.has_key(uid): del selection_uid_dict[uid]
453
        self.setSelectionCheckedUidsFor(list_selection_name, selection_uid_dict.keys(), REQUEST=REQUEST)
454 455 456
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
457

Jean-Paul Smets's avatar
Jean-Paul Smets committed
458 459 460 461 462 463 464
    security.declareProtected(ERP5Permissions.View, 'getSelectionListUrlFor')
    def getSelectionListUrlFor(self, selection_name, REQUEST=None):
      """
        Returns the URL of the list mode of selection instance
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
465
        url = selection.getListUrl()
466
        if self.isAnonymous() and '?' in url:
467 468
          url += '&selection_key=%s' % self._getSelectionKeyFromRequest(selection_name, REQUEST)
        return url
Jean-Paul Smets's avatar
Jean-Paul Smets committed
469 470
      else:
        return None
471

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    security.declareProtected(ERP5Permissions.View, 'getSelectionInvertModeFor')
    def getSelectionInvertModeFor(self, selection_name, REQUEST=None):
      """Get the 'invert_mode' parameter of a selection.
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        return selection.isInvertMode()
      return 0

    security.declareProtected(ERP5Permissions.View, 'setSelectionInvertModeFor')
    def setSelectionInvertModeFor(self, selection_name,
                                  invert_mode, REQUEST=None):
      """Change the 'invert_mode' parameter of a selection.
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        selection.edit(invert_mode=invert_mode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
489

490
    security.declareProtected(ERP5Permissions.View, 'getSelectionInvertModeUidListFor')
491 492 493 494 495 496 497 498
    def getSelectionInvertModeUidListFor(self, selection_name, REQUEST=None):
      """Get the 'invert_mode' parameter of a selection.
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        return selection.getInvertModeUidList()
      return 0

499 500 501 502 503 504 505 506 507
    security.declareProtected(ERP5Permissions.View, 'getSelectionIndexFor')
    def getSelectionIndexFor(self, selection_name, REQUEST=None):
      """Get the 'index' parameter of a selection.
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        return selection.getIndex()
      return None

Jean-Paul Smets's avatar
Jean-Paul Smets committed
508 509 510 511 512 513 514
    security.declareProtected(ERP5Permissions.View, 'setSelectionToIds')
    def setSelectionToIds(self, selection_name, selection_uids, REQUEST=None):
      """
        Sets the selection to a small list of uids of documents
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
515
        selection.edit(invert_mode=1, uids=selection_uids, checked_uids=selection_uids)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
516 517

    security.declareProtected(ERP5Permissions.View, 'setSelectionToAll')
518 519
    def setSelectionToAll(self, selection_name, REQUEST=None,
                          reset_domain_tree=False, reset_report_tree=False):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
520 521 522 523 524
      """
        Resets the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
525
        selection.edit(invert_mode=0, params={}, checked_uids=[], report_opened=1)
526
        if reset_domain_tree:
527
          selection.edit(domain=None, domain_path=None, domain_list=None)
528
        if reset_report_tree:
529
          selection.edit(report=None, report_path=None, report_list=None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
530 531 532 533 534 535 536 537 538 539 540

    security.declareProtected(ERP5Permissions.View, 'setSelectionSortOrder')
    def setSelectionSortOrder(self, selection_name, sort_on, REQUEST=None):
      """
        Defines the sort order of the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        selection.edit(sort_on=sort_on)

    security.declareProtected(ERP5Permissions.View, 'setSelectionQuickSortOrder')
541
    def setSelectionQuickSortOrder(self, selection_name=None, sort_on=None, REQUEST=None,
542
                                   query_string=None, form_id=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
543 544 545 546
      """
        Defines the sort order of the selection directly from the listbox
        In this method, sort_on is just a string that comes from url
      """
547 548
      # selection_name, sort_on and form_id params are kept only for bacward compatibilty
      # as some test call setSelectionQuickSortOrder in url with these params
Aurel's avatar
Aurel committed
549
      listbox_id = None
550 551
      if REQUEST is not None:
        form = REQUEST.form
552
      if sort_on is None:
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
553
        listbox_id, sort_on = form["setSelectionQuickSortOrder"].split(".", 1)
554

555
      # Sort order can be specified in sort_on.
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
      if sort_on.endswith(':asc'):
        order = 'ascending'
        sort_on = sort_on[:-4]
      elif sort_on.endswith(':desc'):
        order = 'descending'
        sort_on = sort_on[:-5]
      elif sort_on.endswith(':none'):
        order = 'none'
        sort_on = sort_on[:-5]
      else:
        order = None
      # ... as well as cast type
      i = sort_on.find(':')
      if i < 0:
        as_type = None
      else:
        as_type = sort_on[i+1:]
        if as_type != 'float':
          return
        sort_on = sort_on[:i]
576

577 578 579 580 581 582
      if REQUEST is not None:
        if listbox_id is not None:
            selection_name_key = "%s_list_selection_name" %listbox_id
            selection_name = form[selection_name_key]
        elif selection_name is None:
            selection_name = form['selection_name']
583

Jean-Paul Smets's avatar
Jean-Paul Smets committed
584 585
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
586
        if order is not None:
587
          # Allow user to sort by multiple columns
588 589 590 591
          new_sort_on = [s for s in selection.sort_on if s[0] != sort_on]
          if order != 'none':
            new_sort_on.append((sort_on, order, as_type) if as_type else
                               (sort_on, order))
592 593 594
        else:
          # We must first switch from asc to desc and vice-versa if sort_order exists
          # in selection
595 596
          order = 'ascending'
          for current in selection.sort_on:
597
            if current[0] == sort_on:
598 599 600 601 602
              if current[1] == order:
                order = 'descending'
              break
          new_sort_on = ((sort_on, order, as_type) if as_type else
                         (sort_on, order),)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
603 604
        selection.edit(sort_on=new_sort_on)

605
      if REQUEST is not None:
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
606 607
        if form.has_key('listbox_uid') and \
            form.has_key('uids'):
608 609 610
          self.uncheckAll(selection_name, REQUEST.get('listbox_uid'))
          self.checkAll(selection_name, REQUEST.get('uids'))

611 612
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
613 614 615 616 617 618 619 620

    security.declareProtected(ERP5Permissions.View, 'getSelectionSortOrder')
    def getSelectionSortOrder(self, selection_name, REQUEST=None):
      """
        Returns the sort order of the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is None: return ()
621
      return selection.sort_on
Jean-Paul Smets's avatar
Jean-Paul Smets committed
622 623 624 625 626 627 628 629 630 631

    security.declareProtected(ERP5Permissions.View, 'setSelectionColumns')
    def setSelectionColumns(self, selection_name, columns, REQUEST=None):
      """
        Defines the columns in the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      selection.edit(columns=columns)

    security.declareProtected(ERP5Permissions.View, 'getSelectionColumns')
632
    def getSelectionColumns(self, selection_name, columns=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
633
      """
634 635
        Returns the columns in the selection if not empty, otherwise
        returns the value of columns argument
Jean-Paul Smets's avatar
Jean-Paul Smets committed
636
      """
637
      if columns is None: columns = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
638 639
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
640 641
        if len(selection.columns) > 0:
          return selection.columns
642
      return columns
Jean-Paul Smets's avatar
Jean-Paul Smets committed
643 644 645 646 647 648 649 650 651 652 653


    security.declareProtected(ERP5Permissions.View, 'setSelectionStats')
    def setSelectionStats(self, selection_name, stats, REQUEST=None):
      """
        Defines the stats in the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      selection.edit(stats=stats)

    security.declareProtected(ERP5Permissions.View, 'getSelectionStats')
654
    def getSelectionStats(self, selection_name, stats=_MARKER, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
655 656 657
      """
        Returns the stats in the selection
      """
658 659 660 661
      if stats is not _MARKER:
        default_stats = stats
      else:
        default_stats = [' '] * 6
Jean-Paul Smets's avatar
Jean-Paul Smets committed
662 663
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
664
        return getattr(aq_base(selection), 'stats', default_stats)
665
      return default_stats
Jean-Paul Smets's avatar
Jean-Paul Smets committed
666

667 668 669 670 671 672 673
    def _getExistingFormId(self, document, form_id):
      portal = document.getPortalObject()
      for url in [q['url'] for q in portal.portal_actions\
          .listFilteredActionsFor(document).get('object_view', [])]:
        # XXX-Luke: As this is not possible to do form_id -> action_id the
        # only way to know if form_id is implemented by action of document
        # is to use string matching.
674 675 676 677 678 679 680
        # This re will (form_id = Base_view):
        # qdsqdsq/Base_view --> match
        # qdsqdsq/Base_view?qsdsqd --> matches
        # qdsqdsq/Base_view/qsdsqd --> matches
        # qdsqdsq/Base_viewAaa --> doesn't match
        # qdsqdsq/Umpa_view --> doesn't match
        if re.search('/%s($|\W+)' % form_id, url):
681 682
          return form_id
      return 'view'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
683 684 685 686 687 688

    security.declareProtected(ERP5Permissions.View, 'viewFirst')
    def viewFirst(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
        Access first item in a selection
      """
689
      return self._redirectToIndex(0, selection_name, form_id, REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
690 691 692 693

    security.declareProtected(ERP5Permissions.View, 'viewLast')
    def viewLast(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
694
        Access last item in a selection
Jean-Paul Smets's avatar
Jean-Paul Smets committed
695
      """
696
      return self._redirectToIndex(-1, selection_name, form_id, REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
697 698 699 700 701 702

    security.declareProtected(ERP5Permissions.View, 'viewNext')
    def viewNext(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
        Access next item in a selection
      """
703
      return self._redirectToIndex(int(selection_index) + 1, selection_name, form_id, REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
704 705 706 707 708 709

    security.declareProtected(ERP5Permissions.View, 'viewPrevious')
    def viewPrevious(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
        Access previous item in a selection
      """
710 711 712
      return self._redirectToIndex(int(selection_index) - 1, selection_name, form_id, REQUEST)

    def _redirectToIndex(self, selection_index, selection_name, form_id, REQUEST):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
713 714 715 716
      if not REQUEST:
        REQUEST = get_request()
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
717
        method = self.unrestrictedTraverse(selection.method_path)
718
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
719
        if len(selection_list):
720 721 722
          if selection_index > 0:
            selection_index = selection_index % len(selection_list)
          o = selection_list[selection_index]
723
          url = o.absolute_url()
724
          form_id = self._getExistingFormId(o.getObject(), form_id)
725 726
        else:
          url = REQUEST.getURL()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
727
      else:
728
        url = REQUEST.getURL()
729
      ignore_layout = int(REQUEST.get('ignore_layout', 0))
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
730 731
      if form_id != 'view':
        url += '/%s' % form_id
732
      url += '?selection_index=%s&selection_name=%s' % (selection_index, selection_name)
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
733 734
      if ignore_layout:
        url += '&ignore_layout:int=1'
735
      if self.isAnonymous():
736
        url += '&selection_key=%s' % self.getAnonymousSelectionKey(selection_name, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
737 738 739
      REQUEST.RESPONSE.redirect(url)

    # ListBox related methods
740 741

    security.declareProtected(ERP5Permissions.View, 'firstPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
742
    def firstPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
743 744
      """
        Access the first page of a list
745 746 747
        XXX: As its complementary (lastPage) is broken, this method is
        probably not used either. If so, it should be removed along with
        lastPage.
748 749
      """
      if uids is None: uids = []
750 751 752 753 754
      selection = self.getSelectionFor(list_selection_name, REQUEST)
      if selection is not None:
        params = selection.getParams()
        params['list_start'] = 0
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
755 756
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
757 758

    security.declareProtected(ERP5Permissions.View, 'lastPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
759
    def lastPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
760 761
      """
        Access the last page of a list
762 763 764
        XXX: This method is broken, since "total_size" field is not
        present in the listbox rendering any longer. It should be
        removed.
765 766
      """
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
767
      selection = self.getSelectionFor(list_selection_name, REQUEST)
768 769 770 771 772 773 774 775 776 777 778
      if selection is not None:
        params = selection.getParams()
        # XXX This will not work if the number of lines shown in the listbox is greater
        #       than the BIG_INT constan. Such a case has low probability but is not
        #       impossible. If you are in this case, send me a mail ! -- Kev
        BIG_INT = 10000000
        last_page_start = BIG_INT
        total_lines = REQUEST.form.get('total_size', BIG_INT)
        if total_lines != BIG_INT:
          lines_per_page  = params.get('list_lines', 1)
          last_page_start = int(total_lines) - (int(total_lines) % int(lines_per_page))
779 780
        params['list_start'] = last_page_start
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
781 782
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
783

Jean-Paul Smets's avatar
Jean-Paul Smets committed
784
    security.declareProtected(ERP5Permissions.View, 'nextPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
785
    def nextPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
786 787 788
      """
        Access the next page of a list
      """
789
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
790
      selection = self.getSelectionFor(list_selection_name, REQUEST)
791 792
      if selection is not None:
        params = selection.getParams()
793 794 795
        lines = int(params.get('list_lines', 0))
        form = REQUEST.form
        if form.has_key('page_start'):
796 797
          try:
            list_start = (int(form.pop('page_start', 0)) - 1) * lines
798
          except (ValueError, TypeError):
799
            list_start = 0
800 801
        else:
          list_start = int(form.pop('list_start', 0))
802
        params['list_start'] = max(list_start + lines, 0)
803
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
804 805
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
806 807

    security.declareProtected(ERP5Permissions.View, 'previousPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
808
    def previousPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
809 810 811
      """
        Access the previous page of a list
      """
812
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
813
      selection = self.getSelectionFor(list_selection_name, REQUEST)
814 815
      if selection is not None:
        params = selection.getParams()
816 817 818
        lines = int(params.get('list_lines', 0))
        form = REQUEST.form
        if form.has_key('page_start'):
819 820
          try:
            list_start = (int(form.pop('page_start', 0)) - 1) * lines
821
          except (ValueError, TypeError):
822
            list_start = 0
823 824 825
        else:
          list_start = int(form.pop('list_start', 0))
        params['list_start'] = max(list_start - lines, 0)
826
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
827 828
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
829 830

    security.declareProtected(ERP5Permissions.View, 'setPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
831
    def setPage(self, list_selection_name, listbox_uid, query_string=None, uids=None, REQUEST=None):
832
      """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
833
         Sets the current displayed page in a selection
834
      """
835
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
836
      selection = self.getSelectionFor(list_selection_name, REQUEST)
837 838
      if selection is not None:
        params = selection.getParams()
839 840 841
        lines = int(params.get('list_lines', 0))
        form = REQUEST.form
        if form.has_key('page_start'):
842 843
          try:
            list_start = (int(form.pop('page_start', 0)) - 1) * lines
844
          except (ValueError, TypeError):
845
            list_start = 0
846 847
        else:
          list_start = int(form.pop('list_start', 0))
848
        params['list_start'] = max(list_start, 0)
849 850
        selection.edit(params=params)
        self.uncheckAll(list_selection_name, listbox_uid)
Vincent Pelletier's avatar
Vincent Pelletier committed
851
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST, query_string=query_string)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
852

853
    # PlanningBox related methods
854 855
    security.declareProtected(ERP5Permissions.View, 'setLanePath')
    def setLanePath(self, uids=None, REQUEST=None, form_id=None,
856
                     query_string=None):
857 858 859
      """
      Set graphic zoom level in PlanningBox
      """
860 861
      if uids is None:
        uids = []
862 863 864 865 866
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
867 868
        lane_path = request.form.get('lane_path', None)
        if lane_path is None:
869
          # If lane_path is not defined try to
870
          # use the last one from params
871 872 873 874
          lane_path = params.get('lane_path',1)
        bound_start = request.form.get('bound_start', None)
        if bound_start is not None:
          params['bound_start'] = bound_start
875
        params['lane_path'] = lane_path
876
        params['zoom_variation'] = 0
877
        selection.edit(params=params)
878
      if REQUEST is not None:
879 880 881
        return self._redirectToOriginalForm(REQUEST=REQUEST,
                                            form_id=form_id,
                                            query_string=query_string)
882

883 884
    security.declareProtected(ERP5Permissions.View, 'nextLanePage')
    def nextLanePage(self, uids=None, REQUEST=None, form_id=None, query_string=None):
885 886 887
      """
      Set next graphic zoom start in PlanningBox
      """
888 889
      if uids is None:
        uids = []
890 891 892 893 894
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
895
        params['bound_variation'] = 1
896
        selection.edit(params=params)
897
      if REQUEST is not None:
898 899 900
        return self._redirectToOriginalForm(REQUEST=REQUEST,
                                            form_id=form_id,
                                             query_string=query_string)
901

902 903
    security.declareProtected(ERP5Permissions.View, 'previousLanePage')
    def previousLanePage(self, uids=None, REQUEST=None, form_id=None, query_string=None):
904 905 906
      """
      Set previous graphic zoom in PlanningBox
      """
907 908
      if uids is None:
        uids = []
909 910 911 912 913
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
914
        params['bound_variation'] = -1
915 916 917 918 919
        selection.edit(params=params)
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST,
                                            form_id=form_id,
                                             query_string=query_string)
920

Jean-Paul Smets's avatar
Jean-Paul Smets committed
921
    security.declareProtected(ERP5Permissions.View, 'setDomainRoot')
922
    def setDomainRoot(self, REQUEST, form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
923 924 925
      """
        Sets the root domain for the current selection
      """
926
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
927
      selection = self.getSelectionFor(selection_name, REQUEST)
928
      root_url = REQUEST.form.get('domain_root_url','portal_categories')
929
      selection.edit(domain_path=root_url, domain_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
930

931 932 933
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
934

935 936 937 938 939 940
    security.declareProtected(ERP5Permissions.View, 'setDomainRootFromParam')
    def setDomainRootFromParam(self, REQUEST, selection_name, domain_root):
      if REQUEST is None:
        return
      selection = self.getSelectionFor(selection_name, REQUEST)
      selection.edit(domain_path=domain_root, domain_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
941

942
    security.declareProtected(ERP5Permissions.View, 'unfoldDomain')
943
    def unfoldDomain(self, REQUEST, form_id=None, query_string=None):
944 945 946
      """
        Unfold domain for the current selection
      """
947
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
948
      selection = self.getSelectionFor(selection_name, REQUEST)
949

950 951
      unfoldDomain = REQUEST.form.get('unfoldDomain', None)
      domain_url, domain_depth = unfoldDomain.split('.', 2)
952 953
      domain_depth = int(domain_depth)

954 955
      domain_list = list(selection.getDomainList())
      domain_list = domain_list[0:min(domain_depth, len(domain_list))]
956
      if isinstance(domain_url, str):
957
        selection.edit(domain_list = domain_list + [domain_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
958

959 960 961
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
962

963
    security.declareProtected(ERP5Permissions.View, 'foldDomain')
964
    def foldDomain(self, REQUEST, form_id=None, query_string=None):
965 966 967
      """
        Fold domain for the current selection
      """
968
      selection_name = REQUEST.list_selection_name
969
      selection = self.getSelectionFor(selection_name, REQUEST)
970

971 972 973
      foldDomain = REQUEST.form.get('foldDomain', None)
      domain_url, domain_depth = foldDomain.split('.', 2)
      domain_depth = int(domain_depth)
974

975 976
      domain_list = list(selection.getDomainList())
      domain_list = domain_list[0:min(domain_depth, len(domain_list))]
977
      selection.edit(domain_list=[x for x in domain_list if x != domain_url])
978

979 980 981
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
982

983

Jean-Paul Smets's avatar
Jean-Paul Smets committed
984
    security.declareProtected(ERP5Permissions.View, 'setReportRoot')
985
    def setReportRoot(self, REQUEST, form_id=None, query_string=None):
986 987 988
      """
        Sets the root report for the current selection
      """
989
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
990
      selection = self.getSelectionFor(selection_name, REQUEST)
991
      root_url = REQUEST.form.get('report_root_url','portal_categories')
992
      selection.edit(report_path=root_url, report_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
993

994 995 996
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
997 998

    security.declareProtected(ERP5Permissions.View, 'unfoldReport')
999
    def unfoldReport(self, REQUEST, form_id=None, query_string=None):
1000 1001 1002
      """
        Unfold report for the current selection
      """
1003
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1004
      selection = self.getSelectionFor(selection_name, REQUEST)
1005
      report_url = REQUEST.form.get('report_url',None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1006
      if type(report_url) == type('a'):
1007
        selection.edit(report_list=list(selection.getReportList()) + [report_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1008

1009 1010 1011
      return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                          query_string=query_string,
                                          no_report_depth=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1012 1013

    security.declareProtected(ERP5Permissions.View, 'foldReport')
1014
    def foldReport(self, REQUEST, form_id=None, query_string=None):
1015 1016 1017
      """
        Fold domain for the current selection
      """
1018
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1019
      selection = self.getSelectionFor(selection_name, REQUEST)
1020
      report_url = REQUEST.form.get('report_url',None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1021
      if type(report_url) == type('a'):
1022
        report_list = selection.getReportList()
1023
        selection.edit(report_list=[x for x in report_list if x != report_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1024

1025 1026 1027
      return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                          query_string=query_string,
                                          no_report_depth=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1028

1029 1030 1031 1032
    security.declareProtected(ERP5Permissions.View, 'getListboxDisplayMode')
    def getListboxDisplayMode(self, selection_name, REQUEST=None):
      if REQUEST is None:
        REQUEST = get_request()
1033
      selection = self.getSelectionFor(selection_name, REQUEST)
1034

1035 1036 1037 1038 1039
      if getattr(selection, 'report_tree_mode', 0):
        return 'ReportTreeMode'
      elif getattr(selection, 'domain_tree_mode', 0):
        return 'DomainTreeMode'
      return 'FlatListMode'
1040

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1041
    security.declareProtected(ERP5Permissions.View, 'setListboxDisplayMode')
1042
    def setListboxDisplayMode(self, REQUEST, listbox_display_mode,
1043 1044
                              selection_name=None, redirect=0,
                              form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1045
      """
1046
        Toggle display of the listbox
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1047 1048
      """
      request = REQUEST
1049 1050 1051 1052 1053 1054 1055
      # XXX FIXME
      # Dirty fix: we must be able to change the display mode of a listbox
      # in form_view
      # But, form can have multiple listbox...
      # This need to be cleaned
      # Beware, this fix may break the report system...
      # and we don't have test for this
1056 1057
      # Possible fix: currently, display mode icon are implemented as
      # method. It could be easier to generate them as link (where we
1058 1059 1060 1061 1062 1063 1064 1065 1066
      # can define explicitely parameters through the url).
      try:
        list_selection_name = request.list_selection_name
      except AttributeError:
        pass
      else:
        if list_selection_name is not None:
          selection_name = request.list_selection_name
      # Get the selection
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1067
      selection = self.getSelectionFor(selection_name, REQUEST)
1068
      if selection is None:
1069
        selection = Selection(selection_name)
1070
        self.setSelectionFor(selection_name, selection, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083

      if listbox_display_mode == 'FlatListMode':
        flat_list_mode = 1
        domain_tree_mode = 0
        report_tree_mode = 0
      elif listbox_display_mode == 'DomainTreeMode':
        flat_list_mode = 0
        domain_tree_mode = 1
        report_tree_mode = 0
      elif listbox_display_mode == 'ReportTreeMode':
        flat_list_mode = 0
        domain_tree_mode = 0
        report_tree_mode = 1
1084 1085 1086
      else:
        flat_list_mode = 0
        domain_tree_mode = 0
1087
        report_tree_mode = 0
1088

1089 1090 1091
      selection.edit(flat_list_mode=flat_list_mode,
                     domain_tree_mode=domain_tree_mode,
                     report_tree_mode=report_tree_mode)
1092
      # It is better to reset the query when changing the display mode.
1093 1094
      params = selection.getParams()
      if 'where_expression' in params: del params['where_expression']
1095
      selection.edit(params=params)
1096

1097
      if redirect:
1098 1099 1100
        return self._redirectToOriginalForm(REQUEST=request, form_id=form_id,
                                            query_string=query_string,
                                            no_reset=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1101 1102

    security.declareProtected(ERP5Permissions.View, 'setFlatListMode')
1103
    def setFlatListMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1104 1105 1106
      """
        Set display of the listbox to FlatList mode
      """
1107
      return self.setListboxDisplayMode(
1108
                       REQUEST=REQUEST, listbox_display_mode='FlatListMode',
1109
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1110 1111

    security.declareProtected(ERP5Permissions.View, 'setDomainTreeMode')
1112
    def setDomainTreeMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1113 1114 1115
      """
         Set display of the listbox to DomainTree mode
      """
1116
      return self.setListboxDisplayMode(
1117
                       REQUEST=REQUEST, listbox_display_mode='DomainTreeMode',
1118
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1119 1120

    security.declareProtected(ERP5Permissions.View, 'setReportTreeMode')
1121
    def setReportTreeMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1122 1123 1124
      """
        Set display of the listbox to ReportTree mode
      """
1125 1126 1127
      return self.setListboxDisplayMode(
                       REQUEST=REQUEST, listbox_display_mode='ReportTreeMode',
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1128

1129 1130 1131 1132 1133 1134
    security.declareProtected(ERP5Permissions.View, 'getSelectionSelectedValueList')
    def getSelectionSelectedValueList(self, selection_name, REQUEST=None, selection_method=None, context=None):
      """
        Get the list of values selected for 'selection_name'
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
1135 1136
      if selection is None:
        return []
1137
      return selection(method=selection_method, context=context, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1138

1139 1140 1141 1142 1143 1144
    security.declareProtected(ERP5Permissions.View, 'getSelectionCheckedValueList')
    def getSelectionCheckedValueList(self, selection_name, REQUEST=None):
      """
        Get the list of values checked for 'selection_name'
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
1145 1146
      if selection is None:
        return []
1147
      uid_list = selection.getCheckedUids()
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
      value_list = self.portal_catalog.getObjectList(uid_list)
      return value_list

    security.declareProtected(ERP5Permissions.View, 'getSelectionValueList')
    def getSelectionValueList(self, selection_name, REQUEST=None, selection_method=None, context=None):
      """
        Get the list of values checked or selected for 'selection_name'
      """
      value_list = self.getSelectionCheckedValueList(selection_name, REQUEST=REQUEST)
      if len(value_list) == 0:
1158 1159
        value_list = self.getSelectionSelectedValueList(
                                            selection_name,
1160 1161
                                            REQUEST=REQUEST,
                                            selection_method=selection_method,
1162
                                            context=context)
1163
      return value_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1164

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1165 1166 1167
    security.declareProtected(ERP5Permissions.View, 'getSelectionUidList')
    def getSelectionUidList(self, selection_name, REQUEST=None, selection_method=None, context=None):
      """
1168
        Get the list of uids checked or selected for 'selection_name'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1169
      """
1170
      return [x.getObject().getUid() for x in self.getSelectionValueList(selection_name, REQUEST=REQUEST, selection_method=selection_method, context=context)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1171

Sebastien Robin's avatar
Sebastien Robin committed
1172 1173 1174 1175 1176
    security.declareProtected(ERP5Permissions.View, 'selectionHasChanged')
    def selectionHasChanged(self, md5_string, object_uid_list):
      """
        We want to be sure that the selection did not change
      """
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
      return md5_string != self._getUIDListChecksum(object_uid_list)

    security.declareProtected(ERP5Permissions.View, 'getSelectionChecksum')
    def getSelectionChecksum(self, selection_name, uid_list=None):
      """Generate an MD5 checksum against checked uids. This is used to confirm
      that selected values do not change between a display of a dialog and an
      execution.
      uid_list (deprecated)
        For backward compatibility with code not updating selected uids.
      """
      if uid_list is None:
        uid_list = self.getSelectionCheckedUidsFor(selection_name)
      return self._getUIDListChecksum(uid_list)

    def _getUIDListChecksum(self, uid_list):
      if uid_list is None:
          return None
1194
      # XXX To avoid the difference of the string representations of int and long,
1195
      # convert each element to a string.
1196
      return md5(str(sorted(map(str, uid_list)))).hexdigest()
Sebastien Robin's avatar
Sebastien Robin committed
1197

1198
    # Related document searching
1199
    security.declarePublic('viewSearchRelatedDocumentDialog')
1200
    def viewSearchRelatedDocumentDialog(self, index, form_id,
1201
                                        REQUEST=None, sub_index=None, **kw):
1202
      """
1203 1204
      Returns a search related document dialog
      A set of forwarders us defined to circumvent limitations of HTML
1205
      """
Romain Courteaud's avatar
Romain Courteaud committed
1206 1207
      if sub_index != None:
        REQUEST.form['sub_index'] = sub_index
1208
      object_path = REQUEST.form['object_path']
1209
      # Find the object which needs to be updated
1210
      o = self.restrictedTraverse(object_path)
1211
      # Find the field which was clicked on
1212
      # Important to get from the object instead of self
1213
      form = getattr(o, form_id)
1214
      field = None
1215 1216
      # Search the correct field
      relation_field_found = 0
1217
      relation_index = 0
1218
      # XXX may be should support another parameter,
1219
      for field in form.get_fields(include_disabled=0):
1220 1221
        if field.get_value('editable', REQUEST=REQUEST):
          try:
1222 1223
           field.get_value('is_relation_field')
          except KeyError:
1224
            pass
1225
          else:
1226 1227 1228 1229 1230
            if index == relation_index:
              relation_field_found = 1
              break
            else:
              relation_index += 1
1231
      if not relation_field_found:
1232
        # We didn't find the field...
1233
        raise SelectionError, "SelectionTool: can not find the relation" \
1234
                              " field %s" % index
1235 1236 1237 1238
      else:
        # Field found
        field_key = field.generate_field_key()
        field_value = REQUEST.form[field_key]
1239 1240
        dialog_id = field.get_value('relation_form_id') or \
                                                   'Base_viewRelatedObjectList'
1241
        redirect_form = getattr(o, dialog_id)
1242 1243 1244
        # XXX Hardcoded listbox field
        selection_name = redirect_form.listbox.get_value('selection_name')
        # Reset current selection
1245
        self.setSelectionFor(selection_name, None)
1246 1247 1248 1249


        if (field.get_value('is_multi_relation_field')) and \
           (sub_index is None):
1250 1251 1252 1253
          # user click on the wheel, not on the validation button
          # we need to facilitate user search

          # first: store current field value in the selection
1254
          base_category = field.get_value('base_category')
1255

1256 1257 1258 1259 1260 1261
          property_get_related_uid_method_name = \
            "get%sUidList" % ''.join(['%s%s' % (x[0].upper(), x[1:]) \
                                      for x in base_category.split('_')])
          current_uid_list = getattr(o, property_get_related_uid_method_name)\
                               (portal_type=[x[0] for x in \
                                  field.get_value('portal_type')])
Romain Courteaud's avatar
Romain Courteaud committed
1262 1263
          # Checked current uid
          kw ={}
1264 1265
          catalog_index = field.get_value('catalog_index')
          kw[catalog_index] = field_value
1266
          self.setSelectionParamsFor(selection_name,
Vincent Pelletier's avatar
Vincent Pelletier committed
1267 1268 1269
                                     kw.copy())
          self.setSelectionCheckedUidsFor(selection_name,
                                          current_uid_list)
1270 1271 1272 1273
          field_value = str(field_value)
          if len(field_value):
            sql_catalog = self.portal_catalog.getSQLCatalog()
            field_value = sql_catalog.buildQuery({
1274 1275
              catalog_index:{'query':field_value.splitlines(),
                             'key':'ExactMatch',},
1276 1277
            }).asSearchTextExpression(sql_catalog, column='')

1278
          REQUEST.form[field_key] = field_value
1279
          portal_status_message = translateString("Please select one (or more) object.")
1280
        else:
1281
          portal_status_message = translateString("Please select one object.")
1282 1283


1284 1285
        # Save the current REQUEST form
        # We can't put FileUpload instances because we can't pickle them
1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
        saved_form_data = {key: value
          for key, value in REQUEST.form.items()
          if not isinstance(value, FileUpload)}

        kw = {
          'dialog_id': dialog_id,
          'selection_name': selection_name,
          'selection_index': 0, # We start on the first page
          'field_id': field.id,
          'reset': 0,
          'base_category': field.get_value( 'base_category'),
          'form_id': form_id,
          field.get_value('catalog_index'): field_value,
          'portal_status_message': portal_status_message,
          'saved_form_data': saved_form_data,
          'ignore_layout': int(REQUEST.get('ignore_layout', 0)),
          'ignore_hide_rows': 1,
        }
        kw.update(field.get_value('parameter_list'))
1305 1306 1307
        # remove ignore_layout parameter from cancel_url otherwise we
        # will have two ignore_layout parameters after clicking cancel
        # button.
1308 1309 1310
        split_referer = list(urlsplit(REQUEST.get('HTTP_REFERER')))
        split_referer[3] = '&'.join([x for x in \
                                     split_referer[3].split('&') \
1311
                                     if not re.match('^ignore_layout[:=]', x)])
1312
        kw['cancel_url'] = urlunsplit(split_referer)
1313

1314 1315
        proxy_listbox_ids = field.get_value('proxy_listbox_ids')
        REQUEST.set('proxy_listbox_ids', proxy_listbox_ids)
1316
        if len(proxy_listbox_ids) > 0:
1317 1318
          REQUEST.set('proxy_listbox_id', proxy_listbox_ids[0][0])
        else:
1319
          REQUEST.set('proxy_listbox_id',
1320 1321 1322
                       "Base_viewRelatedObjectListBase/listbox")

        # Empty the selection (uid)
1323 1324
        REQUEST.form = kw # New request form
        # Define new HTTP_REFERER
Romain Courteaud's avatar
Romain Courteaud committed
1325
        REQUEST.HTTP_REFERER = '%s/%s' % (o.absolute_url(),
1326
                                          dialog_id)
1327 1328 1329 1330 1331

        # If we are called from a Web Site, we should return
        # in the context of the Web Section
        if self.getApplicableLayout() is not None:
          return getattr(o.__of__(self.getWebSectionValue()), dialog_id)(REQUEST=REQUEST)
1332
        # Return the search dialog
1333
        return getattr(o, dialog_id)(REQUEST=REQUEST)
1334

1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345
    security.declarePublic('buildSQLJoinExpressionFromDomainSelection')
    def buildSQLJoinExpressionFromDomainSelection(self, selection_domain,
                                                  domain_id=None,
                                                  exclude_domain_id=None,
                                                  category_table_alias='category'):
      if isinstance(selection_domain, DomainSelection):
        warnings.warn("To pass a DomainSelection instance is deprecated.\n"
                      "Please use a domain dict instead.",
                      DeprecationWarning)
      else:
        selection_domain = DomainSelection(selection_domain).__of__(self)
1346 1347
      return selection_domain.asSQLJoinExpression(
          category_table_alias=category_table_alias)
1348 1349 1350

    security.declarePublic('buildSQLExpressionFromDomainSelection')
    def buildSQLExpressionFromDomainSelection(self, selection_domain,
1351
                                              table_map=None, domain_id=None,
1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363
                                              exclude_domain_id=None,
                                              strict_membership=0,
                                              join_table="catalog",
                                              join_column="uid",
                                              base_category=None,
                                              category_table_alias='category'):
      if isinstance(selection_domain, DomainSelection):
        warnings.warn("To pass a DomainSelection instance is deprecated.\n"
                      "Please use a domain dict instead.",
                      DeprecationWarning)
      else:
        selection_domain = DomainSelection(selection_domain).__of__(self)
1364 1365 1366 1367 1368 1369
      return selection_domain.asSQLExpression(
          strict_membership = strict_membership,
          join_table=join_table,
          join_column=join_column,
          base_category=base_category,
          category_table_alias = category_table_alias)
1370

1371 1372
    def _aq_dynamic(self, name):
      """
1373
        Generate viewSearchRelatedDocumentDialog0,
1374
                 viewSearchRelatedDocumentDialog1,... if necessary
1375 1376 1377
      """
      aq_base_name = getattr(aq_base(self), name, None)
      if aq_base_name == None:
1378 1379 1380
        DYNAMIC_METHOD_NAME = 'viewSearchRelatedDocumentDialog'
        method_name_length = len(DYNAMIC_METHOD_NAME)

1381
        zope_security = '__roles__'
1382
        if (name[:method_name_length] == DYNAMIC_METHOD_NAME) and \
1383
           (name[-len(zope_security):] != zope_security):
1384
          method_count_string_list = name[method_name_length:].split('_')
1385 1386 1387 1388
          method_count_string = method_count_string_list[0]
          # be sure that method name is correct
          try:
            method_count = string.atoi(method_count_string)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1389
          except TypeError:
1390
            return aq_base_name
1391
          else:
1392 1393 1394
            if len(method_count_string_list) > 1:
              # be sure that method name is correct
              try:
Romain Courteaud's avatar
Romain Courteaud committed
1395
                sub_index = string.atoi(method_count_string_list[1])
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1396
              except TypeError:
1397 1398
                return aq_base_name
            else:
Romain Courteaud's avatar
Romain Courteaud committed
1399
              sub_index = None
1400

1401
            # generate dynamicaly needed forwarder methods
1402
            def viewSearchRelatedDocumentDialogWrapper(self, form_id,
1403
                                                       REQUEST=None, **kw):
1404 1405 1406
              """
                viewSearchRelatedDocumentDialog Wrapper
              """
1407 1408
#               LOG('SelectionTool.viewSearchRelatedDocumentDialogWrapper, kw',
#                   0, kw)
1409
              return self.viewSearchRelatedDocumentDialog(
1410
                                   method_count, form_id,
1411
                                   REQUEST=REQUEST, sub_index=sub_index, **kw)
1412
            setattr(self.__class__, name,
1413
                    viewSearchRelatedDocumentDialogWrapper)
1414 1415

            klass = aq_base(self).__class__
1416 1417 1418 1419
            security_property_id = '%s__roles__' % (name, )
            # Declare method as public
            setattr(klass, security_property_id, None)

1420
            return getattr(self, name)
1421
      return SelectionTool.inheritedAttribute('_aq_dynamic')(self, name)
1422

1423
    def _getUserId(self):
1424
      tv = getTransactionalVariable()
1425 1426 1427
      user_id = tv.get('_user_id', None)
      if user_id is not None:
        return user_id
1428
      user_id = self.portal_membership.getAuthenticatedMember().getId()
1429 1430
      tv['_user_id'] = user_id
      return user_id
1431

1432
    security.declarePrivate('getTemporarySelectionDict')
1433 1434 1435 1436
    def getTemporarySelectionDict(self):
      """ Temporary selections are used in push/pop nested scope,
      to prevent from editting for stored selection in the scope.
      Typically, it is used for ReportSection."""
1437
      tv = getTransactionalVariable()
1438 1439 1440 1441 1442
      return tv.setdefault('_temporary_selection_dict', {})

    def pushSelection(self, selection_name):
      selection = self.getSelectionFor(selection_name)
      # a temporary selection is kept in transaction.
1443
      temp_selection = Selection(selection_name)
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454
      if selection:
        temp_selection.__dict__.update(selection.__dict__)
      self.getTemporarySelectionDict()\
        .setdefault(selection_name, []).append(temp_selection)

    def popSelection(self, selection_name):
      temporary_selection_dict = self.getTemporarySelectionDict()
      if selection_name in temporary_selection_dict and \
         temporary_selection_dict[selection_name]:
        temporary_selection_dict[selection_name].pop()

1455 1456 1457 1458 1459 1460 1461
    def getAnonymousSelection(self, key, selection_name):
      container_id = '_v_anonymous_selection_container'
      storage = self.getAnonymousStorage() or self.getStorage()
      container = self._getContainerFromStorage(container_id, storage)
      return container.getSelection(key, selection_name)

    def getAnonymousSelectionKey(self, selection_name, REQUEST=None):
1462
      if not self.isAnonymous():
1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473
        return ''
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is None:
        return ''
      key = selection.getAnonymousSelectionKey()
      container_id = '_v_anonymous_selection_container'
      storage = self.getAnonymousStorage() or self.getStorage()
      container = self._getContainerFromStorage(container_id, storage)
      container.setSelection(key, selection_name, selection)
      return key

1474 1475 1476
    def _getSelectionFromContainer(self, selection_name):
      user_id = self._getUserId()
      if user_id is None: return None
1477 1478 1479 1480 1481 1482 1483

      temporary_selection_dict = self.getTemporarySelectionDict()
      if temporary_selection_dict and selection_name in temporary_selection_dict:
        if temporary_selection_dict[selection_name]:
          # focus the temporary selection in the most narrow scope.
          return temporary_selection_dict[selection_name][-1]

1484
      return self._getContainer().getSelection(user_id, selection_name)
1485 1486 1487 1488

    def _setSelectionToContainer(self, selection_name, selection):
      user_id = self._getUserId()
      if user_id is None: return
1489 1490 1491 1492 1493 1494 1495 1496

      temporary_selection_dict = self.getTemporarySelectionDict()
      if temporary_selection_dict and selection_name in temporary_selection_dict:
        if temporary_selection_dict[selection_name]:
          # focus the temporary selection in the most narrow scope.
          temporary_selection_dict[selection_name][-1] = selection
          return

1497
      self._getContainer().setSelection(user_id, selection_name, selection)
1498

1499
    def _deleteSelectionForUserFromContainer(self, selection_name, user_id):
1500
      if user_id is None: return None
1501
      self._getContainer().deleteSelection(user_id, selection_name)
1502

1503 1504 1505 1506
    def _deleteSelectionFromContainer(self, selection_name):
      user_id = self._getUserId()
      self._deleteSelectionForUserFromContainer(selection_name, user_id)

1507
    def _deleteGlobalSelectionFromContainer(self, selection_name):
1508
      self._getContainer().deleteGlobalSelection(self, selection_name)
1509

1510
    def _getSelectionNameListFromContainer(self):
1511
      user_id = self._getUserId()
1512
      return list(set(self._getContainer().getSelectionNameList(user_id) +
1513 1514
                      self.getTemporarySelectionDict().keys()))

1515
    def isAnonymous(self):
1516
      return self._getUserId() == 'Anonymous User'
1517

1518
    def _getContainer(self):
1519
      if self.isAnonymous():
1520 1521 1522 1523
        tv = getTransactionalVariable()
        storage = tv.setdefault('_transactional_selection_container', {})
        container = TransactionalCacheContainer(storage)
        return container
1524 1525 1526
      else:
        container_id = '_v_selection_container'
        storage = self.getStorage()
1527 1528 1529
        return self._getContainerFromStorage(container_id, storage)

    def _getContainerFromStorage(self, container_id, storage):
1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544
      container = getattr(aq_base(self), container_id, None)
      if container is None:
        if storage.startswith('portal_memcached/'):
          plugin_path = storage
          value = self.getPortalObject().\
                  portal_memcached.getMemcachedDict(key_prefix='selection_tool',
                                                    plugin_path=plugin_path)
          container = MemcachedContainer(value)
        else:
          if getattr(aq_base(self), 'selection_data', None) is None:
            self.selection_data = PersistentMapping()
          value = self.selection_data
          container = PersistentMappingContainer(value)
        setattr(self, container_id, container)
      return container
1545

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1546
InitializeClass( SelectionTool )
1547

1548
class BaseContainer(object):
1549 1550 1551
  def __init__(self, container):
    self._container = container

1552
class MemcachedContainer(BaseContainer):
1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570
  def getSelectionNameList(self, user_id):
    return []

  def getSelection(self, user_id, selection_name):
    try:
      return self._container.get('%s-%s' % (user_id, selection_name))
    except KeyError:
      return None

  def setSelection(self, user_id, selection_name, selection):
    self._container.set('%s-%s' % (user_id, selection_name), aq_base(selection))

  def deleteSelection(self, user_id, selection_name):
    del(self._container['%s-%s' % (user_id, selection_name)])

  def deleteGlobalSelection(self, user_id, selection_name):
    pass

1571 1572 1573
class TransactionalCacheContainer(MemcachedContainer):
  def setSelection(self, user_id, selection_name, selection):
    self._container.__setitem__('%s-%s' % (user_id, selection_name), aq_base(selection))
1574

1575
class PersistentMappingContainer(BaseContainer):
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608
  def getSelectionNameList(self, user_id):
    try:
      return self._container[user_id].keys()
    except KeyError:
      return []

  def getSelection(self, user_id, selection_name):
    try:
      return self._container[user_id][selection_name]
    except KeyError:
      return None

  def setSelection(self, user_id, selection_name, selection):
    try:
      user_container = self._container[user_id]
    except KeyError:
      user_container = SelectionPersistentMapping()
      self._container[user_id] = user_container
    user_container[selection_name] = aq_base(selection)

  def deleteSelection(self, user_id, selection_name):
    try:
      user_container = self._container[user_id]
      del(user_container[selection_name])
    except KeyError:
      pass

  def deleteGlobalSelection(self, user_id, selection_name):
    for user_container in self._container.itervalues():
      try:
        del(user_container[selection_name])
      except KeyError:
        pass
1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619

class SelectionPersistentMapping(PersistentMapping):
  """A conflict-free PersistentMapping.

  Like selection objects, the purpose is to only prevent restarting
  transactions.
  """
  def _p_independent(self) :
    return 1

  def _p_resolveConflict(self, oldState, savedState, newState):
1620
    # BUG: we should not modify newState
1621
    # update keys that only savedState has
1622 1623
    newState['data'].update(savedState['data'])
    return newState
1624 1625


1626 1627 1628 1629 1630 1631 1632 1633
class TreeListLine:
  def __init__(self,object,is_pure_summary,depth, is_open,select_domain_dict,exception_uid_list):
    self.object=object
    self.is_pure_summary=is_pure_summary
    self.depth=depth
    self.is_open=is_open
    self.select_domain_dict=select_domain_dict
    self.exception_uid_list=exception_uid_list
1634

1635 1636
  def getObject(self):
    return self.object
1637

1638 1639
  def getIsPureSummary(self):
    return self.is_pure_summary
1640

1641 1642
  def getDepth(self):
    return self.depth
1643

1644 1645
  def getIsOpen(self):
    return self.is_open
1646 1647

  def getSelectDomainDict(self):
1648
    return self.select_domain_dict
1649

1650 1651
  def getExceptionUidList(self):
    return self.exception_uid_list
1652

1653

1654
def makeTreeList(here, form, root_dict, report_path, base_category,
Nicolas Delaby's avatar
Nicolas Delaby committed
1655 1656 1657
                 depth, unfolded_list, form_id, selection_name,
                 report_depth, is_report_opened=1, list_method=None,
                 filtered_portal_types=[] ,sort_on = (('id', 'ASC'),)):
1658 1659 1660 1661 1662
  """
    (object, is_pure_summary, depth, is_open, select_domain_dict)

    select_domain_dict is a dictionary of  associative list of (id, domain)
  """
1663 1664
  if isinstance(report_path, str):
    report_path = report_path.split('/')
1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679

  portal_categories = getattr(form, 'portal_categories', None)
  portal_domains = getattr(form, 'portal_domains', None)
  portal_object = form.portal_url.getPortalObject()
  if len(report_path):
    base_category = report_path[0]

  if root_dict is None:
    root_dict = {}

  is_empty_level = 1
  while is_empty_level:
    if not root_dict.has_key(base_category):
      root = None
      if portal_categories is not None:
1680
        if portal_categories._getOb(base_category, None) is not None:
1681 1682 1683 1684
          if base_category == 'parent':
            # parent has a special treatment
            root = root_dict[base_category] = root_dict[None] = here
            report_path = report_path[1:]
1685
          else:
1686 1687
            root = root_dict[base_category] = root_dict[None] = \
                                               portal_categories[base_category]
1688 1689
            report_path = report_path[1:]
      if root is None and portal_domains is not None:
1690 1691 1692
        if portal_domains._getOb(base_category, None) is not None:
          root = root_dict[base_category] = root_dict[None] = \
                                               portal_domains[base_category]
1693 1694 1695
          report_path = report_path[1:]
      if root is None:
        try:
1696 1697
          root = root_dict[None] = \
              portal_object.unrestrictedTraverse(report_path)
1698
        except KeyError:
1699
          LOG('SelectionTool', INFO, "Not found %s" % str(report_path))
1700 1701 1702 1703 1704
          root = None
        report_path = ()
    else:
      root = root_dict[None] = root_dict[base_category]
      report_path = report_path[1:]
1705 1706
    is_empty_level = (root is not None) and \
        (root.objectCount() == 0) and (len(report_path) != 0)
1707
    if is_empty_level:
1708
      base_category = report_path[0]
1709 1710

  tree_list = []
1711
  if root is None:
1712
    return tree_list
1713

1714
  if base_category == 'parent':
1715 1716 1717 1718 1719
    # Use searchFolder as default
    if list_method is None:
      if hasattr(aq_base(root), 'objectValues'):
        # If this is a folder, try to browse the hierarchy
        object_list = root.searchFolder(sort_on=sort_on)
1720
    else:
1721
      if filtered_portal_types not in [[],None,'']:
1722 1723
        object_list = list_method(portal_type=filtered_portal_types,
                                  sort_on=sort_on)
1724
      else:
1725
        object_list = list_method(sort_on=sort_on)
1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741
    for zo in object_list:
      o = zo.getObject()
      if o is not None:
        new_root_dict = root_dict.copy()
        new_root_dict[None] = new_root_dict[base_category] = o

        selection_domain = DomainSelection(domain_dict = new_root_dict)
        if (report_depth is not None and depth <= (report_depth - 1)) or \
                                          o.getRelativeUrl() in unfolded_list:
          exception_uid_list = [] # Object we do not want to display

          for sub_zo in o.searchFolder(sort_on=sort_on):
            sub_o = sub_zo.getObject()
            if sub_o is not None and hasattr(aq_base(root), 'objectValues'):
              exception_uid_list.append(sub_o.getUid())
          # Summary (open)
1742
          tree_list += [TreeListLine(o, 1, depth, 1, selection_domain, exception_uid_list)]
1743 1744
          if is_report_opened :
            # List (contents, closed, must be strict selection)
1745 1746 1747
            tree_list += [TreeListLine(o, 0, depth, 0, selection_domain, exception_uid_list)]

          tree_list += makeTreeList(here, form, new_root_dict, report_path,
1748 1749 1750
                    base_category, depth + 1, unfolded_list, form_id,
                    selection_name, report_depth,
                    is_report_opened=is_report_opened, sort_on=sort_on)
1751 1752
        else:
          tree_list += [TreeListLine(o, 1, depth, 0, selection_domain, ())] # Summary (closed)
1753
  else:
1754 1755 1756 1757 1758 1759
    # process to recover objects in case a generation script is used
    if hasattr(root,'getChildDomainValueList'):
      oblist = root.getChildDomainValueList(root,depth=depth)
    else:
      oblist = root.objectValues()
    for o in oblist:
1760 1761 1762 1763 1764 1765 1766
      new_root_dict = root_dict.copy()
      new_root_dict[None] = new_root_dict[base_category] = o
      selection_domain = DomainSelection(domain_dict = new_root_dict)
      if (report_depth is not None and depth <= (report_depth - 1)) or o.getRelativeUrl() in unfolded_list:
        tree_list += [TreeListLine(o, 1, depth, 1, selection_domain, None)] # Summary (open)
        if is_report_opened :
          tree_list += [TreeListLine(o, 0, depth, 0, selection_domain, None)] # List (contents, closed, must be strict selection)
1767 1768
        tree_list += makeTreeList(here, form, new_root_dict, report_path, base_category, depth + 1,
            unfolded_list, form_id, selection_name, report_depth,
1769 1770 1771 1772
            is_report_opened=is_report_opened, sort_on=sort_on)
      else:

        tree_list += [TreeListLine(o, 1, depth, 0, selection_domain, None)] # Summary (closed)
1773

1774 1775
  return tree_list

1776
# Automaticaly add wrappers on Folder so it can access portal_selections.
1777
# Cannot be done in ERP5Type/Document/Folder.py because ERP5Type must not
1778
# depend on ERP5Form.
1779 1780

from Products.CMFCore.utils import getToolByName
1781
from Products.ERP5Type.Core.Folder import FolderMixIn
1782 1783
from ZPublisher.mapply import mapply

1784
method_id_filter_list = [x for x in FolderMixIn.__dict__ if callable(getattr(FolderMixIn, x))]
1785 1786 1787 1788 1789 1790 1791 1792 1793
candidate_method_id_list = []
for x in SelectionTool.__dict__:
  if not callable(getattr(SelectionTool, x)):
    continue
  if x.startswith('_') or x.endswith('__roles__'):
    continue
  if x in method_id_filter_list:
    continue
  roles = getattr(SelectionTool, '%s__roles__' % x, None)
1794
  if roles is None or roles == ():
1795 1796 1797 1798
    continue
  if roles.__name__ == ERP5Permissions.ManagePortal:
    continue
  candidate_method_id_list.append(x)
1799

1800 1801 1802
# Monkey patch FolderMixIn with SelectionTool methods, and wrapper methods
# ('listbox_<WRAPPED_METHOD_NAME>()') used to set ListBox properties for
# pagination
1803 1804 1805 1806 1807 1808 1809 1810 1811 1812
for property_id in candidate_method_id_list:
  def portal_selection_wrapper(self, wrapper_property_id=property_id, *args, **kw):
    """
      Wrapper method for SelectionTool.
    """
    portal_selection = getToolByName(self, 'portal_selections')
    request = self.REQUEST
    method = getattr(portal_selection, wrapper_property_id)
    return mapply(method, positional=args, keyword=request,
                  context=self, bind=1)
1813
  setattr(FolderMixIn, property_id, portal_selection_wrapper)
1814 1815 1816
  security_property_id = '%s__roles__' % (property_id, )
  security_property = getattr(SelectionTool, security_property_id, None)
  if security_property is not None:
1817
    setattr(FolderMixIn, security_property_id, security_property)
1818

1819 1820 1821 1822 1823 1824
  def portal_selection_wrapper(self, wrapper_property_id=property_id, *args, **kw):
    """
      Wrapper method for SelectionTool.
    """
    portal_selection = getToolByName(self, 'portal_selections')
    request = self.REQUEST
1825 1826
    listbox_id = request.form.get('listbox_%s' % wrapper_property_id, None)
    if not listbox_id:
1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861
      # Backward-compatibility: Should be removed as soon as
      # createFolderMixInPageSelectionMethod has been removed
      warnings.warn(
        "DEPRECATED: listbox_%s: 'value' attribute of the submit button "
        "should be set to the ListBox ID and the method name 'listbox_%s" %
        (wrapper_property_id, wrapper_property_id),
        DeprecationWarning)

      listbox_id = 'listbox'

    selection_name_property_id = "%s_list_selection_name" % listbox_id
    listbox_uid_property_id = "%s_uid" % listbox_id
    list_start_property_id = "%s_list_start" % listbox_id
    page_start_property_id = "%s_page_start" % listbox_id
    # Rename request parameters
    if request.has_key(selection_name_property_id):
      request.form['list_selection_name'] = request[selection_name_property_id]
    if request.has_key(listbox_uid_property_id):
      request.form['listbox_uid'] = request[listbox_uid_property_id]
    if request.has_key(list_start_property_id):
      request.form['list_start'] = request[list_start_property_id]
    if request.has_key(page_start_property_id):
      request.form['page_start'] = request[page_start_property_id]
    # Call the wrapper
    method = getattr(portal_selection, wrapper_property_id)
    return mapply(method, positional=args, keyword=request,
                  context=self, bind=1)
  new_property_id = "listbox_%s" % property_id
  setattr(FolderMixIn, new_property_id, portal_selection_wrapper)
  security_property_id = '%s__roles__' % (property_id, )
  security_property = getattr(SelectionTool, security_property_id, None)
  if security_property is not None:
    new_security_property_id = '%s__roles__' % (new_property_id, )
    setattr(FolderMixIn, new_security_property_id, security_property)

1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872
def createFolderMixInPageSelectionMethod(listbox_id):
  """
  This method must be called by listbox at rendering time.
  It dynamically creates methods on FolderMixIn in line
  with the naming of the listbox field. Generated method
  are able to convert request parameters in order to
  mimic the API of a listbox with ID "listbox". This
  approach was required for example to implement
  multiple multi-page listboxes in view mode. It also
  opens the way towards multiple editable listboxes in the same
  page although this is something which we can not recommend.
1873 1874 1875 1876

  Deprecated because these methods are generated when rendering the
  ListBox. Therefore, they are only available on the ZEO client
  where it has been rendered but not the other ZEO clients.
1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888
  """
  # Immediately return in the method already exists
  test_method_id = "%s_nextPage" % listbox_id
  if hasattr(FolderMixIn, test_method_id):
    return
  # Monkey patch FolderMixIn
  for property_id in candidate_method_id_list:
    def portal_selection_wrapper(self, wrapper_listbox_id=listbox_id,
                                       wrapper_property_id=property_id, *args, **kw):
      """
        Wrapper method for SelectionTool.
      """
1889 1890 1891 1892
      warnings.warn(
        "DEPRECATED: %s_%s: The ListBox ID must not be contained anymore in the "
        "method name, but instead be in the 'value' attribute of the submit "
        "button and the method name should be 'listbox_%s'" %
1893
        (wrapper_listbox_id, wrapper_property_id, wrapper_listbox_id),
1894 1895
        DeprecationWarning)

1896 1897 1898 1899 1900
      portal_selection = getToolByName(self, 'portal_selections')
      request = self.REQUEST
      selection_name_property_id = "%s_list_selection_name" % listbox_id
      listbox_uid_property_id = "%s_uid" % listbox_id
      list_start_property_id = "%s_list_start" % listbox_id
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
1901
      page_start_property_id = "%s_page_start" % listbox_id
1902 1903
      # Rename request parameters
      if request.has_key(selection_name_property_id):
1904
        request.form['list_selection_name'] = request[selection_name_property_id]
1905 1906 1907 1908
      if request.has_key(listbox_uid_property_id):
        request.form['listbox_uid'] = request[listbox_uid_property_id]
      if request.has_key(list_start_property_id):
        request.form['list_start'] = request[list_start_property_id]
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
1909 1910
      if request.has_key(page_start_property_id):
        request.form['page_start'] = request[page_start_property_id]
1911 1912 1913 1914 1915 1916 1917 1918 1919 1920
      # Call the wrapper
      method = getattr(portal_selection, wrapper_property_id)
      return mapply(method, positional=args, keyword=request,
                    context=self, bind=1)
    new_property_id = "%s_%s" % (listbox_id, property_id)
    setattr(FolderMixIn, new_property_id, portal_selection_wrapper)
    security_property_id = '%s__roles__' % (property_id, )
    security_property = getattr(SelectionTool, security_property_id, None)
    if security_property is not None:
      new_security_property_id = '%s__roles__' % (new_property_id, )
1921
      setattr(FolderMixIn, new_security_property_id, security_property)