SelectionTool.py 77 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 35
"""

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

54

55 56
_MARKER = []

Jean-Paul Smets's avatar
Jean-Paul Smets committed
57 58 59
class SelectionError( Exception ):
    pass

60
class SelectionTool( BaseTool, SimpleItem ):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
61 62 63 64 65 66 67 68
    """
      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'
69
    portal_type     = 'Selection Tool'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
70 71 72 73 74 75 76
    security = ClassSecurityInfo()

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

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

89
    security.declareProtected( ERP5Permissions.ManagePortal
90 91
                             , 'manage_viewSelections' )
    manage_viewSelections = DTMLFile( 'SelectionTool_manageViewSelections', _dtmldir )
92

93 94 95 96
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_configure' )
    manage_configure = DTMLFile( 'SelectionTool_configure', _dtmldir )

97 98 99 100 101 102 103 104 105 106 107
    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'))

108 109 110 111 112 113 114 115 116 117 118
    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'))

119 120 121 122 123 124 125 126 127 128 129
    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'))

130
    # storages of SelectionTool
131 132 133 134 135 136 137 138 139 140 141
    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
142 143

    security.declareProtected( ERP5Permissions.ManagePortal, 'setStorage')
144
    def setStorage(self, storage, anonymous_storage=None, RESPONSE=None):
145 146 147
      """
        Set the storage of Selection Tool.
      """
148 149
      if storage in [item[1] for item in self.getStorageItemList()]:
        self.storage = storage
150
      else:
151 152 153 154 155 156
        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
      else:
        raise ValueError, 'Given storage type (%s) is now supported.' % (anonymous_storage,)
157 158 159
      if RESPONSE is not None:
        RESPONSE.redirect('%s/manage_configure' % (self.absolute_url()))

160
    security.declareProtected( ERP5Permissions.ManagePortal, 'getStorage')
161
    def getStorage(self, default='selection_data'):
162 163
      """return the selected storage
      """
164
      storage = getattr(aq_base(self), 'storage', default)
165
      if storage is not default:
166 167 168 169 170
        #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')
171 172 173 174
          if len(memcached_plugin_list):
            storage = memcached_plugin_list[0].getRelativeUrl()
          else:
            storage = 'selection_data'
175 176
      return storage

177 178 179 180 181 182
    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
183 184
    def _redirectToOriginalForm(self, REQUEST=None, form_id=None, dialog_id=None,
                                query_string=None,
185
                                no_reset=False, no_report_depth=False):
Vincent Pelletier's avatar
Vincent Pelletier committed
186 187
      """Redirect to the original form or dialog, using the information given
         as parameters.
188 189
         (Actually does not redirect  in the HTTP meaning because of URL
         limitation problems.)
Vincent Pelletier's avatar
Vincent Pelletier committed
190 191 192

         DEPRECATED parameters :
         query_string is used to transmit parameters from caller to callee.
193
         If no_reset is True, replace reset parameters with noreset.
Vincent Pelletier's avatar
Vincent Pelletier committed
194 195
         If no_report_depth is True, replace report_depth parameters with
         noreport_depth.
196 197 198 199
      """
      if REQUEST is None:
        return

Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
200 201 202 203 204 205 206
      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
207

208
      if query_string is not None:
209 210
        warnings.warn('DEPRECATED: _redirectToOriginalForm got called with a query_string. The variables must be passed in REQUEST.form.',
                      DeprecationWarning)
211
      context = REQUEST['PARENTS'][0]
Vincent Pelletier's avatar
Vincent Pelletier committed
212
      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
213
      return getattr(context, form_id)()
214

215 216
    security.declareProtected(ERP5Permissions.View, 'getSelectionNameList')
    def getSelectionNameList(self, context=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
217 218 219
      """
        Returns the selection names of the current user.
      """
220
      return sorted(self._getSelectionNameListFromContainer())
221

222 223 224 225 226 227 228 229
    # 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)

230
    security.declareProtected(ERP5Permissions.View, 'callSelectionFor')
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
    def callSelectionFor(self, selection_name, method=None, context=None, 
                                               REQUEST=None, params=None):
      """
      Calls the selection and return the list of selected documents
      or objects. Seledction method, context and parameters may be 
      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

      REQUEST -- optional REQUEST parameters (not used, only to 
                 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)
      """
254 255 256 257
      if context is None: context = self
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is None:
        return None
258
      return selection(method=method, context=context, REQUEST=REQUEST, params=params)
259

Jean-Paul Smets's avatar
Jean-Paul Smets committed
260 261 262 263 264
    security.declareProtected(ERP5Permissions.View, 'getSelectionFor')
    def getSelectionFor(self, selection_name, REQUEST=None):
      """
        Returns the selection instance for a given selection_name
      """
265 266
      if isinstance(selection_name, (tuple, list)):
        selection_name = selection_name[0]
267 268
      if not selection_name:
        return None
269 270 271
      selection = self._getSelectionFromContainer(selection_name)
      if selection is not None:
        return selection.__of__(self)
272

273 274 275
    def __getitem__(self, key):
        return self.getSelectionParamsFor(key)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
276 277 278 279 280
    security.declareProtected(ERP5Permissions.View, 'setSelectionFor')
    def setSelectionFor(self, selection_name, selection_object, REQUEST=None):
      """
        Sets the selection instance for a given selection_name
      """
281 282
      if not selection_name:
        return
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
283 284 285 286
      anonymous_uid = REQUEST and REQUEST.get('anonymous_uid', None)
      if anonymous_uid is not None:
        self.REQUEST.response.setCookie('anonymous_uid', anonymous_uid,
                                        path='/')
287
      assert selection_object is None or selection_name == selection_object.name
288

289 290
      if self.getSelectionFor(selection_name) != selection_object:
        self._setSelectionToContainer(selection_name, selection_object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
291

292 293
    security.declareProtected(ERP5Permissions.View, 'getSelectionParamsFor')
    def getSelectionParamsFor(self, selection_name, params=None, REQUEST=None):
294 295 296
      """
        Returns the params in the selection
      """
297 298
      if params is None:
        params = {}
299 300
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
301
        if selection.params:
302
          return selection.getParams()
303
      return params
304 305

    # backward compatibility
306 307
    security.declareProtected(ERP5Permissions.View, 'getSelectionParams')
    getSelectionParams = getSelectionParamsFor
308

Jean-Paul Smets's avatar
Jean-Paul Smets committed
309 310 311 312 313 314
    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)
315
      if selection_object is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
316 317
        selection_object.edit(params=params)
      else:
318
        selection_object = Selection(selection_name, params=params)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
319 320
      self.setSelectionFor(selection_name, selection_object, REQUEST)

321
    security.declareProtected(ERP5Permissions.View, 'getSelectionDomainDictFor')
322 323 324 325 326 327 328
    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:
329
          return selection.getDomain().asDomainDict()
330 331 332
        except AttributeError:
          return {}

333
    security.declareProtected(ERP5Permissions.View, 'getSelectionReportDictFor')
334 335 336 337 338 339 340
    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:
341
          return selection.getReport().asDomainDict()
342 343 344
        except AttributeError:
          return {}

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

357
    security.declareProtected(ERP5Permissions.View, 'updateSelectionCheckedUidList')
358 359
    def updateSelectionCheckedUidList(self, selection_name, listbox_uid, uids, REQUEST=None):
      """
360 361
        Updates the unchecked uids(listbox_uids) and checked uids (uids)
        for a given selection_name
362 363 364 365 366 367 368 369
      """
      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)

370 371 372
    security.declareProtected(ERP5Permissions.View, 'getSelectionCheckedUidsFor')
    def getSelectionCheckedUidsFor(self, selection_name, REQUEST=None):
      """
373
        Returns the checked uids for a given selection_name
374 375 376
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
377
        return selection_object.getCheckedUids()
378 379 380
      return []

    security.declareProtected(ERP5Permissions.View, 'checkAll')
381
    def checkAll(self, list_selection_name, listbox_uid=[], REQUEST=None,
382
                 query_string=None, form_id=None):
383
      """
384
        Check uids in a given listbox_uid list for a given list_selection_name
385
      """
386
      selection_object = self.getSelectionFor(list_selection_name, REQUEST)
387 388
      if selection_object:
        selection_uid_dict = {}
389
        for uid in selection_object.checked_uids:
390 391
          selection_uid_dict[uid] = 1
        for uid in listbox_uid:
392 393
          try:
            selection_uid_dict[int(uid)] = 1
394
          except (ValueError, TypeError):
395
            selection_uid_dict[uid] = 1
396
        self.setSelectionCheckedUidsFor(list_selection_name, selection_uid_dict.keys(), REQUEST=REQUEST)
397 398 399
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
400 401

    security.declareProtected(ERP5Permissions.View, 'uncheckAll')
402
    def uncheckAll(self, list_selection_name, listbox_uid=[], REQUEST=None,
403
                   query_string=None, form_id=None):
404
      """
405
        Uncheck uids in a given listbox_uid list for a given list_selection_name
406
      """
407
      selection_object = self.getSelectionFor(list_selection_name, REQUEST)
408 409
      if selection_object:
        selection_uid_dict = {}
410
        for uid in selection_object.checked_uids:
411 412
          selection_uid_dict[uid] = 1
        for uid in listbox_uid:
413 414
          try:
            if selection_uid_dict.has_key(int(uid)): del selection_uid_dict[int(uid)]
415
          except (ValueError, TypeError):
416
            if selection_uid_dict.has_key(uid): del selection_uid_dict[uid]
417
        self.setSelectionCheckedUidsFor(list_selection_name, selection_uid_dict.keys(), REQUEST=REQUEST)
418 419 420
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
421

Jean-Paul Smets's avatar
Jean-Paul Smets committed
422 423 424 425 426 427 428
    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:
429
        return selection.getListUrl()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
430 431
      else:
        return None
432

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    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
450

451
    security.declareProtected(ERP5Permissions.View, 'getSelectionInvertModeUidListFor')
452 453 454 455 456 457 458 459
    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

460 461 462 463 464 465 466 467 468
    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
469 470 471 472 473 474 475
    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:
476
        selection.edit(invert_mode=1, uids=selection_uids, checked_uids=selection_uids)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
477 478

    security.declareProtected(ERP5Permissions.View, 'setSelectionToAll')
479 480
    def setSelectionToAll(self, selection_name, REQUEST=None,
                          reset_domain_tree=False, reset_report_tree=False):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
481 482 483 484 485
      """
        Resets the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
486
        selection.edit(invert_mode=0, params={}, checked_uids=[], report_opened=1)
487
        if reset_domain_tree:
488
          selection.edit(domain=None, domain_path=None, domain_list=None)
489
        if reset_report_tree:
490
          selection.edit(report=None, report_path=None, report_list=None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
491 492 493 494 495 496 497 498 499 500 501

    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')
502
    def setSelectionQuickSortOrder(self, selection_name=None, sort_on=None, REQUEST=None,
503
                                   query_string=None, form_id=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
504 505 506 507
      """
        Defines the sort order of the selection directly from the listbox
        In this method, sort_on is just a string that comes from url
      """
508 509
      # 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
510
      listbox_id = None
511 512
      if REQUEST is not None:
        form = REQUEST.form
513
      if sort_on is None:
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
514
        listbox_id, sort_on = form["setSelectionQuickSortOrder"].split(".", 1)
515

516 517 518 519 520 521 522 523 524 525 526 527 528
      # 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]

529 530 531 532 533 534
      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']
Aurel's avatar
Aurel committed
535
          
Jean-Paul Smets's avatar
Jean-Paul Smets committed
536 537
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
        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
565 566
        selection.edit(sort_on=new_sort_on)

567
      if REQUEST is not None:
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
568 569
        if form.has_key('listbox_uid') and \
            form.has_key('uids'):
570 571 572
          self.uncheckAll(selection_name, REQUEST.get('listbox_uid'))
          self.checkAll(selection_name, REQUEST.get('uids'))

573 574
        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
575 576 577 578 579 580 581 582

    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 ()
583
      return selection.sort_on
Jean-Paul Smets's avatar
Jean-Paul Smets committed
584 585 586 587 588 589 590 591 592 593

    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')
594
    def getSelectionColumns(self, selection_name, columns=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
595
      """
596 597
        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
598
      """
599
      if columns is None: columns = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
600 601
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
602 603
        if len(selection.columns) > 0:
          return selection.columns
604
      return columns
Jean-Paul Smets's avatar
Jean-Paul Smets committed
605 606 607 608 609 610 611 612 613 614 615


    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')
616
    def getSelectionStats(self, selection_name, stats=_MARKER, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
617 618 619
      """
        Returns the stats in the selection
      """
620 621 622 623
      if stats is not _MARKER:
        default_stats = stats
      else:
        default_stats = [' '] * 6
Jean-Paul Smets's avatar
Jean-Paul Smets committed
624 625
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
626
        return getattr(aq_base(selection), 'stats', default_stats)
627
      return default_stats
Jean-Paul Smets's avatar
Jean-Paul Smets committed
628

629 630 631 632 633 634 635
    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.
636 637 638 639 640 641 642
        # 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):
643 644
          return form_id
      return 'view'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
645 646 647 648 649 650 651 652 653 654

    security.declareProtected(ERP5Permissions.View, 'viewFirst')
    def viewFirst(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
        Access first item in a selection
      """
      if not REQUEST:
        REQUEST = get_request()
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
655
        method = self.unrestrictedTraverse(selection.method_path)
656
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
657 658 659
        if len(selection_list):
          o = selection_list[0]
          url = o.absolute_url()
660
          form_id = self._getExistingFormId(o.getObject(), form_id)
661
        else:
662
          url = REQUEST.getURL()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
663
      else:
664
        url = REQUEST.getURL()
665
      ignore_layout = int(REQUEST.get('ignore_layout', 0))
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
666 667 668 669 670
      if form_id != 'view':
        url += '/%s' % form_id
      url += '?selection_index=%s&selection_name=%s' % (0, selection_name)
      if ignore_layout:
        url += '&ignore_layout:int=1'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
671 672 673 674 675
      REQUEST.RESPONSE.redirect(url)

    security.declareProtected(ERP5Permissions.View, 'viewLast')
    def viewLast(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
676
        Access last item in a selection
Jean-Paul Smets's avatar
Jean-Paul Smets committed
677 678 679 680 681
      """
      if not REQUEST:
        REQUEST = get_request()
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
682
        method = self.unrestrictedTraverse(selection.method_path)
683
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
684 685 686
        if len(selection_list):
          o = selection_list[-1]
          url = o.absolute_url()
687
          form_id = self._getExistingFormId(o.getObject(), form_id)
688 689
        else:
          url = REQUEST.getURL()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
690
      else:
691
        url = REQUEST.getURL()
692
      ignore_layout = int(REQUEST.get('ignore_layout', 0))
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
693 694 695 696 697
      if form_id != 'view':
        url += '/%s' % form_id
      url += '?selection_index=%s&selection_name=%s' % (-1, selection_name)
      if ignore_layout:
        url += '&ignore_layout:int=1'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
698 699 700 701 702 703 704 705 706 707 708
      REQUEST.RESPONSE.redirect(url)

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

    security.declareProtected(ERP5Permissions.View, 'viewPrevious')
    def viewPrevious(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
        Access previous item in a selection
      """
      if not REQUEST:
        REQUEST = get_request()
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
737
        method = self.unrestrictedTraverse(selection.method_path)
738
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
739 740 741
        if len(selection_list):
          o = selection_list[(int(selection_index) - 1) % len(selection_list)]
          url = o.absolute_url()
742
          form_id = self._getExistingFormId(o.getObject(), form_id)
743 744
        else:
          url = REQUEST.getURL()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
745
      else:
746
        url = REQUEST.getURL()
747
      ignore_layout = int(REQUEST.get('ignore_layout', 0))
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
748 749 750 751 752 753
      if form_id != 'view':
        url += '/%s' % form_id
      url += '?selection_index=%s&selection_name=%s' % (int(selection_index)-1,
                                                        selection_name)
      if ignore_layout:
        url += '&ignore_layout:int=1'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
754 755 756 757
      REQUEST.RESPONSE.redirect(url)


    # ListBox related methods
758 759

    security.declareProtected(ERP5Permissions.View, 'firstPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
760
    def firstPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
761 762
      """
        Access the first page of a list
763 764 765
        XXX: As its complementary (lastPage) is broken, this method is
        probably not used either. If so, it should be removed along with
        lastPage.
766 767
      """
      if uids is None: uids = []
768 769 770 771 772
      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
773 774
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
775 776

    security.declareProtected(ERP5Permissions.View, 'lastPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
777
    def lastPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
778 779
      """
        Access the last page of a list
780 781 782
        XXX: This method is broken, since "total_size" field is not
        present in the listbox rendering any longer. It should be
        removed.
783 784
      """
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
785
      selection = self.getSelectionFor(list_selection_name, REQUEST)
786 787 788 789 790 791 792 793 794 795 796
      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))
797 798
        params['list_start'] = last_page_start
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
799 800
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
801

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

    security.declareProtected(ERP5Permissions.View, 'previousPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
826
    def previousPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
827 828 829
      """
        Access the previous page of a list
      """
830
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
831
      selection = self.getSelectionFor(list_selection_name, REQUEST)
832 833
      if selection is not None:
        params = selection.getParams()
834 835 836
        lines = int(params.get('list_lines', 0))
        form = REQUEST.form
        if form.has_key('page_start'):
837 838
          try:
            list_start = (int(form.pop('page_start', 0)) - 1) * lines
839
          except (ValueError, TypeError):
840
            list_start = 0
841 842 843
        else:
          list_start = int(form.pop('list_start', 0))
        params['list_start'] = max(list_start - lines, 0)
844
        selection.edit(params=params)
Vincent Pelletier's avatar
Vincent Pelletier committed
845 846
      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
847 848

    security.declareProtected(ERP5Permissions.View, 'setPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
849
    def setPage(self, list_selection_name, listbox_uid, query_string=None, uids=None, REQUEST=None):
850
      """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
851
         Sets the current displayed page in a selection
852
      """
853
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
854
      selection = self.getSelectionFor(list_selection_name, REQUEST)
855 856
      if selection is not None:
        params = selection.getParams()
857 858 859
        lines = int(params.get('list_lines', 0))
        form = REQUEST.form
        if form.has_key('page_start'):
860 861
          try:
            list_start = (int(form.pop('page_start', 0)) - 1) * lines
862
          except (ValueError, TypeError):
863
            list_start = 0
864 865
        else:
          list_start = int(form.pop('list_start', 0))
866
        params['list_start'] = max(list_start, 0)
867 868
        selection.edit(params=params)
        self.uncheckAll(list_selection_name, listbox_uid)
Vincent Pelletier's avatar
Vincent Pelletier committed
869
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST, query_string=query_string)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
870

871
    # PlanningBox related methods
872 873
    security.declareProtected(ERP5Permissions.View, 'setLanePath')
    def setLanePath(self, uids=None, REQUEST=None, form_id=None,
874
                     query_string=None):
875 876 877
      """
      Set graphic zoom level in PlanningBox
      """
878 879
      if uids is None:
        uids = []
880 881 882 883 884
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
885 886 887
        lane_path = request.form.get('lane_path', None)
        if lane_path is None:
          # If lane_path is not defined try to 
888
          # use the last one from params
889 890 891 892 893 894
          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
        params['lane_path'] = lane_path     
        params['zoom_variation'] = 0
895
        selection.edit(params=params)
896
      if REQUEST is not None:
897 898 899
        return self._redirectToOriginalForm(REQUEST=REQUEST,
                                            form_id=form_id,
                                            query_string=query_string)
900

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

920 921
    security.declareProtected(ERP5Permissions.View, 'previousLanePage')
    def previousLanePage(self, uids=None, REQUEST=None, form_id=None, query_string=None):
922 923 924
      """
      Set previous graphic zoom in PlanningBox
      """
925 926
      if uids is None:
        uids = []
927 928 929 930 931
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
932
        params['bound_variation'] = -1
933 934 935 936 937
        selection.edit(params=params)
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST,
                                            form_id=form_id,
                                             query_string=query_string)
938

Jean-Paul Smets's avatar
Jean-Paul Smets committed
939
    security.declareProtected(ERP5Permissions.View, 'setDomainRoot')
940
    def setDomainRoot(self, REQUEST, form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
941 942 943
      """
        Sets the root domain for the current selection
      """
944
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
945
      selection = self.getSelectionFor(selection_name, REQUEST)
946
      root_url = REQUEST.form.get('domain_root_url','portal_categories')
947
      selection.edit(domain_path=root_url, domain_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
948

949 950 951
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
952

953 954 955 956 957 958
    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
959

960
    security.declareProtected(ERP5Permissions.View, 'unfoldDomain')
961
    def unfoldDomain(self, REQUEST, form_id=None, query_string=None):
962 963 964
      """
        Unfold domain for the current selection
      """
965
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
966
      selection = self.getSelectionFor(selection_name, REQUEST)
967 968 969 970 971
     
      unfoldDomain = REQUEST.form.get('unfoldDomain', None)
      domain_url, domain_depth = unfoldDomain.split('.', 2)
      domain_depth = int(domain_depth)      
      
972 973
      domain_list = list(selection.getDomainList())
      domain_list = domain_list[0:min(domain_depth, len(domain_list))]
974
      if isinstance(domain_url, str):
975
        selection.edit(domain_list = domain_list + [domain_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
976

977 978 979
      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
980

981
    security.declareProtected(ERP5Permissions.View, 'foldDomain')
982
    def foldDomain(self, REQUEST, form_id=None, query_string=None):
983 984 985
      """
        Fold domain for the current selection
      """
986
      selection_name = REQUEST.list_selection_name
987
      selection = self.getSelectionFor(selection_name, REQUEST)
988 989 990 991 992
      
      foldDomain = REQUEST.form.get('foldDomain', None)
      domain_url, domain_depth = foldDomain.split('.', 2)
      domain_depth = int(domain_depth)
      
993 994
      domain_list = list(selection.getDomainList())
      domain_list = domain_list[0:min(domain_depth, len(domain_list))]
995
      selection.edit(domain_list=[x for x in domain_list if x != domain_url])
996

997 998 999
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
1000

1001

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1002
    security.declareProtected(ERP5Permissions.View, 'setReportRoot')
1003
    def setReportRoot(self, REQUEST, form_id=None, query_string=None):
1004 1005 1006
      """
        Sets the root report for the current selection
      """
1007
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1008
      selection = self.getSelectionFor(selection_name, REQUEST)
1009
      root_url = REQUEST.form.get('report_root_url','portal_categories')
1010
      selection.edit(report_path=root_url, report_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1011

1012 1013 1014
      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
1015 1016

    security.declareProtected(ERP5Permissions.View, 'unfoldReport')
1017
    def unfoldReport(self, REQUEST, form_id=None, query_string=None):
1018 1019 1020
      """
        Unfold report for the current selection
      """
1021
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1022
      selection = self.getSelectionFor(selection_name, REQUEST)
1023
      report_url = REQUEST.form.get('report_url',None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1024
      if type(report_url) == type('a'):
1025
        selection.edit(report_list=list(selection.getReportList()) + [report_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1026

1027 1028 1029
      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
1030 1031

    security.declareProtected(ERP5Permissions.View, 'foldReport')
1032
    def foldReport(self, REQUEST, form_id=None, query_string=None):
1033 1034 1035
      """
        Fold domain for the current selection
      """
1036
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1037
      selection = self.getSelectionFor(selection_name, REQUEST)
1038
      report_url = REQUEST.form.get('report_url',None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1039
      if type(report_url) == type('a'):
1040
        report_list = selection.getReportList()
1041
        selection.edit(report_list=[x for x in report_list if x != report_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1042

1043 1044 1045
      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
1046

1047 1048 1049 1050
    security.declareProtected(ERP5Permissions.View, 'getListboxDisplayMode')
    def getListboxDisplayMode(self, selection_name, REQUEST=None):
      if REQUEST is None:
        REQUEST = get_request()
1051
      selection = self.getSelectionFor(selection_name, REQUEST)
1052

1053 1054 1055 1056 1057
      if getattr(selection, 'report_tree_mode', 0):
        return 'ReportTreeMode'
      elif getattr(selection, 'domain_tree_mode', 0):
        return 'DomainTreeMode'
      return 'FlatListMode'
1058

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1059
    security.declareProtected(ERP5Permissions.View, 'setListboxDisplayMode')
1060
    def setListboxDisplayMode(self, REQUEST, listbox_display_mode,
1061 1062
                              selection_name=None, redirect=0,
                              form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1063
      """
1064
        Toggle display of the listbox
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1065 1066
      """
      request = REQUEST
1067 1068 1069 1070 1071 1072 1073
      # 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
1074 1075
      # Possible fix: currently, display mode icon are implemented as
      # method. It could be easier to generate them as link (where we
1076 1077 1078 1079 1080 1081 1082 1083 1084
      # 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
1085
      selection = self.getSelectionFor(selection_name, REQUEST)
1086
      if selection is None:
1087
        selection = Selection(selection_name)
1088
        self.setSelectionFor(selection_name, selection, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101

      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
1102 1103 1104
      else:
        flat_list_mode = 0
        domain_tree_mode = 0
1105
        report_tree_mode = 0
1106

1107 1108 1109
      selection.edit(flat_list_mode=flat_list_mode,
                     domain_tree_mode=domain_tree_mode,
                     report_tree_mode=report_tree_mode)
1110
      # It is better to reset the query when changing the display mode.
1111 1112
      params = selection.getParams()
      if 'where_expression' in params: del params['where_expression']
1113
      selection.edit(params=params)
1114

1115
      if redirect:
1116 1117 1118
        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
1119 1120

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

    security.declareProtected(ERP5Permissions.View, 'setDomainTreeMode')
1130
    def setDomainTreeMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1131 1132 1133
      """
         Set display of the listbox to DomainTree mode
      """
1134
      return self.setListboxDisplayMode(
1135
                       REQUEST=REQUEST, listbox_display_mode='DomainTreeMode',
1136
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1137 1138

    security.declareProtected(ERP5Permissions.View, 'setReportTreeMode')
1139
    def setReportTreeMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1140 1141 1142
      """
        Set display of the listbox to ReportTree mode
      """
1143 1144 1145
      return self.setListboxDisplayMode(
                       REQUEST=REQUEST, listbox_display_mode='ReportTreeMode',
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1146

1147 1148 1149 1150 1151 1152
    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)
1153 1154
      if selection is None:
        return []
1155
      return selection(method=selection_method, context=context, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1156

1157 1158 1159 1160 1161 1162
    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)
1163 1164
      if selection is None:
        return []
1165
      uid_list = selection.getCheckedUids()
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
      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:
1176 1177
        value_list = self.getSelectionSelectedValueList(
                                            selection_name,
1178 1179
                                            REQUEST=REQUEST,
                                            selection_method=selection_method,
1180
                                            context=context)
1181
      return value_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1182

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1183 1184 1185
    security.declareProtected(ERP5Permissions.View, 'getSelectionUidList')
    def getSelectionUidList(self, selection_name, REQUEST=None, selection_method=None, context=None):
      """
1186
        Get the list of uids checked or selected for 'selection_name'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1187
      """
1188
      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
1189

Sebastien Robin's avatar
Sebastien Robin committed
1190 1191 1192 1193 1194
    security.declareProtected(ERP5Permissions.View, 'selectionHasChanged')
    def selectionHasChanged(self, md5_string, object_uid_list):
      """
        We want to be sure that the selection did not change
      """
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211
      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
1212
      # XXX To avoid the difference of the string representations of int and long,
1213
      # convert each element to a string.
1214
      return md5(str(sorted(map(str, uid_list)))).hexdigest()
Sebastien Robin's avatar
Sebastien Robin committed
1215

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


        if (field.get_value('is_multi_relation_field')) and \
           (sub_index is None):
1267 1268 1269 1270
          # user click on the wheel, not on the validation button
          # we need to facilitate user search

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

1273 1274 1275 1276 1277 1278
          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
1279 1280
          # Checked current uid
          kw ={}
1281 1282
          catalog_index = field.get_value('catalog_index')
          kw[catalog_index] = field_value
1283
          self.setSelectionParamsFor(selection_name,
Vincent Pelletier's avatar
Vincent Pelletier committed
1284 1285 1286
                                     kw.copy())
          self.setSelectionCheckedUidsFor(selection_name,
                                          current_uid_list)
1287 1288 1289 1290 1291 1292 1293
          field_value = str(field_value)
          if len(field_value):
            sql_catalog = self.portal_catalog.getSQLCatalog()
            field_value = sql_catalog.buildQuery({
              catalog_index: field_value.splitlines()
            }).asSearchTextExpression(sql_catalog, column='')

1294
          REQUEST.form[field_key] = field_value
1295
          portal_status_message = translateString("Please select one (or more) object.")
1296
        else:
1297
          portal_status_message = translateString("Please select one object.")
1298 1299


1300 1301
        # Save the current REQUEST form
        # We can't put FileUpload instances because we can't pickle them
1302 1303 1304
        saved_form_data = {}
        for key, value in REQUEST.form.items():
          if not isinstance(value, FileUpload):
1305
            saved_form_data[key] = value
1306

1307 1308
        base_category = None
        kw = {}
1309
        kw['dialog_id'] = dialog_id
1310 1311 1312 1313 1314 1315 1316 1317 1318
        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')
1319
        kw['form_id'] = form_id
1320 1321
        kw[field.get_value('catalog_index')] = field_value
        kw['portal_status_message'] = portal_status_message
1322
        kw['saved_form_data'] = saved_form_data
1323
        kw['ignore_layout'] = int(REQUEST.get('ignore_layout', 0))
1324
        kw['ignore_hide_rows'] = 1
1325 1326 1327
        # remove ignore_layout parameter from cancel_url otherwise we
        # will have two ignore_layout parameters after clicking cancel
        # button.
1328 1329 1330
        split_referer = list(urlsplit(REQUEST.get('HTTP_REFERER')))
        split_referer[3] = '&'.join([x for x in \
                                     split_referer[3].split('&') \
1331
                                     if not re.match('^ignore_layout[:=]', x)])
1332
        kw['cancel_url'] = urlunsplit(split_referer)
1333

1334 1335
        proxy_listbox_ids = field.get_value('proxy_listbox_ids')
        REQUEST.set('proxy_listbox_ids', proxy_listbox_ids)
1336
        if len(proxy_listbox_ids) > 0:
1337 1338
          REQUEST.set('proxy_listbox_id', proxy_listbox_ids[0][0])
        else:
1339
          REQUEST.set('proxy_listbox_id',
1340 1341 1342
                       "Base_viewRelatedObjectListBase/listbox")

        # Empty the selection (uid)
1343 1344
        REQUEST.form = kw # New request form
        # Define new HTTP_REFERER
Romain Courteaud's avatar
Romain Courteaud committed
1345
        REQUEST.HTTP_REFERER = '%s/%s' % (o.absolute_url(),
1346
                                          dialog_id)
1347 1348 1349 1350 1351

        # 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)
1352
        # Return the search dialog
1353
        return getattr(o, dialog_id)(REQUEST=REQUEST)
1354

1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365
    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)
1366 1367
      return selection_domain.asSQLJoinExpression(
          category_table_alias=category_table_alias)
1368 1369 1370

    security.declarePublic('buildSQLExpressionFromDomainSelection')
    def buildSQLExpressionFromDomainSelection(self, selection_domain,
1371
                                              table_map=None, domain_id=None,
1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
                                              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)
1384 1385 1386 1387 1388 1389
      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)
1390

1391 1392
    def _aq_dynamic(self, name):
      """
1393
        Generate viewSearchRelatedDocumentDialog0,
1394
                 viewSearchRelatedDocumentDialog1,... if necessary
1395 1396 1397
      """
      aq_base_name = getattr(aq_base(self), name, None)
      if aq_base_name == None:
1398 1399 1400
        DYNAMIC_METHOD_NAME = 'viewSearchRelatedDocumentDialog'
        method_name_length = len(DYNAMIC_METHOD_NAME)

1401
        zope_security = '__roles__'
1402
        if (name[:method_name_length] == DYNAMIC_METHOD_NAME) and \
1403
           (name[-len(zope_security):] != zope_security):
1404
          method_count_string_list = name[method_name_length:].split('_')
1405 1406 1407 1408
          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
1409
          except TypeError:
1410
            return aq_base_name
1411
          else:
1412 1413 1414
            if len(method_count_string_list) > 1:
              # be sure that method name is correct
              try:
Romain Courteaud's avatar
Romain Courteaud committed
1415
                sub_index = string.atoi(method_count_string_list[1])
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1416
              except TypeError:
1417 1418
                return aq_base_name
            else:
Romain Courteaud's avatar
Romain Courteaud committed
1419
              sub_index = None
1420

1421
            # generate dynamicaly needed forwarder methods
1422
            def viewSearchRelatedDocumentDialogWrapper(self, form_id,
1423
                                                       REQUEST=None, **kw):
1424 1425 1426
              """
                viewSearchRelatedDocumentDialog Wrapper
              """
1427 1428
#               LOG('SelectionTool.viewSearchRelatedDocumentDialogWrapper, kw',
#                   0, kw)
1429
              return self.viewSearchRelatedDocumentDialog(
1430
                                   method_count, form_id,
1431
                                   REQUEST=REQUEST, sub_index=sub_index, **kw)
1432
            setattr(self.__class__, name,
1433
                    viewSearchRelatedDocumentDialogWrapper)
1434 1435

            klass = aq_base(self).__class__
1436 1437 1438 1439
            security_property_id = '%s__roles__' % (name, )
            # Declare method as public
            setattr(klass, security_property_id, None)

1440
            return getattr(self, name)
1441
      return SelectionTool.inheritedAttribute('_aq_dynamic')(self, name)
1442

1443
    def _getUserId(self):
1444
      tv = getTransactionalVariable()
1445 1446 1447 1448 1449 1450 1451
      user_id = tv.get('_user_id', None)
      if user_id is not None:
        return user_id
      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
      if user_id == 'Anonymous User' and self.getAnonymousStorage() is not None:
        anonymous_uid = self.REQUEST.get('anonymous_uid', None)
        if anonymous_uid is None:
1452
          anonymous_uid = md5('%s.%s' % (time(), random())).hexdigest()
1453 1454 1455 1456
          self.REQUEST['anonymous_uid'] = anonymous_uid
        user_id = 'Anonymous:%s' % anonymous_uid
      tv['_user_id'] = user_id
      return user_id
1457

1458 1459 1460 1461
    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."""
1462
      tv = getTransactionalVariable()
1463 1464 1465 1466 1467
      return tv.setdefault('_temporary_selection_dict', {})

    def pushSelection(self, selection_name):
      selection = self.getSelectionFor(selection_name)
      # a temporary selection is kept in transaction.
1468
      temp_selection = Selection(selection_name)
1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479
      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()

1480 1481 1482
    def _getSelectionFromContainer(self, selection_name):
      user_id = self._getUserId()
      if user_id is None: return None
1483 1484 1485 1486 1487 1488 1489

      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]

1490
      return self._getContainer().getSelection(user_id, selection_name)
1491 1492 1493 1494

    def _setSelectionToContainer(self, selection_name, selection):
      user_id = self._getUserId()
      if user_id is None: return
1495 1496 1497 1498 1499 1500 1501 1502

      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

1503
      self._getContainer().setSelection(user_id, selection_name, selection)
1504

1505
    def _deleteSelectionForUserFromContainer(self, selection_name, user_id):
1506
      if user_id is None: return None
1507
      self._getContainer().deleteSelection(user_id, selection_name)
1508

1509 1510 1511 1512
    def _deleteSelectionFromContainer(self, selection_name):
      user_id = self._getUserId()
      self._deleteSelectionForUserFromContainer(selection_name, user_id)

1513
    def _deleteGlobalSelectionFromContainer(self, selection_name):
1514
      self._getContainer().deleteGlobalSelection(self, selection_name)
1515

1516
    def _getSelectionNameListFromContainer(self):
1517 1518 1519 1520
      user_id = self._getUserId()
      return list(set(self._getContainer().getSelectionNameList(user_id) + \
                      self.getTemporarySelectionDict().keys()))

1521 1522 1523
    def _isAnonymous(self):
      return self.portal_membership.isAnonymousUser()

1524
    def _getContainer(self):
1525 1526
      if self._isAnonymous():
        container_id = '_v_anonymous_selection_container'
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
1527
        storage = self.getAnonymousStorage() or self.getStorage()
1528 1529 1530
      else:
        container_id = '_v_selection_container'
        storage = self.getStorage()
1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545
      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
1546

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

1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607
class MemcachedContainer(object):
  def __init__(self, container):
    self._container = container

  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

class PersistentMappingContainer(object):
  def __init__(self, container):
    self._container = container

  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
1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618

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


1625 1626 1627 1628 1629 1630 1631 1632
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
1633

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

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

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

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

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

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

1652

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

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

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

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

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

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

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

1773 1774
  return tree_list

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

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

1783 1784
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]
1785

1786 1787 1788
# Monkey patch FolderMixIn with SelectionTool methods
#   kept here for compatibility with previous implementations
#   of Listbox HTML renderer. See bellow new implementation
1789 1790 1791 1792 1793 1794 1795 1796 1797 1798
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)
1799
  setattr(FolderMixIn, property_id, portal_selection_wrapper)
1800 1801 1802
  security_property_id = '%s__roles__' % (property_id, )
  security_property = getattr(SelectionTool, security_property_id, None)
  if security_property is not None:
1803
    setattr(FolderMixIn, security_property_id, security_property)
1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832

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.
  """
  # 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.
      """
      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
1833
      page_start_property_id = "%s_page_start" % listbox_id
1834 1835
      # Rename request parameters
      if request.has_key(selection_name_property_id):
1836
        request.form['list_selection_name'] = request[selection_name_property_id]
1837 1838 1839 1840
      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
1841 1842
      if request.has_key(page_start_property_id):
        request.form['page_start'] = request[page_start_property_id]
1843 1844 1845 1846 1847 1848 1849 1850 1851 1852
      # 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, )
1853
      setattr(FolderMixIn, new_security_property_id, security_property)