Selection.py 13.2 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 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
    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')
    def getDomainPath(self):
        if self.domain_path is None:
          self.domain_path = self.getDomainList()[0]
        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')
    def getReportPath(self):
        if self.report_path is None:
          self.report_path = ('portal_categories')
        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

299 300 301 302 303 304
    security.declarePublic('isReportOpened')
    def isReportOpened(self):
        if self.report_opened is None:
          self.report_opened = 1
        return self.report_opened

305
InitializeClass(Selection)
306 307
allow_class(Selection)

308 309 310 311
class DomainSelection(Acquisition.Implicit, Traversable, Persistent):
  """
    A class to store a selection of domains which defines a report
    section.
312

313
    Example 1: (hand coded)
314

315 316 317 318 319
    <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)
320

321 322
    <dtml-var "selection.domain.asSqlExpression(table_map=(('eip','movement'), ('group', 'catalog')))">
    <dtml-var "selection.domain.asSqlJoinExpression(table_map=(('eip','movement'), ('group', 'catalog')))">
323

324 325 326
    Example 3: (mixed)

    <dtml-var "selection.domain.eip.asSqlExpresion(table="resource_category")">
327 328 329

  """

330 331
  security = ClassSecurityInfo()
  security.declareObjectPublic()
332

333 334 335 336 337 338 339 340
  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):
341 342
    return len(self.domain_dict)

343 344
  security.declarePublic('getCategoryList')
  def getCategoryList(self):
345
    return
346 347 348 349 350 351 352

  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():
      if k is not None and getattr(aq_base(d), 'isCategory', 0):
        # This is a category, we must join
353 354
        join_expression.append('catalog.uid = %s_category.uid' % k)
        join_expression.append(d.asSqlExpression(table = '%s_category' % k, strict_membership=strict_membership))
355
    result = "( %s )" % ' AND '.join(join_expression)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
356
    #LOG('asSqlExpression', 0, str(result))
357
    return result
358

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

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

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

  security.declarePublic('updateDomain')
  def updateDomain(self, domain):
380 381
    pass

382
InitializeClass(DomainSelection)
383
allow_class(DomainSelection)