Predicate.py 26.8 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
31
from Products.ERP5Type.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.Accessor.Constant import PropertyGetter as ConstantGetter
39
from Products.ERP5Type.Core.Folder import Folder
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40
from Products.ERP5Type.Document import newTempBase
41
from Products.ERP5Type.XMLObject import XMLObject
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42
from Products.ERP5Type.Utils import convertToUpperCase
43
from Products.ERP5Type.Cache import getReadOnlyTransactionCache, enableReadOnlyTransactionCache, disableReadOnlyTransactionCache
44
from Products.ZSQLCatalog.SQLCatalog import SQLQuery
45
from Products.ERP5Type.Globals import PersistentMapping
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    from_table_dict = {}
238

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

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

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

    sql_text = ' AND '.join(join_select_list + membership_select_list +
290 291
                            multimembership_select_list)

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

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

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

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

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

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

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

351 352
      Each criterion is returned in a TempBase instance intended to be
      displayed in a ListBox.
353

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

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

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

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

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

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

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

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

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

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

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

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

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

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

544 545 546
    return new_self

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

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

      TO BE IMPLEMENTED using portal_catalog(**kw)
568
    """
569
    pass
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 601 602

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