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 556 557 558 559 560 561 562 563 564 565 566 567
      # Sort order can be specified in sort_on.
      forced_sort_order = None
      if sort_on is not None:
        if sort_on.endswith(':asc'):
          forced_sort_order = 'ascending'
          sort_on = sort_on[:-4]
        elif sort_on.endswith(':desc'):
          forced_sort_order = 'descending'
          sort_on = sort_on[:-5]
        elif sort_on.endswith(':none'):
          forced_sort_order = 'none'
          sort_on = sort_on[:-5]

568 569 570 571 572 573
      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']
574

Jean-Paul Smets's avatar
Jean-Paul Smets committed
575 576
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
        if forced_sort_order is not None:
          if forced_sort_order == 'none':
            temporary_new_sort_on = []
          else:
            temporary_new_sort_on = [(sort_on, forced_sort_order)]
          # Allow user to sort by multiple columns
          new_sort_on = [s
                         for s in self.getSelectionSortOrder(selection_name)
                         if s[0]!=sort_on]
          new_sort_on.extend(temporary_new_sort_on)
        else:
          current_sort_on = self.getSelectionSortOrder(selection_name)
          # We must first switch from asc to desc and vice-versa if sort_order exists
          # in selection
          n = 0
          for current in current_sort_on:
            if current[0] == sort_on:
              n = 1
              if current[1] == 'ascending':
                new_sort_on = [(sort_on, 'descending')]
                break
              else:
                new_sort_on = [(sort_on,'ascending')]
                break
          # And if no one exists, we just set sort
          if n == 0:
            new_sort_on = [(sort_on, 'ascending')]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
604 605
        selection.edit(sort_on=new_sort_on)

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

612 613
        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
614 615 616 617 618 619 620 621

    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 ()
622
      return selection.sort_on
Jean-Paul Smets's avatar
Jean-Paul Smets committed
623 624 625 626 627 628 629 630 631 632

    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')
633
    def getSelectionColumns(self, selection_name, columns=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
634
      """
635 636
        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
637
      """
638
      if columns is None: columns = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
639 640
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
641 642
        if len(selection.columns) > 0:
          return selection.columns
643
      return columns
Jean-Paul Smets's avatar
Jean-Paul Smets committed
644 645 646 647 648 649 650 651 652 653 654


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

668 669 670 671 672 673 674
    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.
675 676 677 678 679 680 681
        # 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):
682 683
          return form_id
      return 'view'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
684 685 686 687 688 689

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

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

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

    security.declareProtected(ERP5Permissions.View, 'viewPrevious')
    def viewPrevious(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
        Access previous item in a selection
      """
711 712 713
      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
714 715 716 717
      if not REQUEST:
        REQUEST = get_request()
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
718
        method = self.unrestrictedTraverse(selection.method_path)
719
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
720
        if len(selection_list):
721 722 723
          if selection_index > 0:
            selection_index = selection_index % len(selection_list)
          o = selection_list[selection_index]
724
          url = o.absolute_url()
725
          form_id = self._getExistingFormId(o.getObject(), form_id)
726 727
        else:
          url = REQUEST.getURL()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
728
      else:
729
        url = REQUEST.getURL()
730
      ignore_layout = int(REQUEST.get('ignore_layout', 0))
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
731 732
      if form_id != 'view':
        url += '/%s' % form_id
733
      url += '?selection_index=%s&selection_name=%s' % (selection_index, selection_name)
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
734 735
      if ignore_layout:
        url += '&ignore_layout:int=1'
736
      if self.isAnonymous():
737
        url += '&selection_key=%s' % self.getAnonymousSelectionKey(selection_name, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
738 739 740
      REQUEST.RESPONSE.redirect(url)

    # ListBox related methods
741 742

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

    security.declareProtected(ERP5Permissions.View, 'lastPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
760
    def lastPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
761 762
      """
        Access the last page of a list
763 764 765
        XXX: This method is broken, since "total_size" field is not
        present in the listbox rendering any longer. It should be
        removed.
766 767
      """
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
768
      selection = self.getSelectionFor(list_selection_name, REQUEST)
769 770 771 772 773 774 775 776 777 778 779
      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))
780 781
        params['list_start'] = last_page_start
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
782 783
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
784

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

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

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

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

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

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

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

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

936 937 938 939 940 941
    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
942

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

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

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

960 961 962
      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
963

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

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

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

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

984

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

995 996 997
      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
998 999

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

1010 1011 1012
      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
1013 1014

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

1026 1027 1028
      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
1029

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

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1042
    security.declareProtected(ERP5Permissions.View, 'setListboxDisplayMode')
1043
    def setListboxDisplayMode(self, REQUEST, listbox_display_mode,
1044 1045
                              selection_name=None, redirect=0,
                              form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1046
      """
1047
        Toggle display of the listbox
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1048 1049
      """
      request = REQUEST
1050 1051 1052 1053 1054 1055 1056
      # 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
1057 1058
      # Possible fix: currently, display mode icon are implemented as
      # method. It could be easier to generate them as link (where we
1059 1060 1061 1062 1063 1064 1065 1066 1067
      # 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
1068
      selection = self.getSelectionFor(selection_name, REQUEST)
1069
      if selection is None:
1070
        selection = Selection(selection_name)
1071
        self.setSelectionFor(selection_name, selection, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084

      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
1085 1086 1087
      else:
        flat_list_mode = 0
        domain_tree_mode = 0
1088
        report_tree_mode = 0
1089

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

1098
      if redirect:
1099 1100 1101
        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
1102 1103

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

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

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

1130 1131 1132 1133 1134 1135
    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)
1136 1137
      if selection is None:
        return []
1138
      return selection(method=selection_method, context=context, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1139

1140 1141 1142 1143 1144 1145
    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)
1146 1147
      if selection is None:
        return []
1148
      uid_list = selection.getCheckedUids()
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
      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:
1159 1160
        value_list = self.getSelectionSelectedValueList(
                                            selection_name,
1161 1162
                                            REQUEST=REQUEST,
                                            selection_method=selection_method,
1163
                                            context=context)
1164
      return value_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1165

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1166 1167 1168
    security.declareProtected(ERP5Permissions.View, 'getSelectionUidList')
    def getSelectionUidList(self, selection_name, REQUEST=None, selection_method=None, context=None):
      """
1169
        Get the list of uids checked or selected for 'selection_name'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1170
      """
1171
      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
1172

Sebastien Robin's avatar
Sebastien Robin committed
1173 1174 1175 1176 1177
    security.declareProtected(ERP5Permissions.View, 'selectionHasChanged')
    def selectionHasChanged(self, md5_string, object_uid_list):
      """
        We want to be sure that the selection did not change
      """
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
      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
1195
      # XXX To avoid the difference of the string representations of int and long,
1196
      # convert each element to a string.
1197
      return md5(str(sorted(map(str, uid_list)))).hexdigest()
Sebastien Robin's avatar
Sebastien Robin committed
1198

1199
    # Related document searching
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
        saved_form_data = {}
        for key, value in REQUEST.form.items():
          if not isinstance(value, FileUpload):
1289
            saved_form_data[key] = value
1290

1291 1292
        base_category = None
        kw = {}
1293
        kw['dialog_id'] = dialog_id
1294 1295 1296 1297 1298 1299 1300 1301 1302
        kw['selection_name'] = selection_name
        kw['selection_index'] = 0 # We start on the first page
        kw['field_id'] = field.id
        parameter_list = field.get_value('parameter_list')
        if len(parameter_list) > 0:
          for k,v in parameter_list:
            kw[k] = v
        kw['reset'] = 0
        kw['base_category'] = field.get_value( 'base_category')
1303
        kw['form_id'] = form_id
1304 1305
        kw[field.get_value('catalog_index')] = field_value
        kw['portal_status_message'] = portal_status_message
1306
        kw['saved_form_data'] = saved_form_data
1307
        kw['ignore_layout'] = int(REQUEST.get('ignore_layout', 0))
1308
        kw['ignore_hide_rows'] = 1
1309 1310 1311
        # remove ignore_layout parameter from cancel_url otherwise we
        # will have two ignore_layout parameters after clicking cancel
        # button.
1312 1313 1314
        split_referer = list(urlsplit(REQUEST.get('HTTP_REFERER')))
        split_referer[3] = '&'.join([x for x in \
                                     split_referer[3].split('&') \
1315
                                     if not re.match('^ignore_layout[:=]', x)])
1316
        kw['cancel_url'] = urlunsplit(split_referer)
1317

1318 1319
        proxy_listbox_ids = field.get_value('proxy_listbox_ids')
        REQUEST.set('proxy_listbox_ids', proxy_listbox_ids)
1320
        if len(proxy_listbox_ids) > 0:
1321 1322
          REQUEST.set('proxy_listbox_id', proxy_listbox_ids[0][0])
        else:
1323
          REQUEST.set('proxy_listbox_id',
1324 1325 1326
                       "Base_viewRelatedObjectListBase/listbox")

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

        # 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)
1336
        # Return the search dialog
1337
        return getattr(o, dialog_id)(REQUEST=REQUEST)
1338

1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
    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)
1350 1351
      return selection_domain.asSQLJoinExpression(
          category_table_alias=category_table_alias)
1352 1353 1354

    security.declarePublic('buildSQLExpressionFromDomainSelection')
    def buildSQLExpressionFromDomainSelection(self, selection_domain,
1355
                                              table_map=None, domain_id=None,
1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367
                                              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)
1368 1369 1370 1371 1372 1373
      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)
1374

1375 1376
    def _aq_dynamic(self, name):
      """
1377
        Generate viewSearchRelatedDocumentDialog0,
1378
                 viewSearchRelatedDocumentDialog1,... if necessary
1379 1380 1381
      """
      aq_base_name = getattr(aq_base(self), name, None)
      if aq_base_name == None:
1382 1383 1384
        DYNAMIC_METHOD_NAME = 'viewSearchRelatedDocumentDialog'
        method_name_length = len(DYNAMIC_METHOD_NAME)

1385
        zope_security = '__roles__'
1386
        if (name[:method_name_length] == DYNAMIC_METHOD_NAME) and \
1387
           (name[-len(zope_security):] != zope_security):
1388
          method_count_string_list = name[method_name_length:].split('_')
1389 1390 1391 1392
          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
1393
          except TypeError:
1394
            return aq_base_name
1395
          else:
1396 1397 1398
            if len(method_count_string_list) > 1:
              # be sure that method name is correct
              try:
Romain Courteaud's avatar
Romain Courteaud committed
1399
                sub_index = string.atoi(method_count_string_list[1])
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1400
              except TypeError:
1401 1402
                return aq_base_name
            else:
Romain Courteaud's avatar
Romain Courteaud committed
1403
              sub_index = None
1404

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

            klass = aq_base(self).__class__
1420 1421 1422 1423
            security_property_id = '%s__roles__' % (name, )
            # Declare method as public
            setattr(klass, security_property_id, None)

1424
            return getattr(self, name)
1425
      return SelectionTool.inheritedAttribute('_aq_dynamic')(self, name)
1426

1427
    def _getUserId(self):
1428
      tv = getTransactionalVariable()
1429 1430 1431 1432 1433 1434
      user_id = tv.get('_user_id', None)
      if user_id is not None:
        return user_id
      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
      tv['_user_id'] = user_id
      return user_id
1435

1436 1437 1438 1439
    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."""
1440
      tv = getTransactionalVariable()
1441 1442 1443 1444 1445
      return tv.setdefault('_temporary_selection_dict', {})

    def pushSelection(self, selection_name):
      selection = self.getSelectionFor(selection_name)
      # a temporary selection is kept in transaction.
1446
      temp_selection = Selection(selection_name)
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457
      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()

1458 1459 1460 1461 1462 1463 1464
    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):
1465
      if not self.isAnonymous():
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476
        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

1477 1478 1479
    def _getSelectionFromContainer(self, selection_name):
      user_id = self._getUserId()
      if user_id is None: return None
1480 1481 1482 1483 1484 1485 1486

      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]

1487
      return self._getContainer().getSelection(user_id, selection_name)
1488 1489 1490 1491

    def _setSelectionToContainer(self, selection_name, selection):
      user_id = self._getUserId()
      if user_id is None: return
1492 1493 1494 1495 1496 1497 1498 1499

      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

1500
      self._getContainer().setSelection(user_id, selection_name, selection)
1501

1502
    def _deleteSelectionForUserFromContainer(self, selection_name, user_id):
1503
      if user_id is None: return None
1504
      self._getContainer().deleteSelection(user_id, selection_name)
1505

1506 1507 1508 1509
    def _deleteSelectionFromContainer(self, selection_name):
      user_id = self._getUserId()
      self._deleteSelectionForUserFromContainer(selection_name, user_id)

1510
    def _deleteGlobalSelectionFromContainer(self, selection_name):
1511
      self._getContainer().deleteGlobalSelection(self, selection_name)
1512

1513
    def _getSelectionNameListFromContainer(self):
1514
      user_id = self._getUserId()
1515
      return list(set(self._getContainer().getSelectionNameList(user_id) +
1516 1517
                      self.getTemporarySelectionDict().keys()))

1518
    def isAnonymous(self):
1519
      return self._getUserId() == 'Anonymous User'
1520

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

    def _getContainerFromStorage(self, container_id, storage):
1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547
      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
1548

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1549
InitializeClass( SelectionTool )
1550

1551
class BaseContainer(object):
1552 1553 1554
  def __init__(self, container):
    self._container = container

1555
class MemcachedContainer(BaseContainer):
1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573
  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

1574 1575 1576
class TransactionalCacheContainer(MemcachedContainer):
  def setSelection(self, user_id, selection_name, selection):
    self._container.__setitem__('%s-%s' % (user_id, selection_name), aq_base(selection))
1577

1578
class PersistentMappingContainer(BaseContainer):
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 1609 1610 1611
  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
1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622

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):
1623
    # BUG: we should not modify newState
1624
    # update keys that only savedState has
1625 1626
    newState['data'].update(savedState['data'])
    return newState
1627 1628


1629 1630 1631 1632 1633 1634 1635 1636
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
1637

1638 1639
  def getObject(self):
    return self.object
1640

1641 1642
  def getIsPureSummary(self):
    return self.is_pure_summary
1643

1644 1645
  def getDepth(self):
    return self.depth
1646

1647 1648
  def getIsOpen(self):
    return self.is_open
1649 1650

  def getSelectDomainDict(self):
1651
    return self.select_domain_dict
1652

1653 1654
  def getExceptionUidList(self):
    return self.exception_uid_list
1655

1656

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

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

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

  tree_list = []
1714
  if root is None:
1715
    return tree_list
1716

1717
  if base_category == 'parent':
1718 1719 1720 1721 1722
    # 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)
1723
    else:
1724
      if filtered_portal_types not in [[],None,'']:
1725 1726
        object_list = list_method(portal_type=filtered_portal_types,
                                  sort_on=sort_on)
1727
      else:
1728
        object_list = list_method(sort_on=sort_on)
1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744
    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)
1745
          tree_list += [TreeListLine(o, 1, depth, 1, selection_domain, exception_uid_list)]
1746 1747
          if is_report_opened :
            # List (contents, closed, must be strict selection)
1748 1749 1750
            tree_list += [TreeListLine(o, 0, depth, 0, selection_domain, exception_uid_list)]

          tree_list += makeTreeList(here, form, new_root_dict, report_path,
1751 1752 1753
                    base_category, depth + 1, unfolded_list, form_id,
                    selection_name, report_depth,
                    is_report_opened=is_report_opened, sort_on=sort_on)
1754 1755
        else:
          tree_list += [TreeListLine(o, 1, depth, 0, selection_domain, ())] # Summary (closed)
1756
  else:
1757 1758 1759 1760 1761 1762
    # 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:
1763 1764 1765 1766 1767 1768 1769
      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)
1770 1771
        tree_list += makeTreeList(here, form, new_root_dict, report_path, base_category, depth + 1,
            unfolded_list, form_id, selection_name, report_depth,
1772 1773 1774 1775
            is_report_opened=is_report_opened, sort_on=sort_on)
      else:

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

1777 1778
  return tree_list

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

from Products.CMFCore.utils import getToolByName
1784
from Products.ERP5Type.Core.Folder import FolderMixIn
1785 1786
from ZPublisher.mapply import mapply

1787 1788
method_id_filter_list = [x for x in FolderMixIn.__dict__ if callable(getattr(FolderMixIn, x))]
candidate_method_id_list = [x for x in SelectionTool.__dict__ if callable(getattr(SelectionTool, x)) and not x.startswith('_') and not x.endswith('__roles__') and x not in method_id_filter_list]
1789

1790 1791 1792
# Monkey patch FolderMixIn with SelectionTool methods, and wrapper methods
# ('listbox_<WRAPPED_METHOD_NAME>()') used to set ListBox properties for
# pagination
1793 1794 1795 1796 1797 1798 1799 1800 1801 1802
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)
1803
  setattr(FolderMixIn, property_id, portal_selection_wrapper)
1804 1805 1806
  security_property_id = '%s__roles__' % (property_id, )
  security_property = getattr(SelectionTool, security_property_id, None)
  if security_property is not None:
1807
    setattr(FolderMixIn, security_property_id, security_property)
1808

1809 1810 1811 1812 1813 1814
  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
1815 1816
    listbox_id = request.form.get('listbox_%s' % wrapper_property_id, None)
    if not listbox_id:
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 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
      # 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)

1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862
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.
1863 1864 1865 1866

  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.
1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878
  """
  # 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.
      """
1879 1880 1881 1882 1883 1884 1885
      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'" %
        (wrapper_listbox_id, wrapper_property_id, wrapper_method_id),
        DeprecationWarning)

1886 1887 1888 1889 1890
      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
1891
      page_start_property_id = "%s_page_start" % listbox_id
1892 1893
      # Rename request parameters
      if request.has_key(selection_name_property_id):
1894
        request.form['list_selection_name'] = request[selection_name_property_id]
1895 1896 1897 1898
      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
1899 1900
      if request.has_key(page_start_property_id):
        request.form['page_start'] = request[page_start_property_id]
1901 1902 1903 1904 1905 1906 1907 1908 1909 1910
      # 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, )
1911
      setattr(FolderMixIn, new_security_property_id, security_property)