Predicate.py 26.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.
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
#
# 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.
#
##############################################################################

29
import zope.interface
30
from warnings import warn
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31
from Globals import InitializeClass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from AccessControl import ClassSecurityInfo
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33 34
from Acquisition import aq_base, aq_inner

35 36
from Products.CMFCore.utils import getToolByName

37
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
38
from Products.ERP5Type.Core.Folder import Folder
Jean-Paul Smets's avatar
Jean-Paul Smets committed
39
from Products.ERP5Type.Document import newTempBase
40
from Products.ERP5Type.XMLObject import XMLObject
Jean-Paul Smets's avatar
Jean-Paul Smets committed
41
from Products.ERP5Type.Utils import convertToUpperCase
42
from Products.ERP5Type.Cache import getReadOnlyTransactionCache, enableReadOnlyTransactionCache, disableReadOnlyTransactionCache
Jean-Paul Smets's avatar
Jean-Paul Smets committed
43 44

from zLOG import LOG
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45

46
class Predicate(XMLObject, Folder):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
47
  """
48 49
    A Predicate object defines a list of criterions
    which can be applied to test a document or to search for documents.
50

51
    Predicates are defined by a combination of PropertySheet values
52 53 54
    (ex. membership_criterion_list) and criterion list (ex. quantity
    is between 0 and 10). An additional script can be associated to
    extend the standard Predicate semantic with any additional
55
    script based test.
56 57

    The idea between Predicate in ERP5 is to have a simple
58
    way of defining simple predicates which can be later
59
    searched through a simplistic rule based engine and which can
60
    still provide complete expressivity through additional scripting.
61

62
    The approach is intended to provide the expressivity of a rule
63
    based system without the burden of building a fully expressive
64
    rule engine.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
65
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
66 67 68 69 70 71
  meta_type = 'ERP5 Predicate'
  portal_type = 'Predicate'
  add_permission = Permissions.AddPortalContent
  isPortalContent = 1
  isRADContent = 1
  isPredicate = 1
72

Jean-Paul Smets's avatar
Jean-Paul Smets committed
73 74
  # Declarative security
  security = ClassSecurityInfo()
75
  security.declareObjectProtected(Permissions.AccessContentsInformation)
76

Jean-Paul Smets's avatar
Jean-Paul Smets committed
77 78 79
  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.Predicate
80
                    , PropertySheet.CategoryCore
Jean-Paul Smets's avatar
Jean-Paul Smets committed
81 82 83 84
                    , PropertySheet.SortIndex
                    )

  # Declarative interfaces
85
  zope.interface.implements( interfaces.IPredicate, )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
86

Yoshinori Okuji's avatar
Yoshinori Okuji committed
87
  security.declareProtected( Permissions.AccessContentsInformation, 'test' )
88 89
  def test(self, context, tested_base_category_list=None, 
           strict_membership=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
90
    """
91 92
      A Predicate can be tested on a given context.
      Parameters can passed in order to ignore some conditions.
93 94

      - tested_base_category_list:  this is the list of category that we do
95
        want to test. For example, we might want to test only the
96
        destination or the source of a predicate.
97 98
      - if strict_membership is specified, we should make sure that we
        are strictly a member of tested categories
Jean-Paul Smets's avatar
Jean-Paul Smets committed
99 100 101
    """
    self = self.asPredicate()
    result = 1
102
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
103 104
      self._identity_criterion = {}
      self._range_criterion = {}
105
#    LOG('PREDICATE TEST', 0,
106 107
#        'testing %s on context of %s' % \
#        (self.getRelativeUrl(), context.getRelativeUrl()))
108
    for property, value in self._identity_criterion.iteritems():
Jean-Paul Smets's avatar
Jean-Paul Smets committed
109
      result = result and (context.getProperty(property) == value)
110
#      LOG('predicate test', 0,
111 112
#          '%s after prop %s : %s == %s' % \
#          (result, property, context.getProperty(property), value))
113
    for property, (min, max) in self._range_criterion.iteritems():
Jean-Paul Smets's avatar
Jean-Paul Smets committed
114 115 116
      value = context.getProperty(property)
      if min is not None:
        result = result and (value >= min)
117
#        LOG('predicate test', 0,
118 119
#            '%s after prop %s : %s >= %s' % \
#            (result, property, value, min))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
120 121
      if max is not None:
        result = result and (value < max)
122
#        LOG('predicate test', 0,
123 124
#            '%s after prop %s : %s < %s' % \
#            (result, property, value, max))
Romain Courteaud's avatar
Romain Courteaud committed
125 126 127 128
    multimembership_criterion_base_category_list = \
        self.getMultimembershipCriterionBaseCategoryList()
    membership_criterion_base_category_list = \
        self.getMembershipCriterionBaseCategoryList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
129
    tested_base_category = {}
130
#    LOG('predicate test', 0,
131
#        'categories will be tested in multi %s single %s as %s' % \
132 133
#        (multimembership_criterion_base_category_list,
#        membership_criterion_base_category_list,
134
#        self.getMembershipCriterionCategoryList()))
135 136 137 138
    membership_criterion_category_list = \
                            self.getMembershipCriterionCategoryList()
    if tested_base_category_list is not None:
      membership_criterion_category_list = [x for x in \
Yoshinori Okuji's avatar
Yoshinori Okuji committed
139
          membership_criterion_category_list if x.split('/', 1)[0] in \
140 141
          tested_base_category_list]

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    # Test category memberships. Enable the read-only transaction cache
    # temporarily, if not enabled, because this part is strictly read-only,
    # and context.isMemberOf is very expensive, when the category list has
    # many items.
    enabled = (getReadOnlyTransactionCache(self) is not None)
    try:
      if not enabled:
        enableReadOnlyTransactionCache(self)
      for c in membership_criterion_category_list:
        bc = c.split('/', 1)[0]
        if (bc not in tested_base_category) and \
           (bc in multimembership_criterion_base_category_list):
          tested_base_category[bc] = 1
        elif (bc not in tested_base_category) and \
             (bc in membership_criterion_base_category_list):
          tested_base_category[bc] = 0
        if (bc in multimembership_criterion_base_category_list):
          tested_base_category[bc] = tested_base_category[bc] and \
160 161
                                     context.isMemberOf(c, 
                                         strict_membership=strict_membership)
162
#        LOG('predicate test', 0,
163 164
#            '%s after multi membership to %s' % \
#            (tested_base_category[bc], c))
165 166
        elif (bc in membership_criterion_base_category_list):
          tested_base_category[bc] = tested_base_category[bc] or \
167 168
                                     context.isMemberOf(c,
                                         strict_membership=strict_membership)
169 170 171
    finally:
      if not enabled:
        disableReadOnlyTransactionCache(self)
172

173
#        LOG('predicate test', 0,
174 175
#            '%s after single membership to %s' % \
#            (tested_base_category[bc], c))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
176
    result = result and (0 not in tested_base_category.values())
177
#    LOG('predicate test', 0,
178
#        '%s after category %s ' % (result, tested_base_category.items()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
179
    # Test method calls
180 181 182 183 184
    test_method_id_list = self.getTestMethodIdList()
    if test_method_id_list is not None :
      for test_method_id in test_method_id_list :
        if (test_method_id is not None) and result:
          method = getattr(context,test_method_id)
185 186 187
          try:
            result = result and method(self)
          except TypeError:
188 189
            if method.func_code.co_argcount != 0:
              raise
190
            # backward compatibilty with script that takes no argument
191 192 193
            warn('Predicate %s uses an old-style method (%s) that does not'
                 ' take the predicate as argument' % (
               self.getRelativeUrl(), method.__name__), DeprecationWarning)
194
            result = result and method()
195
#        LOG('predicate test', 0,
196
#            '%s after method %s ' % (result, test_method_id))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
197 198
    return result

199
  security.declareProtected( Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
200 201
                             'buildSQLQuery' )
  def buildSQLQuery(self, strict_membership=0, table='category',
202 203
                          join_table='catalog', join_column='uid',
                          **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
204
    """
205 206 207
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
208

209
      XXX - This method is not implemented yet
Jean-Paul Smets's avatar
Jean-Paul Smets committed
210
    """
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    # Build the identity criterion
    catalog_kw = {}
    catalog_kw.update(kw) # query_table, REQUEST, ignore_empty_string, **kw
    for criterion in self.getCriterionList():
      if criterion.min and criterion.max:
        catalog_kw[criterion.property] = { 'query' : (criterion.min, criterion.max),
                                           'range' : 'minmax'
                                         }
      elif criterion.min:
        catalog_kw[criterion.property] = { 'query' : criterion.min,
                                           'range' : 'min'
                                         }
      elif criterion.max:
        catalog_kw[criterion.property] = { 'query' : criterion.max,
                                           'range' : 'max'
                                         }
      else:
        catalog_kw[criterion.property] = criterion.identity

    portal_catalog = getToolByName(self, 'portal_catalog')
231 232 233
    portal_categories = getToolByName(self, 'portal_categories')

    from_table_dict = {}
234

235 236 237 238 239 240 241 242
    # First build SQL for membership criteria
    # It would be much nicer if all this was handled by the catalog in a central place
    membership_dict = {}
    for base_category in self.getMembershipCriterionBaseCategoryList():
      membership_dict[base_category] = [] # Init dict with valid base categories
    for category in self.getMembershipCriterionCategoryList():
      base_category = category.split('/')[0] # Retrieve base category
      if membership_dict.has_key(base_category):
243
        category_value = portal_categories.resolveCategory(category, None)
244 245 246
        if category_value is not None:
          table_alias = "single_%s_%s" % (table, base_category)
          from_table_dict[table_alias] = 'category'
Jérome Perrin's avatar
Jérome Perrin committed
247
          membership_dict[base_category].append(category_value.asSQLExpression(
248 249 250
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
251 252 253 254 255
    membership_select_list = []
    for expression_list in membership_dict.values():
      or_expression = ' OR '.join(expression_list)
      if or_expression:
        membership_select_list.append('( %s )' % or_expression)
256

Jean-Paul Smets's avatar
Jean-Paul Smets committed
257
    # Then build SQL for multimembership_dict criteria
258 259 260 261 262 263 264 265 266 267 268 269
    multimembership_dict = {}
    for base_category in self.getMultimembershipCriterionBaseCategoryList():
      multimembership_dict[base_category] = [] # Init dict with valid base categories
    join_count = 0
    for category in self.getMembershipCriterionCategoryList():
      base_category = category.split('/')[0] # Retrieve base category
      if multimembership_dict.has_key(base_category):
        category_value = portal_categories.resolveCategory(category)
        if category_value is not None:
          join_count += 1
          table_alias = "multi_%s_%s" % (table, join_count)
          from_table_dict[table_alias] = 'category'
Jérome Perrin's avatar
Jérome Perrin committed
270
          multimembership_dict[base_category].append(category_value.asSQLExpression(
271 272 273
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
274 275 276 277 278
    multimembership_select_list = []
    for expression_list in multimembership_dict.values():
      and_expression = ' AND '.join(expression_list)
      if and_expression:
        multimembership_select_list.append(and_expression)
279 280

    # Build the join where expression
281
    join_select_list = []
Yoshinori Okuji's avatar
Yoshinori Okuji committed
282
    for k in from_table_dict.iterkeys():
283
      join_select_list.append('%s.%s = %s.uid' % (join_table, join_column, k))
284 285

    sql_text = ' AND '.join(join_select_list + membership_select_list +
286 287
                            multimembership_select_list)

288 289 290 291 292 293 294 295 296
    # Now merge identity and membership criteria
    catalog_kw['where_expression'] = sql_text
    sql_query = portal_catalog.buildSQLQuery(**catalog_kw)
    for alias, table in sql_query['from_table_list']:
      if from_table_dict.has_key(alias):
        raise KeyError, "The same table is used twice for an identity criterion and for a membership criterion"
      from_table_dict[alias] = table
    sql_query['from_table_list'] = from_table_dict.items()
    return sql_query
297

298 299 300 301
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'buildSqlQuery' )
  buildSqlQuery = buildSQLQuery

Jérome Perrin's avatar
Jérome Perrin committed
302 303
  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLExpression' )
  def asSQLExpression(self, strict_membership=0, table='category'):
304
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
305 306 307
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
308
    """
Jérome Perrin's avatar
Jérome Perrin committed
309
    return self.buildSQLQuery(strict_membership=strict_membership, table=table)['where_expression']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
310

311 312 313 314
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlExpression' )
  asSqlExpression = asSQLExpression

Jérome Perrin's avatar
Jérome Perrin committed
315 316
  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLJoinExpression' )
  def asSQLJoinExpression(self, strict_membership=0, table='category', join_table='catalog', join_column='uid'):
317 318
    """
    """
Jérome Perrin's avatar
Jérome Perrin committed
319
    table_list = self.buildSQLQuery(strict_membership=strict_membership, table=table)['from_table_list']
320
    sql_text_list = map(lambda (a,b): '%s AS %s' % (b,a), filter(lambda (a,b): a != join_table, table_list))
321
    return ' , '.join(sql_text_list)
322

323 324 325 326
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlJoinExpression' )
  asSqlJoinExpression = asSQLJoinExpression

327 328 329 330 331 332 333 334 335 336 337 338
  def searchResults(self, **kw):
    """
    """
    portal_catalog = getToolByName(self, 'portal_catalog')
    return portal_catalog.searchResults(build_sql_query_method=self.buildSQLQuery,**kw)

  def countResults(self, REQUEST=None, used=None, **kw):
    """
    """
    portal_catalog = getToolByName(self, 'portal_catalog')
    return portal_catalog.countResults(build_sql_query_method=self.buildSQLQuery,**kw)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
339 340
  security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' )
  def getCriterionList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
341
    """
342
      Returns the list of criteria which are defined by the Predicate.
343

344 345
      Each criterion is returned in a TempBase instance intended to be
      displayed in a ListBox.
346

347
      XXX - It would be better to return criteria in a Criterion class
348
            instance
Jean-Paul Smets's avatar
Jean-Paul Smets committed
349
    """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
350
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
      self._identity_criterion = {}
      self._range_criterion = {}
    criterion_dict = {}
    for p in self.getCriterionPropertyList():
      criterion_dict[p] = newTempBase(self, 'new_%s' % p)
      criterion_dict[p].identity = self._identity_criterion.get(p, None)
      criterion_dict[p].uid = 'new_%s' % p
      criterion_dict[p].property = p
      criterion_dict[p].min = self._range_criterion.get(p, (None, None))[0]
      criterion_dict[p].max = self._range_criterion.get(p, (None, None))[1]
    criterion_list = criterion_dict.values()
    criterion_list.sort()
    return criterion_list

  security.declareProtected( Permissions.ModifyPortalContent, 'setCriterion' )
  def setCriterion(self, property, identity=None, min=None, max=None, **kw):
367 368 369
    """
      This methods sets parameters of a criterion. There is at most one
      criterion per property. Defined parameters are
370

371 372
      identity -- if not None, allows for testing identity of the property
                  with the provided value
373

374 375
      min      -- if not None, allows for testing that the property
                  is greater than min
376

377 378
      max      -- if not None, allows for testing that the property
                  is greater than max
379

380
    """
381
    # XXX 'min' and 'max' are built-in functions.
Yoshinori Okuji's avatar
Yoshinori Okuji committed
382
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
383 384
      self._identity_criterion = {}
      self._range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
385
    if identity is not None :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
386
      self._identity_criterion[property] = identity
387 388 389 390 391 392 393 394 395 396
    if min == '':
      min = None
    if max == '':
      max = None
    if min is None and max is None:
      try:
        del self._range_criterion[property]
      except KeyError:
        pass
    else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
397 398 399 400
      self._range_criterion[property] = (min, max)
    self.reindexObject()

  security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
401 402 403 404 405 406
  def edit(self, **kwd):
    """
      The edit method is overriden so that any time a
      criterion_property_list property is defined, a list of criteria
      is created to match the provided criterion_property_list.
    """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
407
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
408 409
      self._identity_criterion = {}
      self._range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
410
    if 'criterion_property_list' in kwd:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
411 412 413
      criterion_property_list = kwd['criterion_property_list']
      identity_criterion = {}
      range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
414
      for criterion in self._identity_criterion.iterkeys() :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
415 416
        if criterion in criterion_property_list :
          identity_criterion[criterion] = self._identity_criterion[criterion]
Yoshinori Okuji's avatar
Yoshinori Okuji committed
417
      for criterion in self._range_criterion.iterkeys() :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
418 419 420 421
        if criterion in criterion_property_list :
          range_criterion[criterion] = self._range_criterion[criterion]
      self._identity_criterion = identity_criterion
      self._range_criterion = range_criterion
422
    kwd['reindex_object'] = 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
423 424 425
    return self._edit(**kwd)

  # Predicate fusion method
Yoshinori Okuji's avatar
Yoshinori Okuji committed
426
  security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
427
  def setPredicateCategoryList(self, category_list):
428 429 430 431 432 433
    """
      This method updates a Predicate by implementing an
      AND operation on all predicates (or categories)
      provided in category_list. Categories behave as a
      special kind of predicate which only acts on category
      membership.
434 435

      WARNING: this method does not take into account scripts at
436 437
      this point.
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
438 439 440 441 442 443 444
    category_tool = aq_inner(self.portal_categories)
    base_category_id_list = category_tool.objectIds()
    membership_criterion_category_list = []
    membership_criterion_base_category_list = []
    multimembership_criterion_base_category_list = []
    test_method_id_list = []
    criterion_property_list = []
445 446 447
    # reset criterions
    self._identity_criterion = {}
    self._range_criterion = {}
448

Jean-Paul Smets's avatar
Jean-Paul Smets committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    for c in category_list:
      bc = c.split('/')[0]
      if bc in base_category_id_list:
        # This is a category
        membership_criterion_category_list.append(c)
        membership_criterion_base_category_list.append(bc)
      else:
        predicate_value = category_tool.resolveCategory(c)
        if predicate_value is not None:
          criterion_property_list.extend(predicate_value.getCriterionPropertyList())
          membership_criterion_category_list.extend(
                      predicate_value.getMembershipCriterionCategoryList())
          membership_criterion_base_category_list.extend(
                      predicate_value.getMembershipCriterionBaseCategoryList())
          multimembership_criterion_base_category_list.extend(
                      predicate_value.getMultimembershipCriterionBaseCategoryList())
          test_method_id_list += list(predicate_value.getTestMethodIdList() or [])
          for p in predicate_value.getCriterionList():
            self.setCriterion(p.property, identity=p.identity, min=p.min, max=p.max)
    self.setCriterionPropertyList(criterion_property_list)
    self._setMembershipCriterionCategoryList(membership_criterion_category_list)
    self._setMembershipCriterionBaseCategoryList(membership_criterion_base_category_list)
    self._setMultimembershipCriterionBaseCategoryList(multimembership_criterion_base_category_list)
472
    self._setTestMethodIdList(test_method_id_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
473 474
    self.reindexObject()

Yoshinori Okuji's avatar
Yoshinori Okuji committed
475
  security.declareProtected(Permissions.AccessContentsInformation, 'generatePredicate')
476
  def generatePredicate(self, multimembership_criterion_base_category_list=(),
477 478
                        membership_criterion_base_category_list=(),
                        criterion_property_list=()):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
479
    """
480
    This method generates a new temporary predicate based on an ad-hoc
481
    interpretation of local properties of an object. For example,
482
    a start_range_min property will be interpreted as a way to define
483
    a min criterion on start_date.
484

485
    The purpose of this method is to be called from
486 487 488
    a script called PortalType_asPredicate to ease the generation of
    Predicates based on range properties. It should be considered mostly
    as a trick to simplify the development of Predicates and forms.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
489
    """
490
    new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList())
491
    new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList())
492
    new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList())
493

494
    for base_category in multimembership_criterion_base_category_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
495 496 497
      category_list = self.getProperty(base_category + '_list')
      if category_list is not None and len(category_list)>0:
        for category in category_list:
498
          new_membership_criterion_category_list.append(base_category + '/' + category)
499
        if base_category not in new_multimembership_criterion_base_category_list:
500
          new_multimembership_criterion_base_category_list.append(base_category)
501

502 503 504 505 506
    for base_category in membership_criterion_base_category_list:
      category_list = self.getProperty(base_category + '_list')
      if category_list is not None and len(category_list)>0:
        for category in category_list:
          new_membership_criterion_category_list.append(base_category + '/' + category)
507
        if base_category not in new_membership_criterion_base_category_list:
508
          new_membership_criterion_base_category_list.append(base_category)
509

510
    new_criterion_property_list =  list(self.getCriterionPropertyList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
511 512 513
    identity_criterion = getattr(self,'_identity_criterion',{})
    range_criterion = getattr(self,'_range_criterion',{})
    # Look at local properties and make it criterion properties
514
    for property in criterion_property_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
515 516
      if property not in self.getCriterionPropertyList() \
        and property in self.propertyIds():
517
          new_criterion_property_list.append(property)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
518 519
          property_min = property + '_range_min'
          property_max = property + '_range_max'
520
          if getattr(self, 'get%s' % convertToUpperCase(property), None) is not None\
Jean-Paul Smets's avatar
Jean-Paul Smets committed
521 522
            and self.getProperty(property) is not None:
            identity_criterion[property] = self.getProperty(property)
523
          elif getattr(self, 'get%s' % convertToUpperCase(property_min), None) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
524 525 526 527 528 529
            min = self.getProperty(property_min)
            max = self.getProperty(property_max)
            range_criterion[property] = (min,max)
    # Return a new context with new properties, like if
    # we have a predicate with local properties
    new_self = self.asContext(
530
        membership_criterion_category=new_membership_criterion_category_list,
531
        membership_criterion_base_category=new_membership_criterion_base_category_list,
532 533
        multimembership_criterion_base_category=new_multimembership_criterion_base_category_list,
        criterion_property_list=new_criterion_property_list,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
534 535 536
        _identity_criterion=identity_criterion,
        _range_criterion=range_criterion)

537 538 539
    return new_self

  # Predicate handling
540 541 542
  security.declareProtected(Permissions.AccessContentsInformation,
                            'asPredicate')
  def asPredicate(self, script_id=None):
543
    """
544
      This method tries to convert the current Document into a predicate
545 546
      looking up methods named ${PortalType}_asPredicate,
      ${MetaType}_asPredicate, ${Class}_asPredicate     
547
    """
548 549 550 551
    if script_id is not None:
      script = getattr(self, script_id, None)
    else:
      script = self._getTypeBasedMethod('asPredicate')
552
    if script is not None:
553 554
      return script()
    return self
555 556 557 558

  def searchPredicate(self, **kw):
    """
      Returns a list of documents matching the predicate
Jean-Paul Smets's avatar
Jean-Paul Smets committed
559 560

      TO BE IMPLEMENTED using portal_catalog(**kw)
561
    """
562
    pass
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getMembershipCriterionCategoryList')
  def getMembershipCriterionCategoryList(self, filter=None, **kw):
    """
    If filter is specified, return category only or document only
    in membership_criterion_category values.
    """
    all_list = self._baseGetMembershipCriterionCategoryList()
    if filter in ('category', 'document'):
      portal_categories = self.getPortalObject().portal_categories
      result_dict = {'category':[], 'document':[]}
      for x in all_list:
        try:
          if portal_categories.restrictedTraverse(x).getPortalType() == \
             'Category':
            result_dict['category'].append(x)
          else:
            result_dict['document'].append(x)
        except KeyError:
          result_dict['document'].append(x)
      return result_dict[filter]
    else:
      return all_list

  security.declareProtected(Permissions.ModifyPortalContent,
                            'setMembershipCriterionDocumentList' )
  def setMembershipCriterionDocumentList(self, document_list):
    """
    Appends to membership_criterion_category values.
    """
    return self.setMembershipCriterionCategoryList(
      (self.getMembershipCriterionCategoryList() + document_list))