Predicate.py 26.5 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
43
from Products.ZSQLCatalog.SQLCatalog import SQLQuery
44
from Globals import PersistentMapping
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45 46

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

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

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

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

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

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

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

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

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

      - tested_base_category_list:  this is the list of category that we do
97
        want to test. For example, we might want to test only the
98
        destination or the source of a predicate.
99 100
      - 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
101 102 103
    """
    self = self.asPredicate()
    result = 1
104
    if getattr(aq_base(self), '_identity_criterion', None) is None:
105 106
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
107
#    LOG('PREDICATE TEST', 0,
108 109
#        'testing %s on context of %s' % \
#        (self.getRelativeUrl(), context.getRelativeUrl()))
110
    for property, value in self._identity_criterion.iteritems():
111
      result = result and (context.getProperty(property) in 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
  security.declareProtected( Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
202 203
                             'buildSQLQuery' )
  def buildSQLQuery(self, strict_membership=0, table='category',
204 205
                          join_table='catalog', join_column='uid',
                          **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
206
    """
207 208 209
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
210

211
      XXX - This method is not implemented yet
Jean-Paul Smets's avatar
Jean-Paul Smets committed
212
    """
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    # 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')
233 234 235
    portal_categories = getToolByName(self, 'portal_categories')

    from_table_dict = {}
236

237 238 239 240 241 242 243 244
    # 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):
245
        category_value = portal_categories.resolveCategory(category, None)
246 247 248
        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
249
          membership_dict[base_category].append(category_value.asSQLExpression(
250 251 252
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
253 254 255 256 257
    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)
258

Jean-Paul Smets's avatar
Jean-Paul Smets committed
259
    # Then build SQL for multimembership_dict criteria
260 261 262 263 264 265 266 267 268 269 270 271
    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
272
          multimembership_dict[base_category].append(category_value.asSQLExpression(
273 274 275
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
276 277 278 279 280
    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)
281 282

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

    sql_text = ' AND '.join(join_select_list + membership_select_list +
288 289
                            multimembership_select_list)

290
    # Now merge identity and membership criteria
291 292 293 294
    if len(sql_text):
      catalog_kw['where_expression'] = SQLQuery(sql_text)
    else:
      catalog_kw['where_expression'] = ''
295 296 297 298 299 300 301
    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
302

303 304 305 306
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'buildSqlQuery' )
  buildSqlQuery = buildSQLQuery

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

316 317 318 319
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlExpression' )
  asSqlExpression = asSQLExpression

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

328 329 330 331
  # Compatibililty SQL Sql
  security.declareProtected( Permissions.AccessContentsInformation, 'asSqlJoinExpression' )
  asSqlJoinExpression = asSQLJoinExpression

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

349 350
      Each criterion is returned in a TempBase instance intended to be
      displayed in a ListBox.
351

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

376 377
      identity -- if not None, allows for testing identity of the property
                  with the provided value
378

379 380
      min      -- if not None, allows for testing that the property
                  is greater than min
381

382 383
      max      -- if not None, allows for testing that the property
                  is greater than max
384

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

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

  # Predicate fusion method
Yoshinori Okuji's avatar
Yoshinori Okuji committed
431
  security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
432
  def setPredicateCategoryList(self, category_list):
433 434 435 436 437 438
    """
      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.
439 440

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    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)
477
    self._setTestMethodIdList(test_method_id_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
478 479
    self.reindexObject()

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

490
    The purpose of this method is to be called from
491 492 493
    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
494
    """
495
    new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList())
496
    new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList())
497
    new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList())
498

499
    for base_category in multimembership_criterion_base_category_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
500 501 502
      category_list = self.getProperty(base_category + '_list')
      if category_list is not None and len(category_list)>0:
        for category in category_list:
503
          new_membership_criterion_category_list.append(base_category + '/' + category)
504
        if base_category not in new_multimembership_criterion_base_category_list:
505
          new_multimembership_criterion_base_category_list.append(base_category)
506

507 508 509 510 511
    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)
512
        if base_category not in new_membership_criterion_base_category_list:
513
          new_membership_criterion_base_category_list.append(base_category)
514

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

542 543 544
    return new_self

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

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

      TO BE IMPLEMENTED using portal_catalog(**kw)
566
    """
567
    pass
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 596 597 598 599 600

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