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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
29
from Globals import InitializeClass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30
from AccessControl import ClassSecurityInfo
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31 32
from Acquisition import aq_base, aq_inner

33 34
from Products.CMFCore.utils import getToolByName

Jean-Paul Smets's avatar
Jean-Paul Smets committed
35
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
36
from Products.ERP5Type.Core.Folder import Folder
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37
from Products.ERP5Type.Document import newTempBase
38
from Products.ERP5Type.XMLObject import XMLObject
Jean-Paul Smets's avatar
Jean-Paul Smets committed
39 40 41
from Products.ERP5Type.Utils import convertToUpperCase

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

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

48
    Predicates are defined by a combination of PropertySheet values
49 50 51
    (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
52
    script based test.
53 54

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

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
70 71
  # Declarative security
  security = ClassSecurityInfo()
72
  security.declareObjectProtected(Permissions.AccessContentsInformation)
73

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

  # Declarative interfaces
  __implements__ = ( Interface.Predicate )

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

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

    for c in membership_criterion_category_list:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
136 137
      bc = c.split('/', 1)[0]
      if (bc not in tested_base_category) and \
Romain Courteaud's avatar
Romain Courteaud committed
138
         (bc in multimembership_criterion_base_category_list):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
139
        tested_base_category[bc] = 1
Yoshinori Okuji's avatar
Yoshinori Okuji committed
140
      elif (bc not in tested_base_category) and \
Romain Courteaud's avatar
Romain Courteaud committed
141
           (bc in membership_criterion_base_category_list):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
142
        tested_base_category[bc] = 0
Romain Courteaud's avatar
Romain Courteaud committed
143 144 145
      if (bc in multimembership_criterion_base_category_list):
        tested_base_category[bc] = tested_base_category[bc] and \
                                   context.isMemberOf(c)
146
#        LOG('predicate test', 0,
147 148
#            '%s after multi membership to %s' % \
#            (tested_base_category[bc], c))
Romain Courteaud's avatar
Romain Courteaud committed
149 150 151
      elif (bc in membership_criterion_base_category_list):
        tested_base_category[bc] = tested_base_category[bc] or \
                                   context.isMemberOf(c)
152

153
#        LOG('predicate test', 0,
154 155
#            '%s after single membership to %s' % \
#            (tested_base_category[bc], c))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
156
    result = result and (0 not in tested_base_category.values())
157
#    LOG('predicate test', 0,
158
#        '%s after category %s ' % (result, tested_base_category.items()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
159
    # Test method calls
160 161 162 163 164
    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)
165 166 167 168 169
          try:
            result = result and method(self)
          except TypeError:
            # backward compatibilty with script that takes no argument
            result = result and method()
170
#        LOG('predicate test', 0,
171
#            '%s after method %s ' % (result, test_method_id))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
172 173
    return result

174
  security.declareProtected( Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
175 176
                             'buildSQLQuery' )
  def buildSQLQuery(self, strict_membership=0, table='category',
177 178
                          join_table='catalog', join_column='uid',
                          **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
179
    """
180 181 182
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
183

184
      XXX - This method is not implemented yet
Jean-Paul Smets's avatar
Jean-Paul Smets committed
185
    """
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    # 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')
206 207 208
    portal_categories = getToolByName(self, 'portal_categories')

    from_table_dict = {}
209

210 211 212 213 214 215 216 217 218 219 220 221
    # 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):
        category_value = portal_categories.resolveCategory(category)
        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
222
          membership_dict[base_category].append(category_value.asSQLExpression(
223 224 225 226 227 228
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
    membership_select_list = map(lambda l: '( %s )' % ' OR '.join(l),
                                                         membership_dict.values())

Jean-Paul Smets's avatar
Jean-Paul Smets committed
229
    # Then build SQL for multimembership_dict criteria
230 231 232 233 234 235 236 237 238 239 240 241
    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
242
          multimembership_dict[base_category].append(category_value.asSQLExpression(
243 244 245 246
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
    multimembership_select_list = map(lambda l: ' AND '.join(l), multimembership_dict.values())
247 248

    # Build the join where expression
249
    join_select_list = []
Yoshinori Okuji's avatar
Yoshinori Okuji committed
250
    for k in from_table_dict.iterkeys():
251
      join_select_list.append('%s.%s = %s.uid' % (join_table, join_column, k))
252 253

    sql_text = ' AND '.join(join_select_list + membership_select_list +
254 255
                            multimembership_select_list)

256 257 258 259 260 261 262 263 264
    # 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
265

266 267 268 269
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'buildSqlQuery' )
  buildSqlQuery = buildSQLQuery

Jérome Perrin's avatar
Jérome Perrin committed
270 271
  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLExpression' )
  def asSQLExpression(self, strict_membership=0, table='category'):
272
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273 274 275
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
276
    """
Jérome Perrin's avatar
Jérome Perrin committed
277
    return self.buildSQLQuery(strict_membership=strict_membership, table=table)['where_expression']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
278

279 280 281 282
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlExpression' )
  asSqlExpression = asSQLExpression

Jérome Perrin's avatar
Jérome Perrin committed
283 284
  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLJoinExpression' )
  def asSQLJoinExpression(self, strict_membership=0, table='category', join_table='catalog', join_column='uid'):
285 286
    """
    """
Jérome Perrin's avatar
Jérome Perrin committed
287
    table_list = self.buildSQLQuery(strict_membership=strict_membership, table=table)['from_table_list']
288
    sql_text_list = map(lambda (a,b): '%s AS %s' % (b,a), filter(lambda (a,b): a != join_table, table_list))
289
    return ' , '.join(sql_text_list)
290

291 292 293 294
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlJoinExpression' )
  asSqlJoinExpression = asSQLJoinExpression

295 296 297 298 299 300 301 302 303 304 305 306
  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
307 308
  security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' )
  def getCriterionList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
309
    """
310
      Returns the list of criteria which are defined by the Predicate.
311

312 313
      Each criterion is returned in a TempBase instance intended to be
      displayed in a ListBox.
314

315
      XXX - It would be better to return criteria in a Criterion class
316
            instance
Jean-Paul Smets's avatar
Jean-Paul Smets committed
317
    """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
318
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
      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):
335 336 337
    """
      This methods sets parameters of a criterion. There is at most one
      criterion per property. Defined parameters are
338

339 340
      identity -- if not None, allows for testing identity of the property
                  with the provided value
341

342 343
      min      -- if not None, allows for testing that the property
                  is greater than min
344

345 346
      max      -- if not None, allows for testing that the property
                  is greater than max
347

348
    """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
349
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
350 351
      self._identity_criterion = {}
      self._range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
352
    if identity is not None :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
353 354 355 356 357 358
      self._identity_criterion[property] = identity
    if min != '' or max != '' :
      self._range_criterion[property] = (min, max)
    self.reindexObject()

  security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
359 360 361 362 363 364
  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
365
    if getattr(aq_base(self), '_identity_criterion', None) is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
366 367
      self._identity_criterion = {}
      self._range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
368
    if 'criterion_property_list' in kwd:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
369 370 371
      criterion_property_list = kwd['criterion_property_list']
      identity_criterion = {}
      range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
372
      for criterion in self._identity_criterion.iterkeys() :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
373 374
        if criterion in criterion_property_list :
          identity_criterion[criterion] = self._identity_criterion[criterion]
Yoshinori Okuji's avatar
Yoshinori Okuji committed
375
      for criterion in self._range_criterion.iterkeys() :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376 377 378 379
        if criterion in criterion_property_list :
          range_criterion[criterion] = self._range_criterion[criterion]
      self._identity_criterion = identity_criterion
      self._range_criterion = range_criterion
380
    kwd['reindex_object'] = 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
381 382 383
    return self._edit(**kwd)

  # Predicate fusion method
Yoshinori Okuji's avatar
Yoshinori Okuji committed
384
  security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
385
  def setPredicateCategoryList(self, category_list):
386 387 388 389 390 391
    """
      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.
392 393

      WARNING: this method does not take into account scripts at
394 395
      this point.
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
396 397 398 399 400 401 402
    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 = []
403 404 405
    # reset criterions
    self._identity_criterion = {}
    self._range_criterion = {}
406

Jean-Paul Smets's avatar
Jean-Paul Smets committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    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)
430
    self._setTestMethodIdList(test_method_id_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
431 432
    self.reindexObject()

Yoshinori Okuji's avatar
Yoshinori Okuji committed
433
  security.declareProtected(Permissions.AccessContentsInformation, 'generatePredicate')
434
  def generatePredicate(self, multimembership_criterion_base_category_list=(),
435 436
                        membership_criterion_base_category_list=(),
                        criterion_property_list=()):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
437
    """
438
    This method generates a new temporary predicate based on an ad-hoc
439
    interpretation of local properties of an object. For example,
440
    a start_range_min property will be interpreted as a way to define
441
    a min criterion on start_date.
442

443
    The purpose of this method is to be called from
444 445 446
    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
447
    """
448
    new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList())
449
    new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList())
450
    new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList())
451

452
    for base_category in multimembership_criterion_base_category_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
453 454 455
      category_list = self.getProperty(base_category + '_list')
      if category_list is not None and len(category_list)>0:
        for category in category_list:
456
          new_membership_criterion_category_list.append(base_category + '/' + category)
457
        if base_category not in new_multimembership_criterion_base_category_list:
458
          new_multimembership_criterion_base_category_list.append(base_category)
459

460 461 462 463 464
    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)
465
        if base_category not in new_membership_criterion_base_category_list:
466
          new_membership_criterion_base_category_list.append(base_category)
467

468
    new_criterion_property_list =  list(self.getCriterionPropertyList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
469 470 471
    identity_criterion = getattr(self,'_identity_criterion',{})
    range_criterion = getattr(self,'_range_criterion',{})
    # Look at local properties and make it criterion properties
472
    for property in criterion_property_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
473 474
      if property not in self.getCriterionPropertyList() \
        and property in self.propertyIds():
475
          new_criterion_property_list.append(property)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
476 477
          property_min = property + '_range_min'
          property_max = property + '_range_max'
478
          if getattr(self, 'get%s' % convertToUpperCase(property), None) is not None\
Jean-Paul Smets's avatar
Jean-Paul Smets committed
479 480
            and self.getProperty(property) is not None:
            identity_criterion[property] = self.getProperty(property)
481
          elif getattr(self, 'get%s' % convertToUpperCase(property_min), None) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
482 483 484 485 486 487
            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(
488
        membership_criterion_category=new_membership_criterion_category_list,
489
        membership_criterion_base_category=new_membership_criterion_base_category_list,
490 491
        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
492 493 494
        _identity_criterion=identity_criterion,
        _range_criterion=range_criterion)

495 496 497
    return new_self

  # Predicate handling
498 499 500
  security.declareProtected(Permissions.AccessContentsInformation,
                            'asPredicate')
  def asPredicate(self, script_id=None):
501
    """
502
      This method tries to convert the current Document into a predicate
503 504
      looking up methods named ${PortalType}_asPredicate,
      ${MetaType}_asPredicate, ${Class}_asPredicate     
505
    """
506 507 508 509
    if script_id is not None:
      script = getattr(self, script_id, None)
    else:
      script = self._getTypeBasedMethod('asPredicate')
510
    if script is not None:
511 512
      return script()
    return self
513 514 515 516

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

      TO BE IMPLEMENTED using portal_catalog(**kw)
519
    """
520
    pass