Predicate.py 26.9 KB
Newer Older
1
# -*- coding: utf-8 -*-
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2 3 4
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
5
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
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.
#
##############################################################################

30
import zope.interface
31
from warnings import warn
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, interfaces
38
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
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
43
from Products.ZSQLCatalog.SQLCatalog import SQLQuery
44
from Products.ERP5Type.Globals import PersistentMapping
45
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46

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

52
    Predicates are defined by a combination of PropertySheet values
53 54 55
    (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
56
    script based test.
57 58

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

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

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

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

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

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

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

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    # 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 \
162 163
                                     context.isMemberOf(c, 
                                         strict_membership=strict_membership)
164
#        LOG('predicate test', 0,
165 166
#            '%s after multi membership to %s' % \
#            (tested_base_category[bc], c))
167 168
        elif (bc in membership_criterion_base_category_list):
          tested_base_category[bc] = tested_base_category[bc] or \
169 170
                                     context.isMemberOf(c,
                                         strict_membership=strict_membership)
171 172 173
    finally:
      if not enabled:
        disableReadOnlyTransactionCache(self)
174

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

201 202 203 204 205 206 207
  @UnrestrictedMethod
  def _unrestrictedResolveCategory(self, *args):
    # Categories used on predicate can be not available to user query, which
    # shall be applied with predicate.
    portal_categories = getToolByName(self, 'portal_categories')
    return portal_categories.resolveCategory(*args)

208
  security.declareProtected( Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
209 210
                             'buildSQLQuery' )
  def buildSQLQuery(self, strict_membership=0, table='category',
211 212
                          join_table='catalog', join_column='uid',
                          **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
213
    """
214 215 216
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
217

218
      XXX - This method is not implemented yet
Jean-Paul Smets's avatar
Jean-Paul Smets committed
219
    """
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    # 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')
240 241

    from_table_dict = {}
242

243 244 245 246 247 248 249 250
    # 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):
251
        category_value = self._unrestrictedResolveCategory(category, None)
252 253 254
        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
255
          membership_dict[base_category].append(category_value.asSQLExpression(
256 257 258
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
259 260 261 262 263
    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)
264

Jean-Paul Smets's avatar
Jean-Paul Smets committed
265
    # Then build SQL for multimembership_dict criteria
266 267 268 269 270 271 272
    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):
273
        category_value = self._unrestrictedResolveCategory(category)
274 275 276 277
        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
278
          multimembership_dict[base_category].append(category_value.asSQLExpression(
279 280 281
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
282 283 284 285 286
    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)
287 288

    # Build the join where expression
289
    join_select_list = []
Yoshinori Okuji's avatar
Yoshinori Okuji committed
290
    for k in from_table_dict.iterkeys():
291
      join_select_list.append('%s.%s = %s.uid' % (join_table, join_column, k))
292 293

    sql_text = ' AND '.join(join_select_list + membership_select_list +
294 295
                            multimembership_select_list)

296
    # Now merge identity and membership criteria
297 298 299 300
    if len(sql_text):
      catalog_kw['where_expression'] = SQLQuery(sql_text)
    else:
      catalog_kw['where_expression'] = ''
301 302 303 304 305 306 307
    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
308

309 310 311 312
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'buildSqlQuery' )
  buildSqlQuery = buildSQLQuery

Jérome Perrin's avatar
Jérome Perrin committed
313 314
  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLExpression' )
  def asSQLExpression(self, strict_membership=0, table='category'):
315
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
316 317 318
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
319
    """
Jérome Perrin's avatar
Jérome Perrin committed
320
    return self.buildSQLQuery(strict_membership=strict_membership, table=table)['where_expression']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
321

322 323 324 325
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlExpression' )
  asSqlExpression = asSQLExpression

Jérome Perrin's avatar
Jérome Perrin committed
326 327
  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLJoinExpression' )
  def asSQLJoinExpression(self, strict_membership=0, table='category', join_table='catalog', join_column='uid'):
328 329
    """
    """
Jérome Perrin's avatar
Jérome Perrin committed
330
    table_list = self.buildSQLQuery(strict_membership=strict_membership, table=table)['from_table_list']
331
    sql_text_list = map(lambda (a,b): '%s AS %s' % (b,a), filter(lambda (a,b): a != join_table, table_list))
332
    return ' , '.join(sql_text_list)
333

334 335 336 337
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlJoinExpression' )
  asSqlJoinExpression = asSQLJoinExpression

338 339 340 341 342 343 344 345 346 347 348 349
  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
350 351
  security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' )
  def getCriterionList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
352
    """
353
      Returns the list of criteria which are defined by the Predicate.
354

355 356
      Each criterion is returned in a TempBase instance intended to be
      displayed in a ListBox.
357

358
      XXX - It would be better to return criteria in a Criterion class
359
            instance
Jean-Paul Smets's avatar
Jean-Paul Smets committed
360
    """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
361
    if getattr(aq_base(self), '_identity_criterion', None) is None:
362 363
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
364 365 366 367 368 369 370 371 372 373 374 375 376 377
    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):
378 379 380
    """
      This methods sets parameters of a criterion. There is at most one
      criterion per property. Defined parameters are
381

382 383
      identity -- if not None, allows for testing identity of the property
                  with the provided value
384

385 386
      min      -- if not None, allows for testing that the property
                  is greater than min
387

388 389
      max      -- if not None, allows for testing that the property
                  is greater than max
390

391
    """
392
    # XXX 'min' and 'max' are built-in functions.
Yoshinori Okuji's avatar
Yoshinori Okuji committed
393
    if getattr(aq_base(self), '_identity_criterion', None) is None:
394 395
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
396
    if identity is not None :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
397
      self._identity_criterion[property] = identity
398 399 400 401 402 403 404 405 406 407
    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
408 409 410 411
      self._range_criterion[property] = (min, max)
    self.reindexObject()

  security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
412 413 414 415 416 417
  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
418
    if getattr(aq_base(self), '_identity_criterion', None) is None:
419 420
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
421
    if 'criterion_property_list' in kwd:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
422 423 424
      criterion_property_list = kwd['criterion_property_list']
      identity_criterion = {}
      range_criterion = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
425
      for criterion in self._identity_criterion.iterkeys() :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
426 427
        if criterion in criterion_property_list :
          identity_criterion[criterion] = self._identity_criterion[criterion]
Yoshinori Okuji's avatar
Yoshinori Okuji committed
428
      for criterion in self._range_criterion.iterkeys() :
Jean-Paul Smets's avatar
Jean-Paul Smets committed
429 430 431 432
        if criterion in criterion_property_list :
          range_criterion[criterion] = self._range_criterion[criterion]
      self._identity_criterion = identity_criterion
      self._range_criterion = range_criterion
433
    kwd['reindex_object'] = 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
434 435 436
    return self._edit(**kwd)

  # Predicate fusion method
Yoshinori Okuji's avatar
Yoshinori Okuji committed
437
  security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
438
  def setPredicateCategoryList(self, category_list):
439 440 441 442 443 444
    """
      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.
445 446

      WARNING: this method does not take into account scripts at
447 448
      this point.
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
449 450 451 452 453 454 455
    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 = []
456
    # reset criterions
457 458
    self._identity_criterion = PersistentMapping()
    self._range_criterion = PersistentMapping()
459

Jean-Paul Smets's avatar
Jean-Paul Smets committed
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
    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)
483
    self._setTestMethodIdList(test_method_id_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
484 485
    self.reindexObject()

Yoshinori Okuji's avatar
Yoshinori Okuji committed
486
  security.declareProtected(Permissions.AccessContentsInformation, 'generatePredicate')
487
  def generatePredicate(self, multimembership_criterion_base_category_list=(),
488 489
                        membership_criterion_base_category_list=(),
                        criterion_property_list=()):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
490
    """
491
    This method generates a new temporary predicate based on an ad-hoc
492
    interpretation of local properties of an object. For example,
493
    a start_range_min property will be interpreted as a way to define
494
    a min criterion on start_date.
495

496
    The purpose of this method is to be called from
497 498 499
    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
500
    """
501
    new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList())
502
    new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList())
503
    new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList())
504

505
    for base_category in multimembership_criterion_base_category_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
506 507 508
      category_list = self.getProperty(base_category + '_list')
      if category_list is not None and len(category_list)>0:
        for category in category_list:
509
          new_membership_criterion_category_list.append(base_category + '/' + category)
510
        if base_category not in new_multimembership_criterion_base_category_list:
511
          new_multimembership_criterion_base_category_list.append(base_category)
512

513 514 515 516 517
    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)
518
        if base_category not in new_membership_criterion_base_category_list:
519
          new_membership_criterion_base_category_list.append(base_category)
520

521
    new_criterion_property_list =  list(self.getCriterionPropertyList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
522 523 524
    identity_criterion = getattr(self,'_identity_criterion',{})
    range_criterion = getattr(self,'_range_criterion',{})
    # Look at local properties and make it criterion properties
525
    for property in criterion_property_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
526 527
      if property not in self.getCriterionPropertyList() \
        and property in self.propertyIds():
528
          new_criterion_property_list.append(property)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
529 530
          property_min = property + '_range_min'
          property_max = property + '_range_max'
531
          if getattr(self, 'get%s' % convertToUpperCase(property), None) is not None\
Jean-Paul Smets's avatar
Jean-Paul Smets committed
532 533
            and self.getProperty(property) is not None:
            identity_criterion[property] = self.getProperty(property)
534
          elif getattr(self, 'get%s' % convertToUpperCase(property_min), None) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
535 536 537 538 539 540
            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(
541
        membership_criterion_category=new_membership_criterion_category_list,
542
        membership_criterion_base_category=new_membership_criterion_base_category_list,
543 544
        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
545 546 547
        _identity_criterion=identity_criterion,
        _range_criterion=range_criterion)

548 549 550
    return new_self

  # Predicate handling
551 552 553
  security.declareProtected(Permissions.AccessContentsInformation,
                            'asPredicate')
  def asPredicate(self, script_id=None):
554
    """
555
      This method tries to convert the current Document into a predicate
556 557
      looking up methods named ${PortalType}_asPredicate,
      ${MetaType}_asPredicate, ${Class}_asPredicate     
558
    """
559 560 561 562
    if script_id is not None:
      script = getattr(self, script_id, None)
    else:
      script = self._getTypeBasedMethod('asPredicate')
563
    if script is not None:
564 565
      return script()
    return self
566 567 568 569

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

      TO BE IMPLEMENTED using portal_catalog(**kw)
572
    """
573
    pass
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606

  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))