Selection.py 16.1 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3
##############################################################################
#
# Copyright (c) 2002 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 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# 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.
#
##############################################################################

from Globals import InitializeClass, Persistent, Acquisition
30
from Acquisition import aq_base, aq_inner, aq_parent, aq_self
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31 32 33 34
from OFS.SimpleItem import SimpleItem
from OFS.Traversable import Traversable
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions as ERP5Permissions
35
from Products.PythonScripts.Utility import allow_class
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37
import string

38 39 40 41
# Put a try in front XXX
from Products.CMFCategory.Category import Category
from Products.ERP5.Document.Domain import Domain

Jean-Paul Smets's avatar
Jean-Paul Smets committed
42 43 44 45 46 47 48 49 50 51 52 53
from zLOG import LOG

class Selection(Acquisition.Implicit, Traversable, Persistent):
    """
        Selection

        A Selection instance allows a ListBox object to browse the data
        resulting from a method call such as an SQL Method Call. Selection
        instances are used to implement persistent selections in ERP5.

        Selection uses the following control variables

54
        - method      --  a method which will be used
Jean-Paul Smets's avatar
Jean-Paul Smets committed
55 56
                                    to select objects

57
        - params      --  a dictionnary of parameters to call the
Jean-Paul Smets's avatar
Jean-Paul Smets committed
58 59
                                    method with

60
        - sort_on     --  a dictionnary of parameters to sort
Jean-Paul Smets's avatar
Jean-Paul Smets committed
61 62
                                    the selection

63
        - uids        --  a list of object uids which defines the
Jean-Paul Smets's avatar
Jean-Paul Smets committed
64 65
                                    selection

66
        - invert_mode --  defines the mode of the selection
Jean-Paul Smets's avatar
Jean-Paul Smets committed
67 68 69
                                    if mode is 1, then only show the
                                    ob

70
        - list_url    --  the URL to go back to list mode
Jean-Paul Smets's avatar
Jean-Paul Smets committed
71

72
        - checked_uids --  a list of uids checked
Jean-Paul Smets's avatar
Jean-Paul Smets committed
73

74
        - domain_path --  the path to the root of the selection tree
Jean-Paul Smets's avatar
Jean-Paul Smets committed
75 76


77 78
        - domain_list --  the relative path of the current selected domain
                                    XXX this will have to be updated for cartesion product
Jean-Paul Smets's avatar
Jean-Paul Smets committed
79

80
        - report_path --  the report path
Jean-Paul Smets's avatar
Jean-Paul Smets committed
81

82 83
        - report_list -- list of open report nodes
                                    XXX this will have to be updated for cartesion product
84 85 86 87

        - domain                -- a DomainSelection instance

        - report                -- a DomainSelection instance
Jean-Paul Smets's avatar
Jean-Paul Smets committed
88

89
        - flat_list_mode  --
90 91 92 93 94

        - domain_tree_mode --

        - report_tree_mode --

Jean-Paul Smets's avatar
Jean-Paul Smets committed
95
    """
96

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    method_path=None
    params={}
    sort_on=()
    default_sort_on=None
    uids=()
    invert_mode=0
    list_url=''
    columns=()
    checked_uids=()
    name=None
    index=None
    domain_path = ('portal_categories',)
    domain_list = ((),)
    report_path = ('portal_categories',)
    report_list = ((),)
    domain=None
    report=None
114
    report_opened=None
115

Jean-Paul Smets's avatar
Jean-Paul Smets committed
116
    security = ClassSecurityInfo()
117
    security.declareObjectPublic()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
118

119
    security.declarePublic('domain')
120 121
    security.declarePublic('report')

122 123 124
    def getId(self):
      return self.name
      
125
    def __init__(self, method_path=None, params=None, sort_on=None, default_sort_on=None,
126
                 uids=None, invert_mode=0, list_url='', domain=None, report=None,
127
                 columns=None, checked_uids=None, name=None, index=None):
128 129 130 131 132
        if params is None: params = {}
        if sort_on is None: sort_on = []
        if uids is None: uids = []
        if columns is None: columns = []
        if checked_uids is None: checked_uids = []
133 134 135 136
        # XXX Because method_path is an URI, it must be in ASCII.
        #     Shouldn't Zope automatically does this conversion? -yo
        if type(method_path) is type(u'a'):
          method_path = method_path.encode('ascii')
137 138 139 140 141 142 143 144 145 146 147 148
        self.method_path = method_path
        self.params = params
        self.uids = uids
        self.invert_mode = invert_mode
        self.list_url = list_url
        self.columns = columns
        self.sort_on = sort_on
        self.default_sort_on = default_sort_on
        self.checked_uids = checked_uids
        self.name = name
        self.index = index
        self.domain_path = ('portal_categories',)
149
        self.domain_list = ()
150
        self.report_path = None
151
        self.report_list = ()
152 153
        self.domain = None
        self.report = None
154
        self.report_opened = None
155 156

    security.declarePrivate('edit')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
157 158
    def edit(self, params=None, **kw):
        if params is not None:
159
          self.params = {}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
160 161 162 163 164
          for key in params.keys():
            # We should only keep params which do not start with field_
            # in order to make sure we do not collect unwanted params
            # resulting form the REQUEST generated by an ERP5Form submit
            if key[0:6] != 'field_':
165
              self.params[key] = params[key]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
166 167
        if kw is not None:
          for k,v in kw.items():
168
            if k in ('domain', 'report') or v is not None:
169 170 171 172
              # XXX Because method_path is an URI, it must be in ASCII.
              #     Shouldn't Zope automatically does this conversion? -yo
              if k == 'method_path' and type(v) is type(u'a'):
                v = v.encode('ascii')
173
              setattr(self, k, v)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
174

175
    def __call__(self, method = None, context=None, REQUEST=None):
176
        #LOG("Selection", 0, str(self.__dict__))
177 178 179
        #LOG("Selection", 0, str(method))
        #LOG('Selection', 0, "self.invert_mode = %s" % repr(self.invert_mode))
        if self.invert_mode is 0:
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
          kw = self.params
        else:
          kw = self.params.copy()
          kw['uid'] = self.uids
        if method is None or type(method) is type('a'):
          method_path = method or self.method_path
          method = context.unrestrictedTraverse(method_path)
        if type(method) is type('a'):
          method = context.unrestrictedTraverse(self.method_path)
        sort_on = getattr(self, 'sort_on', [])
        if len(sort_on) == 0:
          sort_on = getattr(self, 'default_sort_on', [])
        if len(sort_on) > 0:
          self.params['sort_on'] = sort_on
        elif self.params.has_key('sort_on'):
          del self.params['sort_on']
        if method is not None:
          if callable(method):
            #LOG('Selection', 0, "self.params = %s" % repr(self.params))
            if self.domain is not None and self.report is not None:
              result = method(selection_domain = self.domain,
                              selection_report = self.report, selection=self, **kw)
            elif self.domain is not None:
              result = method(selection_domain = self.domain, selection=self, **kw)
            elif self.report is not None:
              result = method(selection_report = self.report, selection=self, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
206
            else:
207 208
              result = method(selection=self, **kw)
            return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
209 210 211
          else:
            return []
        else:
212
          return []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
213 214 215 216

    def __getitem__(self, index, REQUEST=None):
        return self(REQUEST)[index]

217 218
    security.declarePublic('getName')
    def getName(self):
219 220 221
        """
          Get the name of this selection.
        """
222
        return self.name
223

224 225
    security.declarePublic('getIndex')
    def getIndex(self):
226 227 228
        """
          Get the index of this selection.
        """
229
        return self.index
230

231 232 233 234 235 236 237
    security.declarePublic('getDomain')
    def getDomain(self):
        """
          Get the domain selection of this selection.
        """
        return self.domain

238 239 240 241 242 243 244
    security.declarePublic('getReport')
    def getReport(self):
        """
          Get the report selection of this selection.
        """
        return self.report

245 246
    security.declarePublic('getParams')
    def getParams(self):
247 248 249
        """
          Get a dictionary of parameters in this selection.
        """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
250
        #LOG('getParams',0,'params: %s' % str(self.params))
251 252 253 254 255 256
        if self.params is None:
          self.params = {}
        if type(self.params) != type({}):
          self.params = {}
        return self.params

257 258 259 260 261 262 263
    security.declarePublic('getSortOrder')
    def getSortOrder(self):
        """
          Return sort order stored in selection
        """
        return self.sort_on

264 265
    security.declarePublic('getListUrl')
    def getListUrl(self):
Sebastien Robin's avatar
Sebastien Robin committed
266
        result = ''
Yoshinori Okuji's avatar
Yoshinori Okuji committed
267
        #LOG('getListUrl', 0, 'list_url = %s' % str(self.list_url))
268 269
        if self.list_url is None:
          self.list_url = ''
Sebastien Robin's avatar
Sebastien Robin committed
270
        else:
271
          result = self.list_url
Sebastien Robin's avatar
Sebastien Robin committed
272
        return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273

274 275 276 277 278 279 280 281 282
    security.declarePublic('getCheckedUids')
    def getCheckedUids(self):
        if not hasattr(self, 'checked_uids'):
          self.checked_uids = []
        elif self.checked_uids is None:
          self.checked_uids = []
        return self.checked_uids

    security.declarePublic('getDomainPath')
283
    def getDomainPath(self, default=None):
284
        if self.domain_path is None:
285 286 287 288
          if default is None:
            self.domain_path = self.getDomainList()[0]
          else:            
            self.domain_path = default
289 290 291 292 293 294 295 296 297
        return self.domain_path

    security.declarePublic('getDomainList')
    def getDomainList(self):
        if self.domain_list is None:
          self.domain_list = (('portal_categories',),)
        return self.domain_list

    security.declarePublic('getReportPath')
298
    def getReportPath(self, default=None):
299
        if self.report_path is None:
300 301 302 303
          if default is None:
            self.report_path = self.getReportList()[0]
          else:            
            self.report_path = default
304
        return self.report_path
305 306 307 308 309 310
        
    security.declarePublic('getZoom')
    def getZoom(self):
      try:
        current_zoom=self.params['zoom']
        if current_zoom != None:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
311
          return current_zoom 
312 313
        else:
          return 1  
Yoshinori Okuji's avatar
Yoshinori Okuji committed
314
      except KeyError:
315 316
        return 1
    
317 318 319 320 321 322
    security.declarePublic('getReportList')
    def getReportList(self):
        if self.report_list is None:
          self.report_list = (('portal_categories',),)
        return self.report_list

323 324 325 326 327 328
    security.declarePublic('isReportOpened')
    def isReportOpened(self):
        if self.report_opened is None:
          self.report_opened = 1
        return self.report_opened

329 330 331 332
    security.declarePublic('isInvertMode')
    def isInvertMode(self):
        return self.invert_mode
 
Jérome Perrin's avatar
Jérome Perrin committed
333 334 335 336 337
    security.declarePublic('getInvertModeUidList')
    def getInvertModeUidList(self):
        return self.uids
     
 
338
InitializeClass(Selection)
339 340
allow_class(Selection)

341 342 343
class DomainSelection(Acquisition.Implicit, Traversable, Persistent):
  """
    A class to store a selection of domains which defines a report
344 345 346
    section. There are different ways to use DomainSelection in 
    SQL methods. As a general principle, SQL methods are passed
    DomainSelection instances as a parameter.
347

348
    Example 1: (hand coded)
349

350 351 352 353 354
    The domain is accessed directly from the selection and a list of
    uids is gathered from the ZODB to feed the SQL request. This
    approach is only suitable for categories and relations. It is
    not suitable for predicates. Do not use it unless there is no other way.

355 356 357 358 359
    <dtml-if selection.domain.eip>
      <dtml-in "selection.domain.eip.getCategoryChildUidList()">uid = <dtml-sqlvar sequence-item type="int"></dtml-in>
    </dtml-if>

    Example 2: (auto generated)
360 361 362 363
    
    The domain object is in charge of generating automatically all
    SQL expressions to feed the SQL method (or the catalog). This
    is the recommended approach.
364

365 366
    <dtml-var "selection.domain.asSqlExpression(table_map=(('eip','movement'), ('group', 'catalog')))">
    <dtml-var "selection.domain.asSqlJoinExpression(table_map=(('eip','movement'), ('group', 'catalog')))">
367

368 369
    Example 3: (mixed)

370 371 372 373
    The category or predicate of the domain object is accessed. SQL
    code generation is invoked on it. This is better than the manual
    approach.

374
    <dtml-var "selection.domain.eip.asSqlExpresion(table="resource_category")">
375

376 377 378
    Current implementation is only suitable for categories.
    It needs to be extended to support also predicates. The right approach
    would be to turn any category into a predicate.
379 380
  """

381 382
  security = ClassSecurityInfo()
  security.declareObjectPublic()
383

384
  def __init__(self, domain_dict = None):
385
    #LOG('DomainSelection', 0, '__init__ is called with %r' % (domain_dict,))
386 387 388 389 390 391 392
    if domain_dict is not None:
      self.domain_dict = domain_dict
      for k,v in domain_dict.items():
        if k is not None:
          setattr(self, k, v)

  def __len__(self):
393 394
    return len(self.domain_dict)

395 396
  security.declarePublic('getCategoryList')
  def getCategoryList(self):
397
    return
398 399

  security.declarePublic('asSqlExpression')
400 401 402
  def asSqlExpression(self, table_map=None, domain_id=None, 
                      exclude_domain_id=None, strict_membership=0,
                      join_table="catalog", join_column="uid"):
403
    select_expression = []
404
    for k, d in self.domain_dict.items():
405 406
      if k == 'parent':
        # Special treatment for parent
407
        select_expression.append(d.getParentSqlExpression(table='catalog', 
408
                               strict_membership=strict_membership))
409 410 411 412 413 414 415 416 417
      elif k is not None:
        if getattr(aq_base(d), 'isPredicate', 0):
          select_expression.append(d.asSqlExpression(table='%s_category' % k,
                                                     strict_membership=strict_membership))
        else:
          # This is a category, we must join
          select_expression.append('%s.%s = %s_category.uid' % \
                                (join_table, join_column, k))
          select_expression.append(d.asSqlExpression(table='%s_category' % k, 
418
                                base_category=k,
419 420 421 422 423 424 425 426
                                strict_membership=strict_membership))
                                # XXX We should take into account k explicitely
                                # if we want to support category acquisition
    if select_expression:
      result = "( %s )" % ' AND '.join(select_expression)
    else:
      result = ''
    #LOG('DomainSelection', 0, 'asSqlExpression returns %r' % (result,))
427
    return result
428

429 430 431
  security.declarePublic('asSqlJoinExpression')
  def asSqlJoinExpression(self, domain_id=None, exclude_domain_id=None):
    join_expression = []
432
    #LOG('DomainSelection', 0, 'domain_id = %r, exclude_domain_id = %r, self.domain_dict = %r' % (domain_id, exclude_domain_id, self.domain_dict))
433
    for k, d in self.domain_dict.items():
434 435
      if k == 'parent':
        pass
436 437 438 439 440 441
      elif k is not None:
        if getattr(aq_base(d), 'isPredicate', 0):
          join_expression.append(d.asSqlJoinExpression(table='%s_category' % k))
        else:
          # This is a category, we must join
          join_expression.append('category AS %s_category' % k)
442
    result = "%s" % ' , '.join(join_expression)
443
    #LOG('DomainSelection', 0, 'asSqlJoinExpression returns %r' % (result,))
444 445 446 447
    return result

  security.declarePublic('asDomainDict')
  def asDomainDict(self, domain_id=None, exclude_domain_id=None):
448
    return self.domain_dict
449 450 451 452 453 454 455

  security.declarePublic('asDomainItemDict')
  def asDomainItemDict(self, domain_id=None, exclude_domain_id=None):
    pass

  security.declarePublic('updateDomain')
  def updateDomain(self, domain):
456 457
    pass

458
InitializeClass(DomainSelection)
459
allow_class(DomainSelection)