Selection.py 13.7 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
    def __init__(self, method_path=None, params=None, sort_on=None, default_sort_on=None,
123
                 uids=None, invert_mode=0, list_url='', domain=None, report=None,
124
                 columns=None, checked_uids=None, name=None, index=None):
125 126 127 128 129
        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 = []
130 131 132 133
        # 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')
134 135 136 137 138 139 140 141 142 143 144 145
        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',)
146
        self.domain_list = ()
147
        self.report_path = ('portal_categories',)
148
        self.report_list = ()
149 150
        self.domain = None
        self.report = None
151
        self.report_opened = None
152 153

    security.declarePrivate('edit')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
154 155
    def edit(self, params=None, **kw):
        if params is not None:
156
          self.params = {}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
157 158 159 160 161
          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_':
162
              self.params[key] = params[key]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
163 164
        if kw is not None:
          for k,v in kw.items():
165
            if k in ('domain', 'report') or v is not None:
166 167 168 169
              # 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')
170
              setattr(self, k, v)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
171

172
    def __call__(self, method = None, context=None, REQUEST=None):
173
        #LOG("Selection", 0, str(self.__dict__))
174 175 176 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:
          if method is None:
            method = context.unrestrictedTraverse(self.method_path)
          sort_on = getattr(self, 'sort_on', [])
180
          if len(sort_on) == 0:
181
            sort_on = getattr(self, 'default_sort_on', [])
182 183 184 185 186 187 188 189
          if len(sort_on) > 0:
            new_sort_index = []
            for (k , v) in sort_on:
              if v == 'descending' or v == 'reverse':
                new_sort_index += ['%s DESC' % k]
              else:
                new_sort_index += ['%s' % k]
            sort_order_string = string.join(new_sort_index,',')
190 191 192 193 194 195 196
            self.params['sort_on'] = sort_order_string
          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:
197
                result = method(selection_domain = self.domain,
198 199 200 201 202 203 204 205
                                selection_report = self.report, selection=self, **self.params)
              elif self.domain is not None:
                result = method(selection_domain = self.domain, selection=self, **self.params)
              elif self.report is not None:
                result = method(selection_report = self.report, selection=self, **self.params)
              else:
                result = method(selection=self, **self.params)
              return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
206 207 208 209 210
            else:
              return []
          else:
            return []
        else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
211
          # We sould try to allow more filtering
212
          return context.portal_catalog(uid = self.uids)
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 257 258
        if self.params is None:
          self.params = {}
        if type(self.params) != type({}):
          self.params = {}
        return self.params

    security.declarePublic('getListUrl')
    def getListUrl(self):
Sebastien Robin's avatar
Sebastien Robin committed
259
        result = ''
Yoshinori Okuji's avatar
Yoshinori Okuji committed
260
        #LOG('getListUrl', 0, 'list_url = %s' % str(self.list_url))
261 262
        if self.list_url is None:
          self.list_url = ''
Sebastien Robin's avatar
Sebastien Robin committed
263
        else:
264
          result = self.list_url
Sebastien Robin's avatar
Sebastien Robin committed
265
        return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
266

267 268 269 270 271 272 273 274 275
    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')
276
    def getDomainPath(self, default=None):
277
        if self.domain_path is None:
278 279 280 281
          if default is None:
            self.domain_path = self.getDomainList()[0]
          else:            
            self.domain_path = default
282 283 284 285 286 287 288 289 290
        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')
291
    def getReportPath(self, default=None):
292
        if self.report_path is None:
293 294 295 296
          if default is None:
            self.report_path = self.getReportList()[0]
          else:            
            self.report_path = default
297 298 299 300 301 302 303 304
        return self.report_path

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

305 306 307 308 309 310
    security.declarePublic('isReportOpened')
    def isReportOpened(self):
        if self.report_opened is None:
          self.report_opened = 1
        return self.report_opened

311
InitializeClass(Selection)
312 313
allow_class(Selection)

314 315 316 317
class DomainSelection(Acquisition.Implicit, Traversable, Persistent):
  """
    A class to store a selection of domains which defines a report
    section.
318

319
    Example 1: (hand coded)
320

321 322 323 324 325
    <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)
326

327 328
    <dtml-var "selection.domain.asSqlExpression(table_map=(('eip','movement'), ('group', 'catalog')))">
    <dtml-var "selection.domain.asSqlJoinExpression(table_map=(('eip','movement'), ('group', 'catalog')))">
329

330 331 332
    Example 3: (mixed)

    <dtml-var "selection.domain.eip.asSqlExpresion(table="resource_category")">
333 334 335

  """

336 337
  security = ClassSecurityInfo()
  security.declareObjectPublic()
338

339 340 341 342 343 344 345 346
  def __init__(self, domain_dict = None):
    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):
347 348
    return len(self.domain_dict)

349 350
  security.declarePublic('getCategoryList')
  def getCategoryList(self):
351
    return
352 353 354 355 356

  security.declarePublic('asSqlExpression')
  def asSqlExpression(self, table_map=None, domain_id=None, exclude_domain_id=None, strict_membership=0):
    join_expression = []
    for k, d in self.domain_dict.items():
357 358 359 360
      if k == 'parent':
        # Special treatment for parent
        join_expression.append(d.getParentSqlExpression(table = 'catalog', strict_membership=strict_membership))
      elif k is not None and getattr(aq_base(d), 'isCategory', 0):
361
        # This is a category, we must join
362 363
        join_expression.append('catalog.uid = %s_category.uid' % k)
        join_expression.append(d.asSqlExpression(table = '%s_category' % k, strict_membership=strict_membership))
364
    result = "( %s )" % ' AND '.join(join_expression)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
365
    #LOG('asSqlExpression', 0, str(result))
366
    return result
367

368 369 370 371
  security.declarePublic('asSqlJoinExpression')
  def asSqlJoinExpression(self, domain_id=None, exclude_domain_id=None):
    join_expression = []
    for k, d in self.domain_dict.items():
372 373 374
      if k == 'parent':
        pass
      elif k is not None and getattr(aq_base(d), 'isCategory', 0):
375
        # This is a category, we must join
376
        join_expression.append('category AS %s_category' % k)
377
    result = "%s" % ' , '.join(join_expression)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
378
    #LOG('asSqlJoinExpression', 0, str(result))
379 380 381 382
    return result

  security.declarePublic('asDomainDict')
  def asDomainDict(self, domain_id=None, exclude_domain_id=None):
383
    return self.domain_dict
384 385 386 387 388 389 390

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

  security.declarePublic('updateDomain')
  def updateDomain(self, domain):
391 392
    pass

393
InitializeClass(DomainSelection)
394
allow_class(DomainSelection)