SelectionTool.py 61.5 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
3
# Copyright (c) 2002,2007 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5 6
#
# WARNING: This program as such is intended to be used by professional
7
# programmers who take the whole responsibility of assessing all potential
Jean-Paul Smets's avatar
Jean-Paul Smets committed
8 9
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
10
# guarantees and support are strongly adviced to contract a Free Software
Jean-Paul Smets's avatar
Jean-Paul Smets committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# 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.
#
##############################################################################

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

33
from OFS.Traversable import NotFound
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34 35 36
from OFS.SimpleItem import SimpleItem
from Products.CMFCore.utils import UniqueObject
from Globals import InitializeClass, DTMLFile, PersistentMapping, get_request
37
from ZTUtils import make_query
Jean-Paul Smets's avatar
Jean-Paul Smets committed
38
from AccessControl import ClassSecurityInfo
39
from Products.ERP5Type.Tool.BaseTool import BaseTool
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40 41
from Products.ERP5Type import Permissions as ERP5Permissions
from Products.ERP5Form import _dtmldir
42
from Selection import Selection, DomainSelection
43
from ZPublisher.HTTPRequest import FileUpload
Sebastien Robin's avatar
Sebastien Robin committed
44 45
from email.MIMEBase import MIMEBase
from email import Encoders
Sebastien Robin's avatar
Sebastien Robin committed
46
from copy import copy
47
from DateTime import DateTime
Sebastien Robin's avatar
Sebastien Robin committed
48
import md5
Sebastien Robin's avatar
Sebastien Robin committed
49 50 51
import pickle
import hmac
import random
52
import re
53
import string
Jean-Paul Smets's avatar
Jean-Paul Smets committed
54
from zLOG import LOG
55
from Acquisition import Implicit, aq_base
56
from Products.ERP5Type.Message import Message
57
import warnings
58

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

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

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

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

91 92 93
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_view_selections' )
    manage_view_selections = DTMLFile( 'SelectionTool_manageViewSelections', _dtmldir )
94

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    security.declareProtected( ERP5Permissions.ManagePortal
                             , 'manage_configure' )
    manage_configure = DTMLFile( 'SelectionTool_configure', _dtmldir )

    # storages of SelectionTool
    storage_list = ('Persistent Mapping', 'Memcached Tool')

    security.declareProtected( ERP5Permissions.ManagePortal, 'setStorage')
    def setStorage(self, value, RESPONSE=None):
      """
        Set the storage of Selection Tool.
      """
      if value in self.storage_list:
        self.storage = value
      else:
        raise ValueError, 'Given storage type (%s) is now supported.' % (value,)
      if RESPONSE is not None:
        RESPONSE.redirect('%s/manage_configure' % (self.absolute_url()))

    def getStorage(self, default=None):
      if default is None:
        default = self.storage_list[0]
      storage = getattr(self, 'storage', default)
      if storage is not default and storage not in self.storage_list:
        storage = storage_list[0]
      return storage

    def isMemcachedUsed(self):
      return self.getStorage() == 'Memcached Tool'
      
Vincent Pelletier's avatar
Vincent Pelletier committed
125 126
    def _redirectToOriginalForm(self, REQUEST=None, form_id=None, dialog_id=None,
                                query_string=None,
127
                                no_reset=False, no_report_depth=False):
Vincent Pelletier's avatar
Vincent Pelletier committed
128 129
      """Redirect to the original form or dialog, using the information given
         as parameters.
130 131
         (Actually does not redirect  in the HTTP meaning because of URL
         limitation problems.)
Vincent Pelletier's avatar
Vincent Pelletier committed
132 133 134

         DEPRECATED parameters :
         query_string is used to transmit parameters from caller to callee.
135
         If no_reset is True, replace reset parameters with noreset.
Vincent Pelletier's avatar
Vincent Pelletier committed
136 137
         If no_report_depth is True, replace report_depth parameters with
         noreport_depth.
138 139 140 141
      """
      if REQUEST is None:
        return

142 143 144 145 146 147
      if no_reset and REQUEST.form.has_key('reset'):
        REQUEST.form['noreset'] = REQUEST.form['reset'] # Kept for compatibility - might no be used anymore
        del REQUEST.form['reset']
      if no_report_depth and REQUEST.form.has_key('report_depth'):
        REQUEST.form['noreport_depth'] = REQUEST.form['report_depth'] # Kept for compatibility - might no be used anymore
        del REQUEST.form['report_depth']
Vincent Pelletier's avatar
Vincent Pelletier committed
148

149
      if query_string is not None:
150 151
        warnings.warn('DEPRECATED: _redirectToOriginalForm got called with a query_string. The variables must be passed in REQUEST.form.',
                      DeprecationWarning)
152
      context = REQUEST['PARENTS'][0]
Vincent Pelletier's avatar
Vincent Pelletier committed
153
      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
154
      return getattr(context, form_id)()
155

156 157
    security.declareProtected(ERP5Permissions.View, 'getSelectionNameList')
    def getSelectionNameList(self, context=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
158 159 160
      """
        Returns the selection names of the current user.
      """
161 162
      if self.isMemcachedUsed():
        return []
163 164 165 166
      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
      if user_id is not None:
        prefix = '%s-' % user_id
        return [x.replace(prefix, '', 1) for x in self.getSelectionContainer().keys() if x.startswith(prefix)]
167
      return []
168

169 170 171 172 173 174 175 176
    # 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)

177 178 179 180 181 182 183 184
    security.declareProtected(ERP5Permissions.View, 'callSelectionFor')
    def callSelectionFor(self, selection_name, context=None, REQUEST=None):
      if context is None: context = self
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is None:
        return None
      return selection(context=context)

185 186 187 188
    def getSelectionContainer(self):
      """
        Return the selection container.
      """
189 190 191
      if self.isMemcachedUsed():
        value = getattr(self, '_v_selection_data', None)
        if value is None:
192
          value = self.getPortalObject().portal_memcached.getMemcachedDict(key_prefix='selection_tool')
193 194 195 196
          setattr(self, '_v_selection_data', value)
      else:
        value = getattr(self, '_selection_data', None)
        if value is None:
197
          value = PersistentMapping()
198
          setattr(self, '_selection_data', value)
199 200
      return value

Jean-Paul Smets's avatar
Jean-Paul Smets committed
201 202 203 204 205
    security.declareProtected(ERP5Permissions.View, 'getSelectionFor')
    def getSelectionFor(self, selection_name, REQUEST=None):
      """
        Returns the selection instance for a given selection_name
      """
206 207
      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
      if user_id is not None:
208
        if isinstance(selection_name, (tuple, list)):
209
          selection_name = selection_name[0]
210
        selection = self.getSelectionContainer().get('%s-%s' % (user_id, selection_name))
211 212
        if selection is not None:
          return selection.__of__(self)
213

Jean-Paul Smets's avatar
Jean-Paul Smets committed
214 215 216 217 218
    security.declareProtected(ERP5Permissions.View, 'setSelectionFor')
    def setSelectionFor(self, selection_name, selection_object, REQUEST=None):
      """
        Sets the selection instance for a given selection_name
      """
219 220 221
      if selection_object != None:
        # Set the name so that this selection itself can get its own name.
        selection_object.edit(name = selection_name)
222

223 224
      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
      if user_id is not None:
225
        self.getSelectionContainer()['%s-%s' % (user_id, selection_name)] = aq_base(selection_object)
226
      return
Jean-Paul Smets's avatar
Jean-Paul Smets committed
227

228 229
    security.declareProtected(ERP5Permissions.View, 'getSelectionParamsFor')
    def getSelectionParamsFor(self, selection_name, params=None, REQUEST=None):
230 231 232
      """
        Returns the params in the selection
      """
233
      if params is None: params = {}
234 235
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
236 237
        if len(selection.params) > 0:
          return selection.getParams()
238 239 240 241
        else:
          return params
      else:
        return params
242 243

    # backward compatibility
244 245
    security.declareProtected(ERP5Permissions.View, 'getSelectionParams')
    getSelectionParams = getSelectionParamsFor
246

Jean-Paul Smets's avatar
Jean-Paul Smets committed
247 248 249 250 251 252 253 254 255 256 257 258
    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)
      if selection_object:
        selection_object.edit(params=params)
      else:
        selection_object = Selection(params=params)
      self.setSelectionFor(selection_name, selection_object, REQUEST)

259
    security.declareProtected(ERP5Permissions.View, 'getSelectionDomainDictFor')
260 261 262 263 264 265 266 267 268 269 270
    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:
          return selection.getDomain().asDomainDict
        except AttributeError:
          return {}

271
    security.declareProtected(ERP5Permissions.View, 'getSelectionReportDictFor')
272 273 274 275 276 277 278 279 280 281 282
    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:
          return selection.getReport().asDomainDict
        except AttributeError:
          return {}

Jean-Paul Smets's avatar
Jean-Paul Smets committed
283 284 285
    security.declareProtected(ERP5Permissions.View, 'setSelectionCheckedUidsFor')
    def setSelectionCheckedUidsFor(self, selection_name, checked_uids, REQUEST=None):
      """
286
        Sets the checked uids for a given selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
287 288 289 290 291 292 293 294
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
        selection_object.edit(checked_uids=checked_uids)
      else:
        selection_object = Selection(checked_uids=checked_uids)
      self.setSelectionFor(selection_name, selection_object, REQUEST)

295
    security.declareProtected(ERP5Permissions.View, 'updateSelectionCheckedUidList')
296 297
    def updateSelectionCheckedUidList(self, selection_name, listbox_uid, uids, REQUEST=None):
      """
298 299
        Updates the unchecked uids(listbox_uids) and checked uids (uids)
        for a given selection_name
300 301 302 303 304 305 306 307
      """
      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)

308 309 310
    security.declareProtected(ERP5Permissions.View, 'getSelectionCheckedUidsFor')
    def getSelectionCheckedUidsFor(self, selection_name, REQUEST=None):
      """
311
        Returns the checked uids for a given selection_name
312 313 314
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
315
        #return selection_object.selection_checked_uids
316
        return selection_object.getCheckedUids()
317 318 319
      return []

    security.declareProtected(ERP5Permissions.View, 'checkAll')
320 321
    def checkAll(self, selection_name, listbox_uid=[], REQUEST=None,
                 query_string=None, form_id=None):
322
      """
323
        Check uids in a given listbox_uid list for a given selection_name
324 325 326 327
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
        selection_uid_dict = {}
328
        for uid in selection_object.checked_uids:
329 330
          selection_uid_dict[uid] = 1
        for uid in listbox_uid:
331 332
          try:
            selection_uid_dict[int(uid)] = 1
333
          except ValueError:
334
            selection_uid_dict[uid] = 1
335
        self.setSelectionCheckedUidsFor(selection_name, selection_uid_dict.keys(), REQUEST=REQUEST)
336 337 338
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
339 340

    security.declareProtected(ERP5Permissions.View, 'uncheckAll')
341 342
    def uncheckAll(self, selection_name, listbox_uid=[], REQUEST=None,
                   query_string=None, form_id=None):
343
      """
344
        Uncheck uids in a given listbox_uid list for a given selection_name
345 346 347 348
      """
      selection_object = self.getSelectionFor(selection_name, REQUEST)
      if selection_object:
        selection_uid_dict = {}
349
        for uid in selection_object.checked_uids:
350 351
          selection_uid_dict[uid] = 1
        for uid in listbox_uid:
352 353 354
          try:
            if selection_uid_dict.has_key(int(uid)): del selection_uid_dict[int(uid)]
          except ValueError:
355
            if selection_uid_dict.has_key(uid): del selection_uid_dict[uid]
356
        self.setSelectionCheckedUidsFor(selection_name, selection_uid_dict.keys(), REQUEST=REQUEST)
357 358 359
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string, no_reset=True)
360

Jean-Paul Smets's avatar
Jean-Paul Smets committed
361 362 363 364 365 366 367
    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:
368
        return selection.getListUrl()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
369 370
      else:
        return None
371

372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
    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
389

390
    security.declareProtected(ERP5Permissions.View, 'getSelectionInvertModeUidListFor')
391 392 393 394 395 396 397 398
    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

399 400 401 402 403 404 405 406 407
    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
408 409 410 411 412 413 414
    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:
415
        selection.edit(invert_mode=1, uids=selection_uids, checked_uids=selection_uids)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
416 417

    security.declareProtected(ERP5Permissions.View, 'setSelectionToAll')
418 419
    def setSelectionToAll(self, selection_name, REQUEST=None,
                          reset_domain_tree=False, reset_report_tree=False):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
420 421 422 423 424
      """
        Resets the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
425
        selection.edit(invert_mode=0, params={}, checked_uids=[], report_opened=1)
426
        if reset_domain_tree:
427
          selection.edit(domain=None, domain_path=None, domain_list=None)
428
        if reset_report_tree:
429
          selection.edit(report=None, report_path=None, report_list=None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
430 431 432 433 434 435 436 437 438 439 440

    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')
441 442
    def setSelectionQuickSortOrder(self, selection_name, sort_on, REQUEST=None,
                                   query_string=None, form_id=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
      """
        Defines the sort order of the selection directly from the listbox
        In this method, sort_on is just a string that comes from url
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        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 ascending sort
        if n == 0:
          new_sort_on = [(sort_on,'ascending')]
        selection.edit(sort_on=new_sort_on)

467 468 469
      if REQUEST is not None:
        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
470 471 472 473 474 475 476 477

    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 ()
478
      return selection.sort_on
Jean-Paul Smets's avatar
Jean-Paul Smets committed
479 480 481 482 483 484 485 486 487 488

    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')
489
    def getSelectionColumns(self, selection_name, columns=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
490
      """
491 492
        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
493
      """
494
      if columns is None: columns = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
495 496
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
497 498
        if len(selection.columns) > 0:
          return selection.columns
499
      return columns
Jean-Paul Smets's avatar
Jean-Paul Smets committed
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517


    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')
    def getSelectionStats(self, selection_name, stats=[' ',' ',' ',' ',' ',' '], REQUEST=None):
      """
        Returns the stats in the selection
      """
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        try:
518
          return selection.stats
Yoshinori Okuji's avatar
Yoshinori Okuji committed
519
        except AttributeError:
520
          return stats # That is really bad programming XXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
521 522 523 524 525 526 527 528 529 530 531 532 533
      else:
        return stats


    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:
534
        method = self.unrestrictedTraverse(selection.method_path)
535 536
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
        o = selection_list[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
537 538 539 540 541 542 543 544 545
        url = o.absolute_url()
      else:
        url = REQUEST.url
      url = '%s/%s?selection_index=%s&selection_name=%s' % (url, form_id, 0, selection_name)
      REQUEST.RESPONSE.redirect(url)

    security.declareProtected(ERP5Permissions.View, 'viewLast')
    def viewLast(self, selection_index='', selection_name='', form_id='view', REQUEST=None):
      """
546
        Access last item in a selection
Jean-Paul Smets's avatar
Jean-Paul Smets committed
547 548 549 550 551
      """
      if not REQUEST:
        REQUEST = get_request()
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection:
552
        method = self.unrestrictedTraverse(selection.method_path)
553 554
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
        o = selection_list[-1]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
        url = o.absolute_url()
      else:
        url = REQUEST.url
      url = '%s/%s?selection_index=%s&selection_name=%s' % (url, form_id, -1, selection_name)
      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:
570
        method = self.unrestrictedTraverse(selection.method_path)
571 572
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
        o = selection_list[(int(selection_index) + 1) % len(selection_list)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
        url = o.absolute_url()
      else:
        url = REQUEST.url
      url = '%s/%s?selection_index=%s&selection_name=%s' % (url, form_id, int(selection_index) + 1, selection_name)
      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:
588
        method = self.unrestrictedTraverse(selection.method_path)
589 590
        selection_list = selection(method = method, context=self, REQUEST=REQUEST)
        o = selection_list[(int(selection_index) - 1) % len(selection_list)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
591 592 593 594 595 596 597 598
        url = o.absolute_url()
      else:
        url = REQUEST.url
      url = '%s/%s?selection_index=%s&selection_name=%s' % (url, form_id, int(selection_index) - 1, selection_name)
      REQUEST.RESPONSE.redirect(url)


    # ListBox related methods
599 600

    security.declareProtected(ERP5Permissions.View, 'firstPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
601
    def firstPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
602 603 604 605
      """
        Access the first page of a list
      """
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
606 607 608 609
      selection = self.getSelectionFor(list_selection_name, REQUEST)
      REQUEST.form['list_start'] = 0
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
610 611

    security.declareProtected(ERP5Permissions.View, 'lastPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
612
    def lastPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
613 614 615 616
      """
        Access the last page of a list
      """
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
617
      selection = self.getSelectionFor(list_selection_name, REQUEST)
618 619 620 621 622 623
      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
Vincent Pelletier's avatar
Vincent Pelletier committed
624
      total_lines = REQUEST.form.get('total_size', BIG_INT)
625 626 627
      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))
Vincent Pelletier's avatar
Vincent Pelletier committed
628 629 630
      REQUEST.form['list_start'] = last_page_start
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST)
631

Jean-Paul Smets's avatar
Jean-Paul Smets committed
632
    security.declareProtected(ERP5Permissions.View, 'nextPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
633
    def nextPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
634 635 636
      """
        Access the next page of a list
      """
637
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
638
      selection = self.getSelectionFor(list_selection_name, REQUEST)
639
      params = selection.getParams()
Vincent Pelletier's avatar
Vincent Pelletier committed
640
      lines = params.get('list_lines', 0)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
641
      start = params.get('list_start', 0)
Vincent Pelletier's avatar
Vincent Pelletier committed
642 643 644
      REQUEST.form['list_start'] = int(start) + int(lines)
      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
645 646

    security.declareProtected(ERP5Permissions.View, 'previousPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
647
    def previousPage(self, list_selection_name, listbox_uid, uids=None, REQUEST=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
648 649 650
      """
        Access the previous page of a list
      """
651
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
652
      selection = self.getSelectionFor(list_selection_name, REQUEST)
653
      params = selection.getParams()
Vincent Pelletier's avatar
Vincent Pelletier committed
654
      lines = params.get('list_lines', 0)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
655
      start = params.get('list_start', 0)
Vincent Pelletier's avatar
Vincent Pelletier committed
656 657 658
      REQUEST.form['list_start'] = max(int(start) - int(lines), 0)
      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
659 660

    security.declareProtected(ERP5Permissions.View, 'setPage')
Vincent Pelletier's avatar
Vincent Pelletier committed
661
    def setPage(self, list_selection_name, listbox_uid, query_string=None, uids=None, REQUEST=None):
662
      """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
663
         Sets the current displayed page in a selection
664
      """
665
      if uids is None: uids = []
Vincent Pelletier's avatar
Vincent Pelletier committed
666 667 668 669 670 671
      selection = self.getSelectionFor(list_selection_name, REQUEST)
      params = selection.getParams()
      params['list_start'] = REQUEST.form.get('list_start', 0)
      selection.edit(params=params)
      self.uncheckAll(list_selection_name, listbox_uid)
      return self.checkAll(list_selection_name, uids, REQUEST=REQUEST, query_string=query_string)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
672

673
    # PlanningBox related methods
674

675
    security.declareProtected(ERP5Permissions.View, 'setZoomLevel')
676
    def setZoomLevel(self, uids=None, REQUEST=None, form_id=None, query_string=None):
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
      """
      Set graphic zoom level in PlanningBox
      """
      if uids is None: uids = []
      request = REQUEST
      #zoom_level=request.get('zoom_level')
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
        zoom_level = request.form.get('zoom_level',1)
        zoom_start = request.form.get('zoom_start',0)
        params['zoom_level'] = zoom_level
        if zoom_level <= zoom_start:
          zoom_start = max(int(float(zoom_level)),1) - 1
          params['zoom_start'] = zoom_start
        selection.edit(params= params)
      if REQUEST is not None:
695 696
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                           query_string=query_string)
697

698
    security.declareProtected(ERP5Permissions.View, 'setZoom')
699
    def setZoom(self, uids=None, REQUEST=None, form_id=None, query_string=None):
700 701
      """
      Set graphic zoom in PlanningBox
702
      """
703
      if uids is None: uids = []
704
      request = REQUEST
705 706 707 708
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
709 710
        zoom_start = request.form.get('zoom_start')
        params['zoom_start'] = zoom_start
711
        selection.edit(params= params)
712
      if REQUEST is not None:
713 714
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                           query_string=query_string)
715 716

    security.declareProtected(ERP5Permissions.View, 'nextZoom')
717
    def nextZoom(self, uids=None, REQUEST=None, form_id=None, query_string=None):
718 719 720 721 722 723 724 725 726 727 728 729 730
      """
      Set next graphic zoom start in PlanningBox
      """
      if uids is None: uids = []
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
        zoom_start = params.get('zoom_start')
        params['zoom_start'] = int(zoom_start) + 1
        selection.edit(params= params)
      if REQUEST is not None:
731 732
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                           query_string=query_string)
733 734

    security.declareProtected(ERP5Permissions.View, 'previousZoom')
735
    def previousZoom(self, uids=None, REQUEST=None, form_id=None, query_string=None):
736 737 738 739 740 741 742 743 744 745 746 747 748
      """
      Set previous graphic zoom in PlanningBox
      """
      if uids is None: uids = []
      request = REQUEST
      selection_name=request.list_selection_name
      selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
      if selection is not None:
        params = selection.getParams()
        zoom_start = params.get('zoom_start')
        params['zoom_start'] = int(zoom_start) - 1
        selection.edit(params= params)
      if REQUEST is not None:
749 750
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
			                   query_string=query_string)
751

Jean-Paul Smets's avatar
Jean-Paul Smets committed
752
    security.declareProtected(ERP5Permissions.View, 'setDomainRoot')
753
    def setDomainRoot(self, REQUEST, form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
754 755 756
      """
        Sets the root domain for the current selection
      """
757
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
758
      selection = self.getSelectionFor(selection_name, REQUEST)
759
      root_url = REQUEST.form.get('domain_root_url','portal_categories')
760
      selection.edit(domain_path=root_url, domain_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
761

762 763 764
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
765

766 767 768 769 770 771
    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
772

773
    security.declareProtected(ERP5Permissions.View, 'unfoldDomain')
774
    def unfoldDomain(self, REQUEST, form_id=None, query_string=None):
775 776 777
      """
        Unfold domain for the current selection
      """
778
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
779
      selection = self.getSelectionFor(selection_name, REQUEST)
780 781
      domain_url = REQUEST.form.get('domain_url',None)
      domain_depth = REQUEST.form.get('domain_depth',0)
782 783
      domain_list = list(selection.getDomainList())
      domain_list = domain_list[0:min(domain_depth, len(domain_list))]
784
      if isinstance(domain_url, str):
785
        selection.edit(domain_list = domain_list + [domain_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
786

787 788 789
      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
790

791
    security.declareProtected(ERP5Permissions.View, 'foldDomain')
792
    def foldDomain(self, REQUEST, form_id=None, query_string=None):
793 794 795
      """
        Fold domain for the current selection
      """
796
      selection_name = REQUEST.list_selection_name
797
      selection = self.getSelectionFor(selection_name, REQUEST)
798 799
      domain_url = REQUEST.form.get('domain_url',None)
      domain_depth = REQUEST.form.get('domain_depth',0)
800 801
      domain_list = list(selection.getDomainList())
      domain_list = domain_list[0:min(domain_depth, len(domain_list))]
802
      selection.edit(domain_list=[x for x in domain_list if x != domain_url])
803

804 805 806
      if REQUEST is not None:
        return self._redirectToOriginalForm(REQUEST=REQUEST, form_id=form_id,
                                            query_string=query_string)
807

808

Jean-Paul Smets's avatar
Jean-Paul Smets committed
809
    security.declareProtected(ERP5Permissions.View, 'setReportRoot')
810
    def setReportRoot(self, REQUEST, form_id=None, query_string=None):
811 812 813
      """
        Sets the root report for the current selection
      """
814
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
815
      selection = self.getSelectionFor(selection_name, REQUEST)
816
      root_url = REQUEST.form.get('report_root_url','portal_categories')
817
      selection.edit(report_path=root_url, report_list=())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
818

819 820 821
      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
822 823

    security.declareProtected(ERP5Permissions.View, 'unfoldReport')
824
    def unfoldReport(self, REQUEST, form_id=None, query_string=None):
825 826 827
      """
        Unfold report for the current selection
      """
828
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
829
      selection = self.getSelectionFor(selection_name, REQUEST)
830
      report_url = REQUEST.form.get('report_url',None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
831
      if type(report_url) == type('a'):
832
        selection.edit(report_list=list(selection.getReportList()) + [report_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
833

834 835 836
      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
837 838

    security.declareProtected(ERP5Permissions.View, 'foldReport')
839
    def foldReport(self, REQUEST, form_id=None, query_string=None):
840 841 842
      """
        Fold domain for the current selection
      """
843
      selection_name = REQUEST.list_selection_name
Jean-Paul Smets's avatar
Jean-Paul Smets committed
844
      selection = self.getSelectionFor(selection_name, REQUEST)
845
      report_url = REQUEST.form.get('report_url',None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
846
      if type(report_url) == type('a'):
847
        report_list = selection.getReportList()
848
        selection.edit(report_list=[x for x in report_list if x != report_url])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
849

850 851 852
      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
853

854 855 856 857
    security.declareProtected(ERP5Permissions.View, 'getListboxDisplayMode')
    def getListboxDisplayMode(self, selection_name, REQUEST=None):
      if REQUEST is None:
        REQUEST = get_request()
858
      selection = self.getSelectionFor(selection_name, REQUEST)
859

860 861 862 863 864
      if getattr(selection, 'report_tree_mode', 0):
        return 'ReportTreeMode'
      elif getattr(selection, 'domain_tree_mode', 0):
        return 'DomainTreeMode'
      return 'FlatListMode'
865

Jean-Paul Smets's avatar
Jean-Paul Smets committed
866
    security.declareProtected(ERP5Permissions.View, 'setListboxDisplayMode')
867
    def setListboxDisplayMode(self, REQUEST, listbox_display_mode,
868 869
                              selection_name=None, redirect=0,
                              form_id=None, query_string=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
870
      """
871
        Toggle display of the listbox
Jean-Paul Smets's avatar
Jean-Paul Smets committed
872 873
      """
      request = REQUEST
874 875 876 877 878 879 880
      # 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
881 882
      # Possible fix: currently, display mode icon are implemented as
      # method. It could be easier to generate them as link (where we
883 884 885 886 887 888 889 890 891
      # 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
892
      selection = self.getSelectionFor(selection_name, REQUEST)
893 894
      if selection is None:
        selection = Selection()
895
        self.setSelectionFor(selection_name, selection, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
896 897 898 899 900 901 902 903 904 905 906 907 908

      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
909 910 911
      else:
        flat_list_mode = 0
        domain_tree_mode = 0
912
        report_tree_mode = 0
913

914 915 916
      selection.edit(flat_list_mode=flat_list_mode,
                     domain_tree_mode=domain_tree_mode,
                     report_tree_mode=report_tree_mode)
917
      # It is better to reset the query when changing the display mode.
918 919
      params = selection.getParams()
      if 'where_expression' in params: del params['where_expression']
920 921
      selection.edit(params = params)

922
      if redirect:
923 924 925
        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
926 927

    security.declareProtected(ERP5Permissions.View, 'setFlatListMode')
928
    def setFlatListMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
929 930 931
      """
        Set display of the listbox to FlatList mode
      """
932
      return self.setListboxDisplayMode(
933
                       REQUEST=REQUEST, listbox_display_mode='FlatListMode',
934
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
935 936

    security.declareProtected(ERP5Permissions.View, 'setDomainTreeMode')
937
    def setDomainTreeMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
938 939 940
      """
         Set display of the listbox to DomainTree mode
      """
941
      return self.setListboxDisplayMode(
942
                       REQUEST=REQUEST, listbox_display_mode='DomainTreeMode',
943
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
944 945

    security.declareProtected(ERP5Permissions.View, 'setReportTreeMode')
946
    def setReportTreeMode(self, REQUEST, selection_name=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
947 948 949
      """
        Set display of the listbox to ReportTree mode
      """
950 951 952
      return self.setListboxDisplayMode(
                       REQUEST=REQUEST, listbox_display_mode='ReportTreeMode',
                       selection_name=selection_name, redirect=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
953

954 955 956 957 958 959
    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)
960 961
      if selection is None:
        return []
962
      return selection(method=selection_method, context=context, REQUEST=REQUEST)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
963

964 965 966 967 968 969
    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)
970 971
      if selection is None:
        return []
972
      uid_list = selection.getCheckedUids()
973 974 975 976 977 978 979 980 981 982
      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:
983 984
        value_list = self.getSelectionSelectedValueList(
                                            selection_name,
985 986
                                            REQUEST=REQUEST,
                                            selection_method=selection_method,
987
                                            context=context)
988
      return value_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
989

Jean-Paul Smets's avatar
Jean-Paul Smets committed
990 991 992 993 994
    security.declareProtected(ERP5Permissions.View, 'getSelectionUidList')
    def getSelectionUidList(self, selection_name, REQUEST=None, selection_method=None, context=None):
      """
        Get the list of values checked or selected for 'selection_name'
      """
995
      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
996

Sebastien Robin's avatar
Sebastien Robin committed
997 998 999 1000 1001
    security.declareProtected(ERP5Permissions.View, 'selectionHasChanged')
    def selectionHasChanged(self, md5_string, object_uid_list):
      """
        We want to be sure that the selection did not change
      """
1002
      # XXX To avoid the difference of the string representations of int and long,
1003
      # convert each element to a string.
1004 1005 1006 1007
      object_uid_list = [str(x) for x in object_uid_list]
      object_uid_list.sort()
      new_md5_string = md5.new(str(object_uid_list)).hexdigest()
      return md5_string != new_md5_string
Sebastien Robin's avatar
Sebastien Robin committed
1008

1009 1010
    security.declareProtected(ERP5Permissions.View, 'getPickle')
    def getPickle(self,**kw):
Sebastien Robin's avatar
Sebastien Robin committed
1011 1012
      """
      we give many keywords and we will get the corresponding
1013
      pickle string
Sebastien Robin's avatar
Sebastien Robin committed
1014
      """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1015
      #LOG('getPickle kw',0,kw)
1016 1017 1018
      # XXX Remove DateTime, This is really bad, only use for zope 2.6
      # XXX This has to be removed as quickly as possible
      for k,v in kw.items():
Sebastien Robin's avatar
Sebastien Robin committed
1019
        if isinstance(v,DateTime):
1020 1021
          del kw[k]
      # XXX End of the part to remove
1022
      #LOG('SelectionTool.getPickle, kw',0,kw)
Sebastien Robin's avatar
Sebastien Robin committed
1023 1024 1025 1026 1027 1028
      pickle_string = pickle.dumps(kw)
      msg = MIMEBase('application','octet-stream')
      msg.set_payload(pickle_string)
      Encoders.encode_base64(msg)
      pickle_string = msg.get_payload()
      pickle_string = pickle_string.replace('\n','@@@')
1029 1030 1031 1032 1033 1034 1035 1036
      return pickle_string

    security.declareProtected(ERP5Permissions.View, 'getPickleAndSignature')
    def getPickleAndSignature(self,**kw):
      """
      we give many keywords and we will get the corresponding
      pickle string and signature
      """
1037
      cookie_password = self._getCookiePassword()
1038
      pickle_string = self.getPickle(**kw)
Sebastien Robin's avatar
Sebastien Robin committed
1039 1040 1041
      signature = hmac.new(cookie_password,pickle_string).hexdigest()
      return (pickle_string,signature)

1042 1043 1044
    security.declareProtected(ERP5Permissions.View, 'getObjectFromPickle')
    def getObjectFromPickle(self,pickle_string):
      """
1045
      get object from a pickle string
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
      """
      object = None
      pickle_string = pickle_string.replace('@@@','\n')
      msg = MIMEBase('application','octet-stream')
      Encoders.encode_base64(msg)
      msg.set_payload(pickle_string)
      pickle_string = msg.get_payload(decode=1)
      object = pickle.loads(pickle_string)
      return object

Sebastien Robin's avatar
Sebastien Robin committed
1056 1057 1058
    security.declareProtected(ERP5Permissions.View, 'getObjectFromPickleAndSignature')
    def getObjectFromPickleAndSignature(self,pickle_string,signature):
      """
1059
      get object from a pickle string only when a signature maches
Sebastien Robin's avatar
Sebastien Robin committed
1060 1061 1062 1063 1064
      """
      cookie_password = self._getCookiePassword()
      object = None
      new_signature = hmac.new(cookie_password,pickle_string).hexdigest()
      if new_signature==signature:
1065
        object = self.getObjectFromPickle(pickle_string)
Sebastien Robin's avatar
Sebastien Robin committed
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
      return object

    security.declarePrivate('_getCookiePassword')
    def _getCookiePassword(self):
      """
      get the password used for encryption
      """
      cookie_password = getattr(self,'cookie_password',None)
      if cookie_password is None:
        cookie_password = str(random.randrange(1,2147483600))
        self.cookie_password = cookie_password
      return cookie_password

1079
    security.declareProtected(ERP5Permissions.View, 'setCookieInfo')
Sebastien Robin's avatar
Sebastien Robin committed
1080 1081
    def setCookieInfo(self,request,cookie_name,**kw):
      """
1082
      register info directly in cookie
Sebastien Robin's avatar
Sebastien Robin committed
1083 1084 1085 1086 1087 1088 1089
      """
      cookie_name = cookie_name + '_cookie'
      (pickle_string,signature) = self.getPickleAndSignature(**kw)
      request.RESPONSE.setCookie(cookie_name,pickle_string,max_age=15*60)
      signature_cookie_name = cookie_name + '_signature'
      request.RESPONSE.setCookie(signature_cookie_name,signature,max_age=15*60)

1090
    security.declareProtected(ERP5Permissions.View, 'getCookieInfo')
Sebastien Robin's avatar
Sebastien Robin committed
1091 1092
    def getCookieInfo(self,request,cookie_name):
      """
1093
      get info directly from cookie
Sebastien Robin's avatar
Sebastien Robin committed
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
      """
      cookie_name = cookie_name + '_cookie'
      object = None
      if getattr(request,cookie_name,None) is not None:
        pickle_string = request.get(cookie_name)
        signature_cookie_name = cookie_name + '_signature'
        signature = request.get(signature_cookie_name)
        object = self.getObjectFromPickleAndSignature(pickle_string,signature)
      if object is None:
        object = {}
      return object

1106
    # Related document searching
1107
    def viewSearchRelatedDocumentDialog(self, index, form_id,
1108
                                        REQUEST=None, sub_index=None, **kw):
1109
      """
1110 1111
      Returns a search related document dialog
      A set of forwarders us defined to circumvent limitations of HTML
1112
      """
Romain Courteaud's avatar
Romain Courteaud committed
1113 1114
      if sub_index != None:
        REQUEST.form['sub_index'] = sub_index
1115
      object_path = REQUEST.form['object_path']
1116
      # Find the object which needs to be updated
1117
      o = self.restrictedTraverse(object_path)
1118
      # Find the field which was clicked on
1119
      # Important to get from the object instead of self
1120
      form = getattr(o, form_id)
1121
      field = None
1122 1123
      # Search the correct field
      relation_field_found = 0
1124
      relation_index = 0
1125
      # XXX may be should support another parameter,
1126
      for field in form.get_fields(include_disabled=0):
1127 1128
        if field.get_value('editable', REQUEST=REQUEST):
          try:
1129 1130
           field.get_value('is_relation_field')
          except KeyError:
1131
            pass
1132
          else:
1133 1134 1135 1136 1137
            if index == relation_index:
              relation_field_found = 1
              break
            else:
              relation_index += 1
1138
      if not relation_field_found:
1139
        # We didn't find the field...
1140
        raise SelectionError, "SelectionTool: can not find the relation" \
1141
                              " field %s" % index
1142 1143 1144 1145 1146
      else:
        # Field found
        field_key = field.generate_field_key()
        field_value = REQUEST.form[field_key]
        # XXX Hardcoded form name
1147 1148
        dialog_id = 'Base_viewRelatedObjectList'
        redirect_form = getattr(o, dialog_id)
1149 1150 1151 1152 1153 1154 1155 1156
        # XXX Hardcoded listbox field
        selection_name = redirect_form.listbox.get_value('selection_name')
        # Reset current selection
        self.portal_selections.setSelectionFor(selection_name, None)


        if (field.get_value('is_multi_relation_field')) and \
           (sub_index is None):
1157 1158 1159 1160
          # user click on the wheel, not on the validation button
          # we need to facilitate user search

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

1163 1164 1165 1166 1167 1168
          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
1169 1170 1171
          # Checked current uid
          kw ={}
          kw[field.get_value('catalog_index')] = field_value
1172
          self.portal_selections.setSelectionParamsFor(selection_name,
1173 1174
                                                       kw.copy())
          self.portal_selections.setSelectionCheckedUidsFor(
1175
                                             selection_name,
1176
                                             current_uid_list)
1177 1178 1179 1180 1181 1182 1183 1184
          field_value = str(field_value).splitlines()
          REQUEST.form[field_key] = field_value
          portal_status_message = Message(
                          domain='erp5_ui',
                          message="Please select one (or more) object.")
        else:
          portal_status_message = Message(domain='erp5_ui',
                                          message="Please select one object.")
1185 1186


1187 1188 1189 1190 1191 1192
        # Save the current REQUEST form
        # We can't put FileUpload instances because we can't pickle them
        pickle_kw = {}
        for key in REQUEST.form.keys():
          if not isinstance(REQUEST.form[key],FileUpload):
            pickle_kw[key] = REQUEST.form[key]
1193
        form_pickle, form_signature = self.getPickleAndSignature(**pickle_kw)
1194

1195 1196
        base_category = None
        kw = {}
1197
        kw['dialog_id'] = dialog_id
1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
        kw['selection_name'] = selection_name
        kw['selection_index'] = 0 # We start on the first page
        kw['field_id'] = field.id
        kw['portal_type'] = [x[0] for x in field.get_value('portal_type')]
        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')
        kw['cancel_url'] = REQUEST.get('HTTP_REFERER')
1209
        kw['form_id'] = form_id
1210 1211
        kw[field.get_value('catalog_index')] = field_value
        kw['portal_status_message'] = portal_status_message
1212
        kw['form_pickle'] = form_pickle
1213 1214 1215 1216 1217
        kw['form_signature'] = form_signature

         # Empty the selection (uid)
        REQUEST.form = kw # New request form
        # Define new HTTP_REFERER
Romain Courteaud's avatar
Romain Courteaud committed
1218
        REQUEST.HTTP_REFERER = '%s/%s' % (o.absolute_url(),
1219
                                          dialog_id)
1220 1221 1222 1223 1224

        # 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)
1225
        # Return the search dialog
1226
        return getattr(o, dialog_id)(REQUEST=REQUEST)
1227

1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242
    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)
      return selection_domain.asSQLJoinExpression(category_table_alias = category_table_alias)

    security.declarePublic('buildSQLExpressionFromDomainSelection')
    def buildSQLExpressionFromDomainSelection(self, selection_domain,
1243
                                              table_map=None, domain_id=None,
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258
                                              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)
      return selection_domain.asSQLExpression(strict_membership = strict_membership,
                                              category_table_alias = category_table_alias)

1259 1260
    def _aq_dynamic(self, name):
      """
1261
        Generate viewSearchRelatedDocumentDialog0,
1262
                 viewSearchRelatedDocumentDialog1,... if necessary
1263 1264 1265
      """
      aq_base_name = getattr(aq_base(self), name, None)
      if aq_base_name == None:
1266 1267 1268
        DYNAMIC_METHOD_NAME = 'viewSearchRelatedDocumentDialog'
        method_name_length = len(DYNAMIC_METHOD_NAME)

1269
        zope_security = '__roles__'
1270
        if (name[:method_name_length] == DYNAMIC_METHOD_NAME) and \
1271
           (name[-len(zope_security):] != zope_security):
1272
          method_count_string_list = name[method_name_length:].split('_')
1273 1274 1275 1276
          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
1277
          except TypeError:
1278
            return aq_base_name
1279
          else:
1280 1281 1282
            if len(method_count_string_list) > 1:
              # be sure that method name is correct
              try:
Romain Courteaud's avatar
Romain Courteaud committed
1283
                sub_index = string.atoi(method_count_string_list[1])
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1284
              except TypeError:
1285 1286
                return aq_base_name
            else:
Romain Courteaud's avatar
Romain Courteaud committed
1287
              sub_index = None
1288

1289
            # generate dynamicaly needed forwarder methods
1290
            def viewSearchRelatedDocumentDialogWrapper(self, form_id,
1291
                                                       REQUEST=None, **kw):
1292 1293 1294
              """
                viewSearchRelatedDocumentDialog Wrapper
              """
1295 1296
#               LOG('SelectionTool.viewSearchRelatedDocumentDialogWrapper, kw',
#                   0, kw)
1297
              return self.viewSearchRelatedDocumentDialog(
1298
                                   method_count, form_id,
1299
                                   REQUEST=REQUEST, sub_index=sub_index, **kw)
1300
            setattr(self.__class__, name,
1301
                    viewSearchRelatedDocumentDialogWrapper)
1302 1303

            klass = aq_base(self).__class__
1304 1305 1306 1307
            security_property_id = '%s__roles__' % (name, )
            # Declare method as public
            setattr(klass, security_property_id, None)

1308 1309 1310 1311
            return getattr(self, name)
        else:
          return aq_base_name
      return aq_base_name
1312

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1313
InitializeClass( SelectionTool )
1314 1315 1316 1317 1318 1319 1320 1321 1322

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
1323

1324 1325
  def getObject(self):
    return self.object
1326

1327 1328
  def getIsPureSummary(self):
    return self.is_pure_summary
1329

1330 1331
  def getDepth(self):
    return self.depth
1332

1333 1334
  def getIsOpen(self):
    return self.is_open
1335 1336

  def getSelectDomainDict(self):
1337
    return self.select_domain_dict
1338

1339 1340
  def getExceptionUidList(self):
    return self.exception_uid_list
1341

1342

1343 1344
def makeTreeList(here, form, root_dict, report_path, base_category,
		  depth, unfolded_list, form_id, selection_name,
1345
		  report_depth, is_report_opened=1, list_method=None,
1346
		  filtered_portal_types=[] ,sort_on = (('id', 'ASC'),)):
1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372
  """
    (object, is_pure_summary, depth, is_open, select_domain_dict)

    select_domain_dict is a dictionary of  associative list of (id, domain)
  """
  if type(report_path) is type('a'): report_path = report_path.split('/')

  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:
        if base_category in portal_categories.objectIds():
          if base_category == 'parent':
            # parent has a special treatment
            root = root_dict[base_category] = root_dict[None] = here
            report_path = report_path[1:]
1373
          else:
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393
            root = root_dict[base_category] = root_dict[None] = portal_categories[base_category]
            report_path = report_path[1:]
      if root is None and portal_domains is not None:
        if base_category in portal_domains.objectIds():
          root = root_dict[base_category] = root_dict[None] = portal_domains[base_category]
          report_path = report_path[1:]
      if root is None:
        try:
          root = root_dict[None] = portal_object.unrestrictedTraverse(report_path)
        except KeyError:
          root = None
        report_path = ()
    else:
      root = root_dict[None] = root_dict[base_category]
      report_path = report_path[1:]
    is_empty_level = (root.objectCount() == 0) and (len(report_path) != 0)
    if is_empty_level: base_category = report_path[0]

  tree_list = []
  if root is None: return tree_list
1394

1395
  if base_category == 'parent':
1396 1397 1398 1399 1400
    # 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)
1401
    else:
1402
      object_list = list_method(portal_type=filtered_portal_types)
1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418
    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)
1419
          tree_list += [TreeListLine(o, 1, depth, 1, selection_domain, exception_uid_list)]
1420 1421
          if is_report_opened :
            # List (contents, closed, must be strict selection)
1422 1423 1424
            tree_list += [TreeListLine(o, 0, depth, 0, selection_domain, exception_uid_list)]

          tree_list += makeTreeList(here, form, new_root_dict, report_path,
1425
      		    base_category, depth + 1, unfolded_list, form_id, 
1426
      		    selection_name, report_depth,
1427 1428 1429
      		    is_report_opened=is_report_opened, sort_on=sort_on)
        else:
          tree_list += [TreeListLine(o, 1, depth, 0, selection_domain, ())] # Summary (closed)
1430
  else:
1431 1432 1433 1434 1435 1436
    # 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:
1437 1438 1439 1440 1441 1442 1443
      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)
1444 1445
        tree_list += makeTreeList(here, form, new_root_dict, report_path, base_category, depth + 1,
            unfolded_list, form_id, selection_name, report_depth,
1446 1447 1448 1449
            is_report_opened=is_report_opened, sort_on=sort_on)
      else:

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

1451 1452
  return tree_list

1453
# Automaticaly add wrappers on Folder so it can access portal_selections.
1454
# Cannot be done in ERP5Type/Document/Folder.py because ERP5Type must not
1455
# depend on ERP5Form.
1456 1457

from Products.CMFCore.utils import getToolByName
1458
from Products.ERP5Type.Core.Folder import FolderMixIn
1459 1460
from ZPublisher.mapply import mapply

1461 1462
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]
1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473

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)
1474
  setattr(FolderMixIn, property_id, portal_selection_wrapper)
1475 1476 1477
  security_property_id = '%s__roles__' % (property_id, )
  security_property = getattr(SelectionTool, security_property_id, None)
  if security_property is not None:
1478
    setattr(FolderMixIn, security_property_id, security_property)