コード例 #1
0
ファイル: Predicate.py プロジェクト: Soufianeterhzaz/erp5
class Predicate(XMLObject):
  """
    A Predicate object defines a list of criterions
    which can be applied to test a document or to search for documents.

    Predicates are defined by a combination of PropertySheet values
    (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
    script based test.

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

    The approach is intended to provide the expressivity of a rule
    based system without the burden of building a fully expressive
    rule engine.
  """
  meta_type = 'ERP5 Predicate'
  portal_type = 'Predicate'
  add_permission = Permissions.AddPortalContent
  isPredicate = ConstantGetter('isPredicate', value=True)

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.Predicate
                    , PropertySheet.CategoryCore
                    , PropertySheet.SortIndex
                    )

  # Declarative interfaces
  zope.interface.implements( interfaces.IPredicate, )

  security.declareProtected( Permissions.AccessContentsInformation, 'test' )
  def test(self, context, tested_base_category_list=None, 
           strict_membership=0, isMemberOf=None, **kw):
    """
      A Predicate can be tested on a given context.
      Parameters can passed in order to ignore some conditions.

      - tested_base_category_list:  this is the list of category that we do
        want to test. For example, we might want to test only the
        destination or the source of a predicate.
      - if strict_membership is specified, we should make sure that we
        are strictly a member of tested categories
      - isMemberOf can be a function caching results for
        CategoryTool.isMemberOf: it is always called with given 'context' and
        'strict_membership' values, and different categories.
    """
    self = self.asPredicate()
    if self is None:
      # asPredicate returned None, so this predicate never applies.
      # But if we reach this it is because catalog is not up to date.
      return False

    result = 1
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
#    LOG('PREDICATE TEST', 0,
#        'testing %s on context of %s' % \
#        (self.getRelativeUrl(), context.getRelativeUrl()))
    for property, value in self._identity_criterion.iteritems():
      if isinstance(value, (list, tuple)):
        result = context.getProperty(property) in value
      else:
        result = context.getProperty(property) == value
#      LOG('predicate test', 0,
#          '%s after prop %s : %s == %s' % \
#          (result, property, context.getProperty(property), value))
      if not result:
        return result
    for property, (min, max) in self._range_criterion.iteritems():
      value = context.getProperty(property)
      if min is not None:
        result = value >= min
#        LOG('predicate test', 0,
#            '%s after prop %s : %s >= %s' % \
#            (result, property, value, min))
        if not result:
          return result
      if max is not None:
        result = value < max
#        LOG('predicate test', 0,
#            '%s after prop %s : %s < %s' % \
#            (result, property, value, max))
        if not result:
          return result
    multimembership_criterion_base_category_list = \
        self.getMultimembershipCriterionBaseCategoryList()
    membership_criterion_base_category_list = \
        self.getMembershipCriterionBaseCategoryList()
    tested_base_category = {}
#    LOG('predicate test', 0,
#        'categories will be tested in multi %s single %s as %s' % \
#        (multimembership_criterion_base_category_list,
#        membership_criterion_base_category_list,
#        self.getMembershipCriterionCategoryList()))
    # Test category memberships. Enable the read-only transaction cache
    # because this part is strictly read-only, and context.isMemberOf
    # is very expensive when the category list has many items.
    if isMemberOf is None:
      isMemberOf = context._getCategoryTool().isMemberOf
    with readOnlyTransactionCache():
      for c in self.getMembershipCriterionCategoryList():
        bc = c.split('/', 1)[0]
        if tested_base_category_list is None or bc in tested_base_category_list:
          if bc in multimembership_criterion_base_category_list:
            if not isMemberOf(context, c, strict_membership=strict_membership):
              return 0
          elif bc in membership_criterion_base_category_list and \
               not tested_base_category.get(bc):
            tested_base_category[bc] = \
              isMemberOf(context, c, strict_membership=strict_membership)
    if 0 in tested_base_category.itervalues():
      return 0

    # Test method calls
    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:
          method = getattr(context,test_method_id)
          try:
            result = method(self)
          except TypeError:
            if method.func_code.co_argcount != isinstance(method, MethodType):
              raise
            # backward compatibilty with script that takes no argument
            warn('Predicate %s uses an old-style method (%s) that does not'
                 ' take the predicate as argument' % (
               self.getRelativeUrl(), method.__name__), DeprecationWarning)
            result = method()
#          LOG('predicate test', 0,
#              '%s after method %s ' % (result, test_method_id))
          if not result:
            return result
    test_tales_expression = self.getTestTalesExpression()
    if test_tales_expression != 'python: True':
      expression = Expression(test_tales_expression)
      from Products.ERP5Type.Utils import createExpressionContext
      # evaluate a tales expression with the tested value as context
      result = expression(createExpressionContext(context))
    return result

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

  security.declareProtected( Permissions.AccessContentsInformation,
                             'buildSQLQuery' )
  def buildSQLQuery(self, strict_membership=0, table='category',
                          join_table='catalog', join_column='uid',
                          **kw):
    """
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.

      XXX - This method is not implemented yet
    """
    # Build the identity criterion
    catalog_kw = {}
    catalog_kw.update(kw) # query_table, REQUEST, ignore_empty_string, **kw
    criterion_list = self.getCriterionList()
    # BBB: accessor is not present on old Predicate property sheet.
    if criterion_list or getattr(self, 'isEmptyPredicateValid', lambda: True)():
      for criterion in criterion_list:
        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:
          # if a filter was passed as argument
          if catalog_kw.has_key(criterion.property):
            if isinstance(catalog_kw[criterion.property], (tuple, list)):
              catalog_filter_set = set(catalog_kw[criterion.property])
            else:
              catalog_filter_set = set([catalog_kw[criterion.property]])
            if isinstance(criterion.identity, (tuple, list)):
              parameter_filter_set = set(criterion.identity)
            else:
              parameter_filter_set = set([criterion.identity])
            catalog_kw[criterion.property] = \
                list(catalog_filter_set.intersection(parameter_filter_set))
          else:
            catalog_kw[criterion.property] = criterion.identity
    else:
      # By catalog definition, no object has uid 0, so this condition forces an
      # empty result.
      catalog_kw['uid'] = 0

    portal_catalog = getToolByName(self, 'portal_catalog')

    from_table_dict = {}

    # First build SQL for membership criteria
    # It would be much nicer if all this was handled by the catalog in a central place
    membership_dict = {}
    for base_category in self.getMembershipCriterionBaseCategoryList():
      membership_dict[base_category] = [] # Init dict with valid base categories
    for category in self.getMembershipCriterionCategoryList():
      base_category = category.split('/')[0] # Retrieve base category
      if membership_dict.has_key(base_category):
        category_value = self._unrestrictedResolveCategory(category, None)
        if category_value is not None:
          table_alias = "single_%s_%s" % (table, base_category)
          from_table_dict[table_alias] = 'category'
          membership_dict[base_category].append(category_value.asSQLExpression(
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
    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)

    # Then build SQL for multimembership_dict criteria
    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 = self._unrestrictedResolveCategory(category)
        if category_value is not None:
          join_count += 1
          table_alias = "multi_%s_%s" % (table, join_count)
          from_table_dict[table_alias] = 'category'
          multimembership_dict[base_category].append(category_value.asSQLExpression(
                                          strict_membership=strict_membership,
                                          table=table_alias,
                                          base_category=base_category))
    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)

    # Build the join where expression
    join_select_list = []
    for k in from_table_dict.iterkeys():
      join_select_list.append('%s.%s = %s.uid' % (join_table, join_column, k))

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

    # Now merge identity and membership criteria
    if len(sql_text):
      catalog_kw['where_expression'] = SQLQuery(sql_text)
    else:
      catalog_kw['where_expression'] = ''
    # force implicit join
    catalog_kw['implicit_join'] = True
    sql_query = portal_catalog.buildSQLQuery(**catalog_kw)
    # XXX from_table_list is None most of the time after the explicit_join work
    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

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

  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLExpression' )
  def asSQLExpression(self, strict_membership=0, table='category'):
    """
      A Predicate can be rendered as an SQL expression. This
      can be used to generate SQL requests in reports or in
      catalog search queries.
    """
    return self.buildSQLQuery(strict_membership=strict_membership, table=table)['where_expression']

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

  security.declareProtected( Permissions.AccessContentsInformation, 'asSQLJoinExpression' )
  def asSQLJoinExpression(self, strict_membership=0, table='category', join_table='catalog', join_column='uid'):
    """
    """
    table_list = self.buildSQLQuery(strict_membership=strict_membership, table=table)['from_table_list']
    sql_text_list = map(lambda (a,b): '%s AS %s' % (b,a), filter(lambda (a,b): a != join_table, table_list))
    return ' , '.join(sql_text_list)

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

  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)

  security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' )
  def getCriterionList(self, **kw):
    """
      Returns the list of criteria which are defined by the Predicate.

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

      XXX - It would be better to return criteria in a Criterion class
            instance
    """
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
    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):
    """
      This methods sets parameters of a criterion. There is at most one
      criterion per property. Defined parameters are

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

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

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

    """
    # XXX 'min' and 'max' are built-in functions.
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
    if identity is not None :
      self._identity_criterion[property] = identity
    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:
      self._range_criterion[property] = (min, max)
    self.reindexObject()

  security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
  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.
    """
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
    if 'criterion_property_list' in kwd:
      criterion_property_list = kwd['criterion_property_list']
      identity_criterion = PersistentMapping()
      range_criterion = PersistentMapping()
      for criterion in self._identity_criterion.iterkeys() :
        if criterion in criterion_property_list :
          identity_criterion[criterion] = self._identity_criterion[criterion]
      for criterion in self._range_criterion.iterkeys() :
        if criterion in criterion_property_list :
          range_criterion[criterion] = self._range_criterion[criterion]
      self._identity_criterion = identity_criterion
      self._range_criterion = range_criterion
    kwd['reindex_object'] = 1
    return self._edit(**kwd)

  # Predicate fusion method
  security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' )
  def setPredicateCategoryList(self, category_list):
    """
      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.

      WARNING: this method does not take into account scripts at
      this point.
    """
    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 = []
    # reset criterions
    self._identity_criterion = PersistentMapping()
    self._range_criterion = PersistentMapping()

    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)
    self._setTestMethodIdList(test_method_id_list)
    self.reindexObject()

  security.declareProtected(Permissions.AccessContentsInformation, 'generatePredicate')
  def generatePredicate(self, multimembership_criterion_base_category_list=(),
                        membership_criterion_base_category_list=(),
                        criterion_property_list=(),
                        identity_criterion=None,
                        range_criterion=None,):
    """
    This method generates a new temporary predicate based on an ad-hoc
    interpretation of local properties of an object. For example,
    a start_range_min property will be interpreted as a way to define
    a min criterion on start_date.

    The purpose of this method is to be called from
    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.
    """
    new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList())
    new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList())
    new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList())

    for base_category in multimembership_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)
        if base_category not in new_multimembership_criterion_base_category_list:
          new_multimembership_criterion_base_category_list.append(base_category)

    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)
        if base_category not in new_membership_criterion_base_category_list:
          new_membership_criterion_base_category_list.append(base_category)

    new_criterion_property_list =  list(self.getCriterionPropertyList())

    # We need to build new criteria for asContext, and we should not
    # modify the original, so we always make copies. Since the usage is
    # temporary, use dicts instead of persistent mappings.
    new_identity_criterion = dict(getattr(self, '_identity_criterion', None) or
                                  {})
    new_identity_criterion.update(identity_criterion or {})
    new_range_criterion = dict(getattr(self, '_range_criterion', None) or {})
    new_range_criterion.update(range_criterion or {})

    # Look at local properties and make it criterion properties
    for property in criterion_property_list:
      if property not in self.getCriterionPropertyList() \
        and property in self.propertyIds():
          new_criterion_property_list.append(property)
          property_min = property + '_range_min'
          property_max = property + '_range_max'
          if getattr(self, 'get%s' % convertToUpperCase(property), None) is not None\
            and self.getProperty(property) is not None:
            new_identity_criterion[property] = self.getProperty(property)
          elif getattr(self, 'get%s' % convertToUpperCase(property_min), None) is not None:
            min = self.getProperty(property_min)
            max = self.getProperty(property_max)
            new_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(
        membership_criterion_category=new_membership_criterion_category_list,
        membership_criterion_base_category=new_membership_criterion_base_category_list,
        multimembership_criterion_base_category=new_multimembership_criterion_base_category_list,
        criterion_property_list=new_criterion_property_list,
        _identity_criterion=new_identity_criterion,
        _range_criterion=new_range_criterion)

    return new_self

  security.declareProtected(Permissions.AccessContentsInformation,
                            'asPredicate')
  def asPredicate(self, script_id=None):
    """
      This method tries to convert the current Document into a predicate
      looking up methods named Class_asPredictae, MetaType_asPredicate, PortalType_asPredicate
    """
    cache = getTransactionalVariable()
    key = id(self), script_id
    if 'asPredicate' in cache:
      cache = cache['asPredicate']
      if key in cache:
        return cache[key]
    else:
      cache = cache['asPredicate'] = {}
    script = self._getTypeBasedMethod('asPredicate', script_id)
    if script is not None:
      self = script()
    cache[key] = self
    return self

  def searchPredicate(self, **kw):
    """
      Returns a list of documents matching the predicate

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

  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))
コード例 #2
0
class IdTool(BaseTool):
  """
    This tools handles the generation of IDs.
  """
  id = 'portal_ids'
  meta_type = 'ERP5 Id Tool'
  portal_type = 'Id Tool'
  title = 'Id Generators'

  # Declarative Security
  security = ClassSecurityInfo()

  security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainIdTool', _dtmldir )

  def newContent(self, *args, **kw):
    """
      the newContent is overriden to not use generateNewId
    """
    if id not in kw:
      new_id = self._generateNextId()
      if new_id is not None:
        kw['id'] = new_id
      else:
        raise ValueError('Failed to gererate id')
    return BaseTool.newContent(self, *args, **kw)

  def _get_id(self, id):
    """
      _get_id is overrided to not use generateNewId
      It is used for example when an object is cloned
    """
    if self._getOb(id, None) is None :
      return id
    return self._generateNextId()

  @caching_instance_method(id='IdTool._getLatestIdGenerator',
    cache_factory='erp5_content_long')
  def _getLatestIdGenerator(self, reference):
    """
      Tries to find the id_generator with the latest version
      from the current object.
      Use the low-level to create a site without catalog
    """
    assert reference
    id_last_generator = None
    version_last_generator = 0
    for generator in self.objectValues():
      if generator.getReference() == reference:
        # Version Property Sheet defines 'version' property as a 'string'
        version = int(generator.getVersion())
        if version > version_last_generator:
          id_last_generator = generator.getId()
          version_last_generator = version
    if id_last_generator is None:
      raise KeyError(repr(reference))
    return id_last_generator

  def _getLatestGeneratorValue(self, id_generator):
    """
      Return the last generator with the reference
    """
    return self._getOb(self._getLatestIdGenerator(id_generator))

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewId')
  def generateNewId(self, id_group=None, default=None, method=_marker,
                    id_generator=None, poison=False):
    """
      Generate the next id in the sequence of ids of a particular group
    """
    if id_group in (None, 'None'):
      raise ValueError('%r is not a valid id_group' % id_group)
    # for compatibilty with sql data, must not use id_group as a list
    if not isinstance(id_group, str):
      id_group = repr(id_group)
      warnings.warn('id_group must be a string, other types '
                    'are deprecated.', DeprecationWarning)
    if id_generator is None:
      id_generator = 'document'
    if method is not _marker:
      warnings.warn("Use of 'method' argument is deprecated", DeprecationWarning)
    try:
      #use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      new_id = last_generator.generateNewId(
        id_group=id_group,
        default=default,
        poison=poison,
      )
    except KeyError:
      # XXX backward compatiblity
      if self.getTypeInfo():
        LOG('generateNewId', ERROR, 'while generating id')
        raise
      else:
        # Compatibility code below, in case the last version of erp5_core
        # is not installed yet
        warnings.warn("You are using an old version of erp5_core to generate"
                      "ids.\nPlease update erp5_core business template to "
                      "use new id generators", DeprecationWarning)
        dict_ids = getattr(aq_base(self), 'dict_ids', None)
        if dict_ids is None:
          dict_ids = self.dict_ids = PersistentMapping()
        new_id = None
        # Getting the last id
        if default is None:
          default = 0
        marker = []
        new_id = dict_ids.get(id_group, marker)
        if method is _marker:
          if new_id is marker:
            new_id = default
          else:
            new_id = new_id + 1
        else:
          if new_id is marker:
            new_id = default
          new_id = method(new_id)
        # Store the new value
        dict_ids[id_group] = new_id
    return new_id

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewIdList')
  def generateNewIdList(self, id_group=None, id_count=1, default=None,
                        store=_marker, id_generator=None, poison=False):
    """
      Generate a list of next ids in the sequence of ids of a particular group
    """
    if id_group in (None, 'None'):
      raise ValueError('%r is not a valid id_group' % id_group)
    # for compatibilty with sql data, must not use id_group as a list
    if not isinstance(id_group, str):
      id_group = repr(id_group)
      warnings.warn('id_group must be a string, other types '
                    'are deprecated.', DeprecationWarning)
    if id_generator is None:
      id_generator = 'uid'
    if store is not _marker:
      warnings.warn("Use of 'store' argument is deprecated.",
                    DeprecationWarning)
    try:
      #use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      new_id_list = last_generator.generateNewIdList(id_group=id_group,
                         id_count=id_count, default=default, poison=poison)
    except (KeyError, ValueError):
      # XXX backward compatiblity
      if self.getTypeInfo():
        LOG('generateNewIdList', ERROR, 'while generating id')
        raise
      else:
        # Compatibility code below, in case the last version of erp5_core
        # is not installed yet
        warnings.warn("You are using an old version of erp5_core to generate"
                      "ids.\nPlease update erp5_core business template to "
                      "use new id generators", DeprecationWarning)
        new_id = None
        if default is None:
          default = 1
        # XXX It's temporary, a New API will be implemented soon
        #     the code will be change
        portal = self.getPortalObject()
        try:
          query = portal.IdTool_zGenerateId
          commit = portal.IdTool_zCommit
        except AttributeError:
          portal_catalog = portal.portal_catalog.getSQLCatalog()
          query = portal_catalog.z_portal_ids_generate_id
          commit = portal_catalog.z_portal_ids_commit
        try:
          result = query(id_group=id_group, id_count=id_count, default=default)
        finally:
          commit()
        new_id = result[0]['LAST_INSERT_ID()']
        if store:
          if getattr(aq_base(self), 'dict_length_ids', None) is None:
            # Length objects are stored in a persistent mapping: there is one
            # Length object per id_group.
            self.dict_length_ids = PersistentMapping()
          if self.dict_length_ids.get(id_group) is None:
            self.dict_length_ids[id_group] = Length(new_id)
          self.dict_length_ids[id_group].set(new_id)
        if six.PY2:
          new_id_list = range(new_id - id_count, new_id)
        else:
          new_id_list = list(range(new_id - id_count, new_id))
    return new_id_list

  security.declareProtected(Permissions.ModifyPortalContent,
                            'initializeGenerator')
  def initializeGenerator(self, id_generator=None, all=False):
    """
    Initialize generators. This is mostly used when a new ERP5 site
    is created. Some generators will need to do some initialization like
    creating SQL Database, prepare some data in ZODB, etc
    """
    if not all:
      #Use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      last_generator.initializeGenerator()
    else:
      # recovery all the generators and initialize them
      for generator in self.objectValues(\
                       portal_type='Application Id Generator'):
        generator.initializeGenerator()

  security.declareProtected(Permissions.ModifyPortalContent,
                            'clearGenerator')
  def clearGenerator(self, id_generator=None, all=False):
    """
    Clear generators data. This can be usefull when working on a
    development instance or in some other rare cases. This will
    loose data and must be use with caution

    This can be incompatible with some particular generator implementation,
    in this case a particular error will be raised (to be determined and
    added here)
    """
    if not all:
      #Use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      last_generator.clearGenerator()

    else:
      if len(self.objectValues()) == 0:
        # compatibility with old API
        self.getPortalObject().IdTool_zDropTable()
        self.getPortalObject().IdTool_zCreateTable()
      for generator in self.objectValues(\
                       portal_type='Application Id Generator'):
        generator.clearGenerator()

  ## XXX Old API deprecated
  #backward compatibility
  security.declareProtected(Permissions.AccessContentsInformation,
                           'generateNewLengthIdList')
  generateNewLengthIdList = generateNewIdList

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getLastLengthGeneratedId')
  def getLastLengthGeneratedId(self, id_group, default=None):
    """
    Get the last length id generated
    """
    warnings.warn('getLastLengthGeneratedId is deprecated',
                   DeprecationWarning)
    # check in persistent mapping if exists
    if getattr(aq_base(self), 'dict_length_ids', None) is not None:
      last_id = self.dict_length_ids.get(id_group)
      if last_id is not None:
        return last_id.value - 1
    # otherwise check in mysql
    # XXX It's temporary, a New API will be implemented soon
    #     the code will be change
    portal = self.getPortalObject()
    try:
      query = portal.IdTool_zGetLastId
    except AttributeError:
      query = portal.portal_catalog.getSQLCatalog().z_portal_ids_get_last_id
    result = query(id_group=id_group)
    if len(result):
      try:
        return result[0]['last_id']
      except KeyError:
        return result[0]['LAST_INSERT_ID()']
    return default

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getLastGeneratedId')
  def getLastGeneratedId(self, id_group=None, default=None):
    """
    Get the last id generated
    """
    warnings.warn('getLastGeneratedId is deprecated', DeprecationWarning)
    if getattr(aq_base(self), 'dict_ids', None) is None:
      self.dict_ids = PersistentMapping()
    last_id = None
    if id_group is not None and id_group != 'None':
      last_id = self.dict_ids.get(id_group, default)
    return last_id

  security.declareProtected(Permissions.ModifyPortalContent,
                            'setLastGeneratedId')
  def setLastGeneratedId(self, new_id, id_group=None):
    """
    Set a new last id. This is usefull in order to reset
    a sequence of ids.
    """
    if getattr(aq_base(self), 'dict_ids', None) is None:
      self.dict_ids = PersistentMapping()
    if id_group is not None and id_group != 'None':
      self.dict_ids[id_group] = new_id

  security.declareProtected(Permissions.AccessContentsInformation,
                           'generateNewLengthId')
  def generateNewLengthId(self, id_group=None, default=None, store=_marker):
     """Generates an Id using a conflict free id generator. Deprecated.
     """
     warnings.warn('generateNewLengthId is deprecated.\n'
                   'Use generateNewIdList with a sql id_generator',
                   DeprecationWarning)
     if store is not _marker:
       return self.generateNewIdList(id_group=id_group,
                        id_count=1, default=default, store=store)[0]
     return self.generateNewIdList(id_group=id_group,
                        id_count=1, default=default)[0]

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getDictLengthIdsItems')
  def getDictLengthIdsItems(self):
    """
      Return a copy of dict_length_ids.
      This is a workaround to access the persistent mapping content from ZSQL
      method to be able to insert initial tuples in the database at creation.
    """
    if getattr(self, 'dict_length_ids', None) is None:
      self.dict_length_ids = PersistentMapping()
    return self.dict_length_ids.items()

  security.declarePrivate('dumpDictLengthIdsItems')
  def dumpDictLengthIdsItems(self):
    """
      Store persistently data from SQL table portal_ids.
    """
    portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog()
    query = getattr(portal_catalog, 'z_portal_ids_dump')
    dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None)
    if dict_length_ids is None:
      dict_length_ids = self.dict_length_ids = PersistentMapping()
    for line in query().dictionaries():
      id_group = line['id_group']
      last_id = line['last_id']
      stored_last_id = self.dict_length_ids.get(id_group)
      if stored_last_id is None:
        self.dict_length_ids[id_group] = Length(last_id)
      else:
        stored_last_id_value = stored_last_id()
        if stored_last_id_value < last_id:
          stored_last_id.set(last_id)
        else:
          if stored_last_id_value > last_id:
            LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \
                'than SQL value (%r). Keeping ZODB value untouched.' % \
                (stored_last_id, id_group, last_id))
コード例 #3
0
ファイル: IdTool.py プロジェクト: MarkTang/erp5
class IdTool(BaseTool):
  """
    This tools handles the generation of IDs.
  """
  zope.interface.implements(interfaces.IIdTool)
  id = 'portal_ids'
  meta_type = 'ERP5 Id Tool'
  portal_type = 'Id Tool'

  # Declarative Security
  security = ClassSecurityInfo()

  security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainIdTool', _dtmldir )

  def newContent(self, *args, **kw):
    """
      the newContent is overriden to not use generateNewId
    """
    if not kw.has_key(id):
      new_id = self._generateNextId()
      if new_id is not None:
        kw['id'] = new_id
      else:
        raise ValueError('Failed to gererate id')
    return BaseTool.newContent(self, *args, **kw)

  def _get_id(self, id):
    """
      _get_id is overrided to not use generateNewId
      It is used for example when an object is cloned
    """
    if self._getOb(id, None) is None :
      return id
    return self._generateNextId()

  @caching_instance_method(id='IdTool._getLatestIdGenerator',
    cache_factory='erp5_content_long')
  def _getLatestIdGenerator(self, reference):
    """
      Tries to find the id_generator with the latest version
      from the current object.
      Use the low-level to create a site without catalog
    """
    assert reference
    id_tool = self.getPortalObject().portal_ids
    id_last_generator = None
    version_last_generator = 0
    for generator in id_tool.objectValues():
      if generator.getReference() == reference:
        version = generator.getVersion()
        if version > version_last_generator:
          id_last_generator = generator.getId()
          version_last_generator = generator.getVersion()
    if id_last_generator is None:
      raise KeyError, 'The generator %s is not present' % (reference,)
    return id_last_generator

  def _getLatestGeneratorValue(self, id_generator):
    """
      Return the last generator with the reference
    """
    id_tool = self.getPortalObject().portal_ids
    last_id_generator = self._getLatestIdGenerator(id_generator)
    last_generator = id_tool._getOb(last_id_generator)
    return last_generator

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewId')
  def generateNewId(self, id_group=None, default=None, method=_marker,
                    id_generator=None):
    """
      Generate the next id in the sequence of ids of a particular group
    """
    if id_group in (None, 'None'):
      raise ValueError, '%s is not a valid id_group' % (repr(id_group), )
    # for compatibilty with sql data, must not use id_group as a list
    if not isinstance(id_group, str):
      id_group = repr(id_group)
      warnings.warn('id_group must be a string, other types '
                    'are deprecated.', DeprecationWarning)
    if id_generator is None:
      id_generator = 'document'
    if method is not _marker:
      warnings.warn("Use of 'method' argument is deprecated", DeprecationWarning)
    try:
      #use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      new_id = last_generator.generateNewId(id_group=id_group, \
                                            default=default)
    except KeyError:
      template_tool = getattr(self, 'portal_templates', None)
      revision = template_tool.getInstalledBusinessTemplateRevision('erp5_core')
      # XXX backward compatiblity
      if int(revision) > 1561:
        LOG('generateNewId', ERROR, 'while generating id')
        raise
      else:
        # Compatibility code below, in case the last version of erp5_core
        # is not installed yet
        warnings.warn("You are using an old version of erp5_core to generate"
                      "ids.\nPlease update erp5_core business template to "
                      "use new id generators", DeprecationWarning)
        dict_ids = getattr(aq_base(self), 'dict_ids', None)
        if dict_ids is None:
          dict_ids = self.dict_ids = PersistentMapping()
        new_id = None
        # Getting the last id
        if default is None:
          default = 0
        marker = []
        new_id = dict_ids.get(id_group, marker)
        if method is _marker:
          if new_id is marker:
            new_id = default
          else:
            new_id = new_id + 1
        else:
          if new_id is marker:
            new_id = default
          new_id = method(new_id)
        # Store the new value
        dict_ids[id_group] = new_id
    return new_id

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewIdList')
  def generateNewIdList(self, id_group=None, id_count=1, default=None,
                        store=_marker, id_generator=None):
    """
      Generate a list of next ids in the sequence of ids of a particular group
    """
    if id_group in (None, 'None'):
      raise ValueError, '%s is not a valid id_group' % (repr(id_group), )
    # for compatibilty with sql data, must not use id_group as a list
    if not isinstance(id_group, str):
      id_group = repr(id_group)
      warnings.warn('id_group must be a string, other types '
                    'are deprecated.', DeprecationWarning)
    if id_generator is None:
      id_generator = 'uid'
    if store is not _marker:
      warnings.warn("Use of 'store' argument is deprecated.",
                    DeprecationWarning)
    try:
      #use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      new_id_list = last_generator.generateNewIdList(id_group=id_group,
                         id_count=id_count, default=default)
    except (KeyError, ValueError):
      template_tool = getattr(self, 'portal_templates', None)
      revision = template_tool.getInstalledBusinessTemplateRevision('erp5_core')
      # XXX backward compatiblity
      if int(revision) > 1561:
        LOG('generateNewIdList', ERROR, 'while generating id')
        raise
      else:
        # Compatibility code below, in case the last version of erp5_core
        # is not installed yet
        warnings.warn("You are using an old version of erp5_core to generate"
                      "ids.\nPlease update erp5_core business template to "
                      "use new id generators", DeprecationWarning)
        new_id = None
        if default is None:
          default = 1
        # XXX It's temporary, a New API will be implemented soon
        #     the code will be change
        portal = self.getPortalObject()
        query = getattr(portal, 'IdTool_zGenerateId', None)
        commit = getattr(portal, 'IdTool_zCommit', None)

        if query is None or commit is None:
          portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog()
          query = getattr(portal_catalog, 'z_portal_ids_generate_id')
          commit = getattr(portal_catalog, 'z_portal_ids_commit')
        if None in (query, commit):
          raise AttributeError, 'Error while generating Id: ' \
            'idTool_zGenerateId and/or idTool_zCommit could not ' \
            'be found.'
        try:
          result = query(id_group=id_group, id_count=id_count, default=default)
        finally:
          commit()
        new_id = result[0]['LAST_INSERT_ID()']
        if store:
          if getattr(aq_base(self), 'dict_length_ids', None) is None:
            # Length objects are stored in a persistent mapping: there is one
            # Length object per id_group.
            self.dict_length_ids = PersistentMapping()
          if self.dict_length_ids.get(id_group) is None:
            self.dict_length_ids[id_group] = Length(new_id)
          self.dict_length_ids[id_group].set(new_id)
        new_id_list = range(new_id - id_count, new_id)
    return new_id_list

  security.declareProtected(Permissions.ModifyPortalContent,
                            'initializeGenerator')
  def initializeGenerator(self, id_generator=None, all=False):
    """
    Initialize generators. This is mostly used when a new ERP5 site
    is created. Some generators will need to do some initialization like
    creating SQL Database, prepare some data in ZODB, etc
    """
    if not all:
      #Use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      last_generator.initializeGenerator()
    else:
      # recovery all the generators and initialize them
      for generator in self.objectValues(\
                       portal_type='Application Id Generator'):
        generator.initializeGenerator()

  security.declareProtected(Permissions.ModifyPortalContent,
                            'clearGenerator')
  def clearGenerator(self, id_generator=None, all=False):
    """
    Clear generators data. This can be usefull when working on a
    development instance or in some other rare cases. This will
    loose data and must be use with caution

    This can be incompatible with some particular generator implementation,
    in this case a particular error will be raised (to be determined and
    added here)
    """
    if not all:
      #Use _getLatestGeneratorValue here for that the technical level
      #must not call the method
      last_generator = self._getLatestGeneratorValue(id_generator)
      last_generator.clearGenerator()

    else:
      if len(self.objectValues()) == 0:
        # compatibility with old API
        self.getPortalObject().IdTool_zDropTable()
        self.getPortalObject().IdTool_zCreateTable()
      for generator in self.objectValues(\
                       portal_type='Application Id Generator'):
        generator.clearGenerator()

  ## XXX Old API deprecated
  #backward compatibility
  generateNewLengthIdList = generateNewIdList

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getLastLengthGeneratedId')
  def getLastLengthGeneratedId(self, id_group, default=None):
    """
    Get the last length id generated
    """
    warnings.warn('getLastLengthGeneratedId is deprecated',
                   DeprecationWarning)
    # check in persistent mapping if exists
    if getattr(aq_base(self), 'dict_length_ids', None) is not None:
      last_id = self.dict_length_ids.get(id_group)
      if last_id is not None:
        return last_id.value - 1
    # otherwise check in mysql
    # XXX It's temporary, a New API will be implemented soon
    #     the code will be change
    portal = self.getPortalObject()
    query = getattr(portal, 'IdTool_zGetLastId', None)
    if query is None:
      portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog()
      query = getattr(portal_catalog, 'z_portal_ids_get_last_id')
    if query is None:
      raise AttributeError, 'Error while getting last Id: ' \
            'IdTool_zGetLastId could not ' \
            'be found.'
    result = query(id_group=id_group)
    if len(result):
      try:
        return result[0]['last_id']
      except KeyError:
        return result[0]['LAST_INSERT_ID()']
    return default

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getLastGeneratedId')
  def getLastGeneratedId(self, id_group=None, default=None):
    """
    Get the last id generated
    """
    warnings.warn('getLastGeneratedId is deprecated', DeprecationWarning)
    if getattr(aq_base(self), 'dict_ids', None) is None:
      self.dict_ids = PersistentMapping()
    last_id = None
    if id_group is not None and id_group != 'None':
      last_id = self.dict_ids.get(id_group, default)
    return last_id

  security.declareProtected(Permissions.ModifyPortalContent,
                            'setLastGeneratedId')
  def setLastGeneratedId(self, new_id, id_group=None):
    """
    Set a new last id. This is usefull in order to reset
    a sequence of ids.
    """
    if getattr(aq_base(self), 'dict_ids', None) is None:
      self.dict_ids = PersistentMapping()
    if id_group is not None and id_group != 'None':
      self.dict_ids[id_group] = new_id

  security.declareProtected(Permissions.AccessContentsInformation,
                           'generateNewLengthId')
  def generateNewLengthId(self, id_group=None, default=None, store=_marker):
     """Generates an Id using a conflict free id generator. Deprecated.
     """
     warnings.warn('generateNewLengthId is deprecated.\n'
                   'Use generateNewIdList with a sql id_generator',
                   DeprecationWarning)
     if store is not _marker:
       return self.generateNewIdList(id_group=id_group,
                        id_count=1, default=default, store=store)[0]
     return self.generateNewIdList(id_group=id_group,
                        id_count=1, default=default)[0]

  security.declareProtected(Permissions.AccessContentsInformation,
                            'getDictLengthIdsItems')
  def getDictLengthIdsItems(self):
    """
      Return a copy of dict_length_ids.
      This is a workaround to access the persistent mapping content from ZSQL
      method to be able to insert initial tuples in the database at creation.
    """
    if getattr(self, 'dict_length_ids', None) is None:
      self.dict_length_ids = PersistentMapping()
    return self.dict_length_ids.items()

  security.declarePrivate('dumpDictLengthIdsItems')
  def dumpDictLengthIdsItems(self):
    """
      Store persistently data from SQL table portal_ids.
    """
    portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog()
    query = getattr(portal_catalog, 'z_portal_ids_dump')
    dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None)
    if dict_length_ids is None:
      dict_length_ids = self.dict_length_ids = PersistentMapping()
    for line in query().dictionaries():
      id_group = line['id_group']
      last_id = line['last_id']
      stored_last_id = self.dict_length_ids.get(id_group)
      if stored_last_id is None:
        self.dict_length_ids[id_group] = Length(last_id)
      else:
        stored_last_id_value = stored_last_id()
        if stored_last_id_value < last_id:
          stored_last_id.set(last_id)
        else:
          if stored_last_id_value > last_id:
            LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \
                'than SQL value (%r). Keeping ZODB value untouched.' % \
                (stored_last_id, id_group, last_id))
コード例 #4
0
class XMLMatrix(Folder):
    """
        A mix-in class which provides a matrix like
        access to objects. Matrices are of any dimension.
        A single XMLMatrix may contain multiple matrices,
        of different dimension. Each matrix is associated to
        a so-called 'base_id'.

        We still must make XMLMatrix a subclass of Base so
        that we can inherit from ExtensionClass.Base
        which is required for multiple inheritance to work
        as expected. Read this for more information:

        http://www.daa.com.au/pipermail/pygtk/2001-May/001180.html

        In our case, we will use Folder because we want to inherit
        from Folder consistency checking

    """

    # Declarative security
    security = ClassSecurityInfo()

    # Matrix Methods
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCell' )
    def getCell(self, *kw , **kwd):
      """
          Access a cell at row and column
      """
      if getattr(aq_base(self), 'index', None) is None:
        return None

      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return None

      cell_id = self.keyToId(kw, base_id = base_id)
      if cell_id is None:
        return None
      return self.get(cell_id)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellProperty' )
    def getCellProperty(self, *kw , **kwd):
      """
          Get a property of a cell at row and column
      """
      base_id= kwd.get('base_id', "cell")
      cell = self.getCell(*kw, **kwd)
      if cell is None:
        return None

      return cell.getProperty(base_id)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasCell' )
    def hasCell(self, *kw , **kwd):
      """
          Checks if matrix corresponding to base_id contains cell specified
          by *kw coordinates.
      """
      return self.getCell(*kw, **kwd) is not None

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasCellContent' )
    def hasCellContent(self, base_id='cell'):
      """
          Checks if matrix corresponding to base_id contains cells.
      """
      aq_self = aq_base(self)

      if getattr(aq_self, 'index', None) is None:
        return 0

      if not self.index.has_key(base_id):
        return 0

      for i in self.getCellIds(base_id=base_id):
        if hasattr(self, i): # We should try to use aq_self if possible but XXX
          return 1

      return 0

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasInRange' )
    def hasInRange(self, *kw , **kwd):
      """
          Checks if *kw coordinates are in the range of the
        matrix in kwd['base_id'].
      """
      if getattr(aq_base(self), 'index', None) is None:
        return 0

      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return 0
      base_item = self.index[base_id]
      for i, my_id in enumerate(kw):
        if not base_item.has_key(i) or not base_item[i].has_key(my_id):
          return 0

      return 1

    security.declareProtected( Permissions.ModifyPortalContent,
                               '_setCellRange' )
    def _setCellRange(self, *kw, **kwd):
      """
          Set a new range for a matrix.
          If needed, it will resize and/or reorder matrix content.
      """
      movement = {} # Maps original cell id to its new id for each moved cell.
      new_index = PersistentMapping()

      base_id = kwd.get('base_id', 'cell')
      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()

      # Return if previous range is the same
      current_range = self.getCellRange(base_id=base_id)
      if current_range == list(kw): # kw is a tuple
        return

      # Create the new index for the range given in *kw
      # *kw practical example:
      # kw = [ ['color/blue', 'color/red'], ['size/S', 'size/L']]
      for i, index_ids in enumerate(kw):
        temp = PersistentMapping()
        for j, my_id in enumerate(index_ids):
          temp[my_id] = j
        new_index[i] = temp

      if self.index.has_key(base_id):
        # Compute cell movement from their position in previous range to their
        # position in the new range.
        for i, i_value in self.index[base_id].iteritems():
          if new_index.has_key(i):
            temp = {}
            for my_id, my_value in i_value.iteritems():
              temp[my_value] = new_index[i].get(my_id)
            movement[i] = temp

      # List all valid cell ids for current base_id.
      object_id_list = []
      for obj in self.objectValues():
        object_id = obj.getId()
        if object_id.find(base_id) == 0:
          # Check that all '_'-separated fields are of int type.
          if (object_id) > len(base_id):
            try:
              int(object_id[len(base_id)+1:].split('_')[0])
              test = self._getOb(object_id) # If the object was created
                                            # during this transaction,
                                            # then we do not need to
                                            # work on it
              object_id_list.append(object_id)
            except (ValueError, KeyError):
              pass

      # Prepend 'temp_' to all cells, to avoid id conflicts while renaming.
      for object_id in object_id_list:
        new_name = 'temp_' + object_id
        obj = self._getOb(object_id)
        self._delObject(object_id)
        obj.id = new_name
        self._setObject(new_name, aq_base(obj))

      # Rename all cells to their final name.
      for object_id in object_id_list:
        # Retrieve the place of the object, for movement_0_0 it is ['0','0']
        object_place = object_id[len(base_id)+1:].split('_')
        to_delete = 1
        # We must have the same number of dimensions
        if len(object_place) == len(new_index):
          # Let us browse each dimension of the previous index
          for i in range(len(object_place)):
            # Build each part of the nex id by looking up int. values
            old_place = int(object_place[i])
            # We are looking inside the movement dictionnary where
            # we should move the object, so for example
            # 'qantity_2_5' is renamed as 'quantity_4_3'
            if movement.has_key(i):
              if movement[i].has_key(old_place):
                # Replace the place of the cell only if there where a change
                if (movement[i][old_place]) != None:
                  object_place[i] = str(movement[i][old_place])
                  to_delete = 0
                else:
                  object_place[i] = None

            # XXX In this case, we delete every cell wich are not in the
            # movement dictionnary, may be this is very bad, so may be
            # we need to add an extra check here, ie if
            # if movement[i].has_key(old_place) returns None,
            # We may want to keep the cell, but only if we are sure
            # the movement dictionnary will not define a new cell with this id

        new_object_id_list = []

        temp_object_id = 'temp_' + object_id
        o = self._getOb(temp_object_id)
        if not to_delete and not (None in object_place):
          self._delObject(temp_object_id) # In all cases, we have
                                          # to remove the temp object
          object_place.insert(0, base_id)
          new_name = '_'.join(object_place)
          o.id = new_name
          new_object_id_list.extend(new_name)
          self._setObject(new_name, aq_base(o))

          if new_name != object_id:
            # Theses two lines are very important, if the object is renamed
            # then we must uncatalog the previous one
            o.unindexObject(path='%s/%s' % (self.getUrl() , object_id))
            # Force a new uid to be allocated, because unindexObject creates
            # an activity which will later delete lines from catalog based
            # on their uid, and it is not garanted that indexation will happen
            # after this deletion.
            # It is bad to waste uids, but this data structure offers no
            # alternative because cell id gives its index in the matrix,
            # so reordering axes requires the cell id to change.
            # XXX: It can be improved, but requires most of this file to be
            # rewritten, and compatibility code must be written as data
            # structure would most probably change.
            o.uid = None
          o.reindexObject() # we reindex in case position has changed
                            # uid should be consistent
        else:
          # In all cases, we have to remove the temp object
          #WARNING -> if path is not good, it will not be able to uncatalog !!!
          #o.immediateReindexObject() # STILL A PROBLEM -> getUidForPath XXX

          if object_id not in new_object_id_list: # do not unindex a new object
            o.isIndexable = ConstantGetter('isIndexable', value=True)
            o.unindexObject(path='%s/%s' % (self.getUrl() , object_id))
            # unindexed already forced
            o.isIndexable = ConstantGetter('isIndexable', value=False)
          self._delObject(temp_object_id) # object will be removed
                                               # from catalog automaticaly
      # We don't need the old index any more, we
      # can set the new index
      self.index[base_id] = new_index

    security.declareProtected( Permissions.ModifyPortalContent, 'setCellRange' )
    def setCellRange(self, *kw, **kwd):
      """
          Update the matrix ranges using provided lists of indexes (kw).

          Any number of list can be provided
      """
      self._setCellRange(*kw, **kwd)
      self.reindexObject()

    security.declareProtected(Permissions.ModifyPortalContent,
                              '_updateCellRange')
    def _updateCellRange(self, base_id, **kw):
      """
        The asCellRange script is Portal Type dependent
        which is not the case with this kind of code
        a better implementation consists in defining asCellRange as a
        generic method at matrix level (OverridableMethod(portal_type))
        which lookups for script in class, meta_type and PT form
        interaction could be implemented with interaction workflow
        this method should be renamed updateCellRange or updateMatrixCellRange
        base_id is parameter of updateCellRange

        asCellRange scripts should be unified if possible
      """
      script = self._getTypeBasedMethod('asCellRange', **kw)
      if script is None:
        raise UnboundLocalError,\
               "Did not find cell range script for portal type: %r" %\
               self.getPortalType()
      cell_range = script(base_id=base_id, matrixbox=0, **kw)
      self._setCellRange(base_id=base_id, *cell_range)

    security.declareProtected(Permissions.ModifyPortalContent,
                              'updateCellRange')
    def updateCellRange(self, base_id='cell', **kw):
      """ same as _updateCellRange, but reindex the object. """
      self._updateCellRange(base_id=base_id, **kw)
      self.reindexObject()


    security.declareProtected( Permissions.ModifyPortalContent,
                               '_renameCellRange' )
    def _renameCellRange(self, *kw, **kwd):
      """
          Rename a range for a matrix, this method can
          also handle a changement of the size of a matrix
      """
      base_id = kwd.get('base_id', 'cell')

      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()

      # Return if previous range is the same
      current_range = self.getCellRange(base_id=base_id) or []
      if current_range == list(kw): # kw is a tuple
        LOG('XMLMatrix',0,'return form _setCellRange - no need to change range')
        return

      current_len = len(current_range)
      new_len = len(kw)
      len_delta = new_len - current_len

      # We must make sure the base_id exists
      # in the event of a matrix creation for example
      if not self.index.has_key(base_id):
        # Create an index for this base_id
        self.index[base_id] = PersistentMapping()

      cell_id_list = []
      for cell_id in self.getCellIdList(base_id = base_id):
        if self.get(cell_id) is not None:
          cell_id_list.append(cell_id)

      # First, delete all cells which are out of range.
      size_list = map(len, kw)
      if len_delta < 0:
        size_list.extend([1] * (-len_delta))
      def is_in_range(cell_id):
        for i, index in enumerate(cell_id[len(base_id)+1:].split('_')):
          if int(index) >= size_list[i]:
            self._delObject(cell_id)
            return False
        return True
      cell_id_list = filter(is_in_range, cell_id_list)

      # Secondly, rename coordinates. This does not change cell ids.
      for i in range(max(new_len, current_len)):
        if i >= new_len:
          del self.index[base_id][i]
        else:
          if i >= current_len:
            self.index[base_id][i] = PersistentMapping()
          for place in self.index[base_id][i].keys():
            if place not in kw[i]:
              del self.index[base_id][i][place]

          for j, place in enumerate(kw[i]):
            self.index[base_id][i][place] = j

      # Lastly, rename ids and catalog/uncatalog everything.
      if len_delta > 0:
        # Need to move, say, base_1_2 -> base_1_2_0
        appended_id = '_0' * len_delta
        for old_id in cell_id_list:
          cell = self.get(old_id)
          if cell is not None:
            new_id = old_id + appended_id
            self._delObject(old_id)
            cell.isIndexable = ConstantGetter('isIndexable', value=False)
            cell.id = new_id
            self._setObject(new_id, aq_base(cell))
            cell.isIndexable = ConstantGetter('isIndexable', value=True)
            cell.reindexObject()
            #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))
      elif len_delta < 0:
        # Need to move, say, base_1_2_0 -> base_1_2
        removed_id_len = 2 * (-len_delta)
        for old_id in cell_id_list:
          cell = self.get(old_id)
          if cell is not None:
            new_id = old_id[:-removed_id_len]
            self._delObject(old_id)
            cell.isIndexable = ConstantGetter('isIndexable', value=False)
            cell.id = new_id
            self._setObject(new_id, aq_base(cell))
            cell.isIndexable = ConstantGetter('isIndexable', value=True)
            cell.reindexObject()
            #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))

    security.declareProtected( Permissions.ModifyPortalContent,
                               'renameCellRange' )
    def renameCellRange(self, *kw, **kwd):
      """
          Update the matrix ranges using provided lists of indexes (kw).
          This keep cell values if we add/remove dimensions
          Any number of list can be provided
      """
      self._renameCellRange(*kw, **kwd)
      self.reindexObject()

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellRange')
    def getCellRange(self, base_id='cell'):
      """
          Returns the cell range as a list of index ids
      """
      if getattr(aq_base(self), 'index', None) is None:
        return []
      cell_range = self.index.get(base_id, None)
      if cell_range is None:
        return None

      result = []
      for value in cell_range.itervalues():
        result_items = sorted(value.iteritems(), key=lambda x:x[1])
        result.append([x[0] for x in result_items])
      return result

    security.declareProtected( Permissions.ModifyPortalContent, 'newCell' )
    def newCell(self, *kw, **kwd):
      """
          This method creates a new cell
      """
      if getattr(aq_base(self), 'index', None) is None:
        return None
      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return None

      cell_id = self.keyToId(kw, base_id = base_id)
      if cell_id is None:
        raise KeyError, 'Invalid key: %s' % str(kw)

      cell = self.get(cell_id)
      if cell is not None:
        return cell
      else:
        return self.newCellContent(cell_id,**kwd)

    security.declareProtected( Permissions.ModifyPortalContent, 'newCellContent' )
    def newCellContent(self, cell_id, portal_type=None, **kw):
      """
        Creates a new content as a cell. This method is
        meant to be overriden by subclasses.
      """
      if portal_type is None:
        for x in self.allowedContentTypes():
          portal_type_id = x.getId()
          if portal_type_id.endswith(' Cell'):
            portal_type = portal_type_id
            break

      return self.newContent(id=cell_id, portal_type=portal_type, **kw)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellKeyList' )
    def getCellKeyList(self, base_id = 'cell'):
      """
        Returns a list of possible keys as tuples
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      if not self.index.has_key(base_id):
        return ()
      index = self.index[base_id]
      id_tuple = [v.keys() for v in index.itervalues()]
      if len(id_tuple) == 0:
        return ()
      return cartesianProduct(id_tuple)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellKeys' )
    getCellKeys = getCellKeyList

    # We should differenciate in future existing tuples from possible tuples
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellRangeKeyList' )
    getCellRangeKeyList = getCellKeyList

    security.declareProtected( Permissions.AccessContentsInformation, 'keyToId' )
    def keyToId(self, kw, base_id = 'cell'):
      """
        Converts a key into a cell id
      """
      index = self.index[base_id]
      cell_id_list = [base_id]
      append = cell_id_list.append
      for i, item in enumerate(kw):
        try:
          append(str(index[i][item]))
        except KeyError:
          return None
      return '_'.join(cell_id_list)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellIdList' )
    def getCellIdList(self, base_id = 'cell'):
      """
        Returns a list of possible ids as tuples
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      if not self.index.has_key(base_id):
        return ()
      result = []
      append = result.append
      for kw in self.getCellKeys(base_id = base_id):
        cell_id = self.keyToId(kw, base_id = base_id )
        if cell_id is not None:
          append(cell_id)

      return result

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellIds' )
    getCellIds = getCellIdList

    security.declareProtected( Permissions.AccessContentsInformation, 'cellIds' )
    cellIds = getCellIdList

    # We should differenciate in future all possible ids for existing ids
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellRangeIdList' )
    getCellRangeIdList = getCellIdList

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellValueList' )
    def getCellValueList(self, base_id = 'cell'):
      """
        Returns a list of cell values as tuples
      """
      result = []
      append = result.append
      for id in self.getCellIdList(base_id=base_id):
        o = self.get(id)
        if o is not None:
          append(o)
      return result

    security.declareProtected( Permissions.AccessContentsInformation,
                               'cellValues' )
    cellValues = getCellValueList

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getMatrixList' )
    def getMatrixList(self):
      """
        Return possible base_id values
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      return self.index.keys()

    security.declareProtected( Permissions.ModifyPortalContent, 'delMatrix' )
    def delMatrix(self, base_id = 'cell'):
      """
        Delete all cells for a given base_id

        XXX BAD NAME: make a difference between deleting matrix and matrix cells
      """
      ids = self.getCellIds(base_id = base_id)
      my_ids = []
      append = my_ids.append
      for i in self.objectIds():
        if i in ids:
          append(i)

      if len(my_ids) > 0:
        self.manage_delObjects(ids=my_ids)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'delCells' )
    delCells = delMatrix

    security.declareProtected( Permissions.AccessContentsInformation,
                               '_checkConsistency' )
    def _checkConsistency(self, fixit=0):
      """
        Constraint API.
      """
      # Check useless cells
      to_delete_set = set()
      error_list = []
      def addError(error_message):
        if fixit:
          error_message += ' (fixed)'
        error = (self.getRelativeUrl(),
                 'XMLMatrix inconsistency',
                 102,
                 error_message)

        error_list.append(error)

      # We make sure first that there is an index
      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()
      # We will check each cell of the matrix the matrix

      # XXX This code assumes the following predicate:
      #   each subobject of an XMLMatrix is either a Cell that needs
      #   consistency checking OR ( is not a Cell, and has an id that is
      #   not like "(\w+_)+(\d+_)*\d+" )
      # But Documents inheriting XMLMatrix can have unrelated, non-cell
      # subobjects, possibly with id looking like some_id_2. If it ever happens,
      # an error will be wrongly raised.
      for obj in self.objectValues():
        object_id = obj.getId()
        # obect_id is equal to something like 'something_quantity_3_2'
        # So we need to check for every object.id if the value
        # looks like good or not. We split the name
        # check each key in index
        # First we make sure this is a cell
        object_id_split = object_id.split('_')

        base_id = None
        cell_coordinate_list = []
        while object_id_split:
          coordinate = None
          try:
            coordinate = int(object_id_split[-1])
          except ValueError:
            # The last item is not a coordinate, object_id_split hence
            # only contains the base_id elements
            base_id = '_'.join(object_id_split)
            break
          else:
            cell_coordinate_list.insert(0, coordinate)
            # the last item is a coordinate not part of base_id
            object_id_split.pop()

        current_dimension = len(cell_coordinate_list)
        if current_dimension > 0 and base_id is not None:
            if not self.index.has_key(base_id):
              # The matrix does not have this base_id
              addError("There is no index for base_id %s" % base_id)
              to_delete_set.add(object_id)
              continue

            # Check empty indices.
            empty_list = []
            base_item = self.index[base_id]
            for key, value in base_item.iteritems():
              if value is None or len(value) == 0:
                addError("There is no id for the %dth axis of base_id %s" % (key, base_id))
                empty_list.append(key)
            if fixit:
              for i in empty_list:
                del base_item[key]

            len_id = len(base_item)
            if current_dimension != len_id:
              addError("Dimension of cell is %s but should be %s" % (current_dimension,
                                                                     len_id))
              to_delete_set.add(object_id)
            else :
              for i, coordinate in enumerate(cell_coordinate_list):
                if coordinate >= len(base_item[i]):
                  addError("Cell %s is out of bound" % object_id)
                  to_delete_set.add(object_id)
                  break

      if fixit and len(to_delete_set) > 0:
        self.manage_delObjects(list(to_delete_set))

      return error_list

    security.declareProtected( Permissions.ModifyPortalContent, 'notifyAfterUpdateRelatedContent' )
    def notifyAfterUpdateRelatedContent(self, previous_category_url, new_category_url):
      """
        We must do some matrix range update in the event matrix range
        is defined by a category
      """
      LOG('XMLMatrix notifyAfterUpdateRelatedContent', 0, str(new_category_url))
      update_method = self.portal_categories.updateRelatedCategory
      for base_id in self.getMatrixList():
        cell_range = self.getCellRange(base_id=base_id)
        new_cell_range = []
        for range_item_list in cell_range:
          new_range_item_list = map(lambda c: update_method(c, previous_category_url, new_category_url), range_item_list)
          new_cell_range.append(new_range_item_list)
        kwd = {'base_id': base_id}
        LOG('XMLMatrix notifyAfterUpdateRelatedContent matrix', 0, str(base_id))
        LOG('XMLMatrix notifyAfterUpdateRelatedContent _renameCellRange', 0, str(new_cell_range))
        self._renameCellRange(*new_cell_range,**kwd)
コード例 #5
0
class Predicate(XMLObject):
  """
    A Predicate object defines a list of criterions
    which can be applied to test a document or to search for documents.

    Predicates are defined by a combination of PropertySheet values
    (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
    script based test.

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

    The approach is intended to provide the expressivity of a rule
    based system without the burden of building a fully expressive
    rule engine.
  """
  meta_type = 'ERP5 Predicate'
  portal_type = 'Predicate'
  add_permission = Permissions.AddPortalContent
  isPredicate = ConstantGetter('isPredicate', value=True)

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.Predicate
                    , PropertySheet.CategoryCore
                    , PropertySheet.SortIndex
                    )

  # Declarative interfaces
  zope.interface.implements( interfaces.IPredicate, )

  security.declareProtected( Permissions.AccessContentsInformation, 'test' )
  def test(self, context, tested_base_category_list=None,
           strict_membership=0, isMemberOf=None, **kw):
    """
      A Predicate can be tested on a given context.
      Parameters can passed in order to ignore some conditions.

      - tested_base_category_list:  this is the list of category that we do
        want to test. For example, we might want to test only the
        destination or the source of a predicate.
      - if strict_membership is specified, we should make sure that we
        are strictly a member of tested categories
      - isMemberOf can be a function caching results for
        CategoryTool.isMemberOf: it is always called with given 'context' and
        'strict_membership' values, and different categories.
    """
    self = self.asPredicate()
    if self is None:
      # asPredicate returned None, so this predicate never applies.
      # But if we reach this it is because catalog is not up to date.
      return False

    result = 1
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
#    LOG('PREDICATE TEST', 0,
#        'testing %s on context of %s' % \
#        (self.getRelativeUrl(), context.getRelativeUrl()))
    for property, value in self._identity_criterion.iteritems():
      if isinstance(value, (list, tuple)):
        result = context.getProperty(property) in value
      else:
        result = context.getProperty(property) == value
#      LOG('predicate test', 0,
#          '%s after prop %s : %s == %s' % \
#          (result, property, context.getProperty(property), value))
      if not result:
        return result
    for property, (min, max) in self._range_criterion.iteritems():
      value = context.getProperty(property)
      if min is not None:
        result = value >= min
#        LOG('predicate test', 0,
#            '%s after prop %s : %s >= %s' % \
#            (result, property, value, min))
        if not result:
          return result
      if max is not None:
        result = value < max
#        LOG('predicate test', 0,
#            '%s after prop %s : %s < %s' % \
#            (result, property, value, max))
        if not result:
          return result
    # Test category memberships. Enable the read-only transaction cache
    # because this part is strictly read-only, and context.isMemberOf
    # is very expensive when the category list has many items.
    membership_criterion_category_list = self.getMembershipCriterionCategoryList()
    if membership_criterion_category_list:
      multimembership_criterion_base_category_list = \
        self.getMultimembershipCriterionBaseCategoryList()
      membership_criterion_base_category_list = \
        self.getMembershipCriterionBaseCategoryList()
      tested_base_category = {}
#      LOG('predicate test', 0,
#          'categories will be tested in multi %s single %s as %s' % \
#         (multimembership_criterion_base_category_list,
#         membership_criterion_base_category_list,
#         self.getMembershipCriterionCategoryList()))
      if isMemberOf is None:
        isMemberOf = context._getCategoryTool().isMemberOf
      with readOnlyTransactionCache():
        for c in membership_criterion_category_list:
          bc = c.split('/', 1)[0]
          if tested_base_category_list is None or bc in tested_base_category_list:
            if bc in multimembership_criterion_base_category_list:
              if not isMemberOf(context, c, strict_membership=strict_membership):
                return 0
            elif bc in membership_criterion_base_category_list and \
                 not tested_base_category.get(bc):
              tested_base_category[bc] = \
                isMemberOf(context, c, strict_membership=strict_membership)
      if 0 in tested_base_category.itervalues():
        return 0

    # Test method calls
    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:
          method = getattr(context,test_method_id)
          try:
            result = method(self)
          except TypeError:
            if method.func_code.co_argcount != isinstance(method, MethodType):
              raise
            # backward compatibilty with script that takes no argument
            warn('Predicate %s uses an old-style method (%s) that does not'
                 ' take the predicate as argument' % (
               self.getRelativeUrl(), method.__name__), DeprecationWarning)
            result = method()
#          LOG('predicate test', 0,
#              '%s after method %s ' % (result, test_method_id))
          if not result:
            return result
    test_tales_expression = self.getTestTalesExpression()
    if test_tales_expression != 'python: True':
      expression = Expression(test_tales_expression)
      from Products.ERP5Type.Utils import createExpressionContext
      # evaluate a tales expression with the tested value as context
      result = expression(createExpressionContext(context))
    return result

  security.declareProtected(Permissions.AccessContentsInformation, 'asQuery')
  def asQuery(self, strict_membership=False):
    """
    A Predicate can be rendered as a set of catalog conditions. This
    can be useful to create reporting trees based on the
    ZSQLCatalog. This condition set is however partial since
    python scripts which are used by the test method of the predicate
    cannot be converted to catalog conditions. If a python script is defined to
    implement test, results obtained through asQuery must be additionnaly
    tested by invoking test().
    """
    portal_catalog = self.getPortalObject().portal_catalog
    buildSingleQuery = portal_catalog.getSQLCatalog().buildSingleQuery
    getCategoryParameterDict = portal_catalog.getCategoryParameterDict
    def filterCategoryList(base_category_set, category_list):
      return [
        x for x in category_list
        if x.split('/', 1)[0] in base_category_set
      ]
    next_join_counter = itertools.count().next
    def buildSeparateJoinQuery(name, value):
      query = buildSingleQuery(name, value)
      suffix = str(next_join_counter())
      # XXX: using a deprecated API and accessing properties which are not part
      # of the API. Of course this will never break !
      query.setTableAliasList([
        (x, x + suffix) for x in query.search_key.table_list
      ])
      return query

    query_list = []
    append = query_list.append

    for buildQuery, getBaseCategorySet, getCategoryList in (
      ( # Single-membership criterion
        lambda name, value: buildSingleQuery(name, value),
        self.getMembershipCriterionBaseCategoryList,
        self.getMembershipCriterionCategoryList,
      ),
      ( # Multi-membership criterion
        buildSeparateJoinQuery,
        self.getMultimembershipCriterionBaseCategoryList,
        self.getMembershipCriterionCategoryList,
      ),
    ):
      append(
        getCategoryParameterDict(
          filterCategoryList(getBaseCategorySet(), getCategoryList()),
          strict_membership=strict_membership,
          onMissing=lambda category: False,
        ),
      )

    # Value criterion
    for criterion in self.getCriterionList():
      if not criterion.min and not criterion.max:
        append(buildSingleQuery(criterion.property, criterion.identity))
        continue
      if criterion.min:
        append(SimpleQuery(
          comparison_operator='>=',
          **{criterion.property: criterion.min}
        ))
      if criterion.max:
        append(SimpleQuery(
          comparison_operator='<=',
          **{criterion.property: criterion.max}
        ))

    if query_list:
      return ComplexQuery(query_list, logical_operator='AND')
    elif not getattr(self, 'isEmptyCriterionValid', lambda: True)():
      # By catalog definition, no object has uid 0, so this condition forces an
      # empty result.
      return SimpleQuery(uid=0)
    return SimpleQuery(uid=0, comparison_operator='>')

  security.declareProtected(Permissions.AccessContentsInformation, 'searchResults')
  def searchResults(self, **kw):
    """
    """
    return self.getPortalObject().portal_catalog.searchResults(
      predicate_internal_query=self.asQuery(),
      **kw
    )

  security.declareProtected(Permissions.AccessContentsInformation, 'countResults')
  def countResults(self, REQUEST=None, used=None, **kw):
    """
    """
    return self.getPortalObject().portal_catalog.countResults(
      predicate_internal_query=self.asQuery(),
      **kw
    )

  security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' )
  def getCriterionList(self, **kw):
    """
      Returns the list of criteria which are defined by the Predicate.

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

      XXX - It would be better to return criteria in a Criterion class
            instance
    """
    # We do not create PersistentMappings first time we *see* Predicate_view.
    # Instead, we create them first time we modify Predicate document.
    if not self.getCriterionPropertyList():
      return []
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
    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):
    """
      This methods sets parameters of a criterion. There is at most one
      criterion per property. Defined parameters are

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

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

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

    """
    # XXX 'min' and 'max' are built-in functions.
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
    if identity is not None :
      self._identity_criterion[property] = identity
    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:
      self._range_criterion[property] = (min, max)
    self.reindexObject()

  security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
  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.
    """
    if getattr(aq_base(self), '_identity_criterion', None) is None:
      self._identity_criterion = PersistentMapping()
      self._range_criterion = PersistentMapping()
    if 'criterion_property_list' in kwd:
      criterion_property_list = kwd['criterion_property_list']
      identity_criterion = PersistentMapping()
      range_criterion = PersistentMapping()
      for criterion in self._identity_criterion.iterkeys() :
        if criterion in criterion_property_list :
          identity_criterion[criterion] = self._identity_criterion[criterion]
      for criterion in self._range_criterion.iterkeys() :
        if criterion in criterion_property_list :
          range_criterion[criterion] = self._range_criterion[criterion]
      self._identity_criterion = identity_criterion
      self._range_criterion = range_criterion
    kwd['reindex_object'] = 1
    return self._edit(**kwd)

  # Predicate fusion method
  security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' )
  def setPredicateCategoryList(self, category_list):
    """
      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.

      WARNING: this method does not take into account scripts at
      this point.
    """
    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 = []
    # reset criterions
    self._identity_criterion = PersistentMapping()
    self._range_criterion = PersistentMapping()

    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)
    self._setTestMethodIdList(test_method_id_list)
    self.reindexObject()

  security.declareProtected(Permissions.AccessContentsInformation, 'generatePredicate')
  def generatePredicate(self, multimembership_criterion_base_category_list=(),
                        membership_criterion_base_category_list=(),
                        criterion_property_list=(),
                        identity_criterion=None,
                        range_criterion=None,):
    """
    This method generates a new temporary predicate based on an ad-hoc
    interpretation of local properties of an object. For example,
    a start_range_min property will be interpreted as a way to define
    a min criterion on start_date.

    The purpose of this method is to be called from
    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.
    """
    new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList())
    new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList())
    new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList())

    for base_category in multimembership_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)
        if base_category not in new_multimembership_criterion_base_category_list:
          new_multimembership_criterion_base_category_list.append(base_category)

    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)
        if base_category not in new_membership_criterion_base_category_list:
          new_membership_criterion_base_category_list.append(base_category)

    new_criterion_property_list =  list(self.getCriterionPropertyList())

    # We need to build new criteria for asContext, and we should not
    # modify the original, so we always make copies. Since the usage is
    # temporary, use dicts instead of persistent mappings.
    new_identity_criterion = dict(getattr(self, '_identity_criterion', None) or
                                  {})
    new_identity_criterion.update(identity_criterion or {})
    new_range_criterion = dict(getattr(self, '_range_criterion', None) or {})
    new_range_criterion.update(range_criterion or {})

    # Look at local properties and make it criterion properties
    for property in criterion_property_list:
      if property not in self.getCriterionPropertyList() \
        and property in self.propertyIds():
          new_criterion_property_list.append(property)
          property_min = property + '_range_min'
          property_max = property + '_range_max'
          if getattr(self, 'get%s' % convertToUpperCase(property), None) is not None\
            and self.getProperty(property) is not None:
            new_identity_criterion[property] = self.getProperty(property)
          elif getattr(self, 'get%s' % convertToUpperCase(property_min), None) is not None:
            min = self.getProperty(property_min)
            max = self.getProperty(property_max)
            new_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(
        membership_criterion_category=new_membership_criterion_category_list,
        membership_criterion_base_category=new_membership_criterion_base_category_list,
        multimembership_criterion_base_category=new_multimembership_criterion_base_category_list,
        criterion_property_list=new_criterion_property_list,
        _identity_criterion=new_identity_criterion,
        _range_criterion=new_range_criterion)

    return new_self

  security.declareProtected(Permissions.AccessContentsInformation,
                            'asPredicate')
  def asPredicate(self):
    """
      This method tries to convert the current Document into a predicate
      looking up methods named Class_asPredicate, MetaType_asPredicate, PortalType_asPredicate
    """
    cache = getTransactionalVariable()
    key = 'asPredicate', self
    try:
      return cache[key]
    except KeyError:
      self = unrestricted_apply(self._getTypeBasedMethod("asPredicate", "_asPredicate"))
      cache[key] = self
      return self

  def _asPredicate(self):
    return self

  security.declareProtected(Permissions.AccessContentsInformation, 'searchPredicate')
  def searchPredicate(self, **kw):
    """
      Returns a list of documents matching the predicate

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

  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))
コード例 #6
0
ファイル: PasswordTool.py プロジェクト: smetsjp/erp5
class PasswordTool(BaseTool):
  """
    PasswordTool is used to allow a user to change its password
  """
  title = 'Password Tool'
  id = 'portal_password'
  meta_type = 'ERP5 Password Tool'
  portal_type = 'Password Tool'
  allowed_types = ()

  # Declarative Security
  security = ClassSecurityInfo()

  security.declareProtected(Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainPasswordTool', _dtmldir )


  _expiration_day = 1
  _password_request_dict = {}

  def __init__(self, id=None):
    if id is None:
      id = self.__class__.id
    self._password_request_dict = PersistentMapping()
    # XXX no call to BaseTool.__init__ ?
    # BaseTool.__init__(self, id)

  security.declareProtected('Manage users', 'getResetPasswordKey')
  def getResetPasswordKey(self, user_login):
    # generate expiration date
    expiration_date = DateTime() + self._expiration_day

    # generate a random string
    key = self._generateUUID()
    # XXX before r26093, _password_request_dict was initialized by an OOBTree and
    # replaced by a dict on each request, so if it's data structure is not up
    # to date, we update it if needed
    if not isinstance(self._password_request_dict, PersistentMapping):
      LOG('ERP5.PasswordTool', INFO, 'Updating password_request_dict to'
                                     ' PersistentMapping')
      self._password_request_dict = PersistentMapping()

    # register request
    self._password_request_dict[key] = (user_login, expiration_date)
    return key

  security.declareProtected('Manage users', 'getResetPasswordUrl')
  def getResetPasswordUrl(self, user_login=None, key=None, site_url=None):
    if user_login is not None:
      # XXX Backward compatibility
      key = self.getResetPasswordKey(user_login)

    parameter = urlencode(dict(reset_key=key))
    method = self._getTypeBasedMethod("getSiteUrl")
    if method is not None:
      base_url = method()
    else:
      base_url = "%s/portal_password/PasswordTool_viewResetPassword" % (
        site_url,)
    url = "%s?%s" %(base_url, parameter)
    return url

  security.declareProtected('Manage users', 'getResetPasswordUrl')
  def getExpirationDateForKey(self, key=None):
    return self._password_request_dict[key][1]


  def mailPasswordResetRequest(self, user_login=None, REQUEST=None,
                              notification_message=None, sender=None,
                              store_as_event=False):
    """
    Create a random string and expiration date for request
    Parameters:
    user_login -- Reference of the user to send password reset link
    REQUEST -- Request object
    notification_message -- Notification Message Document used to build the email.
                            As default, a standart text will be used.
    sender -- Sender (Person or Organisation) of the email.
            As default, the default email address will be used
    store_as_event -- whenever CRM is available, store
                        notifications as events
    """
    if REQUEST is None:
      REQUEST = get_request()

    if user_login is None:
      user_login = REQUEST["user_login"]

    site_url = self.getPortalObject().absolute_url()
    if REQUEST and 'came_from' in REQUEST:
      site_url = REQUEST.came_from

    msg = None
    # check user exists, and have an email
    user_list = self.getPortalObject().acl_users.\
                      erp5_users.getUserByLogin(user_login)
    if len(user_list) == 0:
      msg = translateString("User ${user} does not exist.",
                            mapping={'user':user_login})
    else:
      # We use checked_permission to prevent errors when trying to acquire
      # email from organisation
      user = user_list[0]
      email_value = user.getDefaultEmailValue(
              checked_permission='Access content information')
      if email_value is None or not email_value.asText():
        msg = translateString(
            "User ${user} does not have an email address, please contact site "
            "administrator directly", mapping={'user':user_login})
    if msg:
      if REQUEST is not None:
        parameter = urlencode(dict(portal_status_message=msg))
        ret_url = '%s/login_form?%s' % \
                  (site_url, parameter)
        return REQUEST.RESPONSE.redirect( ret_url )
      return msg

    key = self.getResetPasswordKey(user_login=user_login)
    url = self.getResetPasswordUrl(key=key, site_url=site_url)

    # send mail
    message_dict = {'instance_name':self.getPortalObject().getTitle(),
                    'reset_password_link':url,
                    'expiration_date':self.getExpirationDateForKey(key)}

    if notification_message is None:
      subject = translateString("[${instance_name}] Reset of your password",
          mapping={'instance_name': self.getPortalObject().getTitle()})
      subject = subject.translate()
      message = translateString("\nYou requested to reset your ${instance_name}"\
                " account password.\n\n" \
                "Please copy and paste the following link into your browser: \n"\
                "${reset_password_link}\n\n" \
                "Please note that this link will be valid only one time, until "\
                "${expiration_date}.\n" \
                "After this date, or after having used this link, you will have to make " \
                "a new request\n\n" \
                "Thank you",
                mapping=message_dict)
      message = message.translate()
    else:
      subject = notification_message.getTitle()
      if notification_message.getContentType() == "text/html":
        message = notification_message.asEntireHTML(substitution_method_parameter_dict=message_dict)
      else:
        message = notification_message.asText(substitution_method_parameter_dict=message_dict)

    self.getPortalObject().portal_notifications.sendMessage(sender=sender, recipient=[user,],
                                                            subject=subject, message=message,
                                                            store_as_event=store_as_event)
    if REQUEST is not None:
      msg = translateString("An email has been sent to you.")
      parameter = urlencode(dict(portal_status_message=msg))
      ret_url = '%s/login_form?%s' % (site_url, parameter)
      return REQUEST.RESPONSE.redirect( ret_url )

  def _generateUUID(self, args=""):
    """
    Generate a unique id that will be used as url for password
    """
    # this code is based on
    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/213761
    # by Carl Free Jr
    # as uuid module is only available in pyhton 2.5
    t = long( time.time() * 1000 )
    r = long( random.random()*100000000000000000L )
    try:
      a = socket.gethostbyname( socket.gethostname() )
    except:
      # if we can't get a network address, just imagine one
      a = random.random()*100000000000000000L
    data = ' '.join((str(t), str(r), str(a), str(args)))
    data = md5_new(data).hexdigest()
    return data


  def resetPassword(self, reset_key=None, REQUEST=None):
    """
    """
    # XXX-Aurel : is it used ?
    if REQUEST is None:
      REQUEST = get_request()
    user_login, expiration_date = self._password_request_dict.get(reset_key, (None, None))
    site_url = self.getPortalObject().absolute_url()
    if REQUEST and 'came_from' in REQUEST:
      site_url = REQUEST.came_from
    if reset_key is None or user_login is None:
      ret_url = '%s/login_form' % site_url
      return REQUEST.RESPONSE.redirect( ret_url )

    # check date
    current_date = DateTime()
    if current_date > expiration_date:
      msg = translateString("Date has expire.")
      parameter = urlencode(dict(portal_status_message=msg))
      ret_url = '%s/login_form?%s' % (site_url, parameter)
      return REQUEST.RESPONSE.redirect( ret_url )

    # redirect to form as all is ok
    REQUEST.set("password_key", reset_key)
    return self.reset_password_form(REQUEST=REQUEST)


  def removeExpiredRequests(self, **kw):
    """
    Browse dict and remove expired request
    """
    current_date = DateTime()
    for key, (login, date) in self._password_request_dict.items():
      if date < current_date:
        self._password_request_dict.pop(key)


  def changeUserPassword(self, password, password_key, password_confirm=None,
                         user_login=None, REQUEST=None, **kw):
    """
    Reset the password for a given login
    """
    # check the key
    register_user_login, expiration_date = self._password_request_dict.get(
                                                    password_key, (None, None))

    current_date = DateTime()
    msg = None
    if REQUEST is None:
      REQUEST = get_request()
    site_url = self.getPortalObject().absolute_url()
    if REQUEST and 'came_from' in REQUEST:
      site_url = REQUEST.came_from
    if self.getWebSiteValue():
      site_url = self.getWebSiteValue().absolute_url()
    if register_user_login is None:
      msg = "Key not known. Please ask reset password."
    elif user_login is not None and register_user_login != user_login:
      msg = translateString("Bad login provided.")
    elif current_date > expiration_date:
      msg = translateString("Date has expire.")
    if msg is not None:
      if REQUEST is not None:
        parameter = urlencode(dict(portal_status_message=msg))
        ret_url = '%s/login_form?%s' % (site_url, parameter)
        return REQUEST.RESPONSE.redirect( ret_url )
      else:
        return msg

    # all is OK, change password and remove it from request dict
    self._password_request_dict.pop(password_key)
    persons = self.getPortalObject().acl_users.erp5_users.getUserByLogin(register_user_login)
    person = persons[0]
    person._forceSetPassword(password)
    person.reindexObject()
    if REQUEST is not None:
      msg = translateString("Password changed.")
      parameter = urlencode(dict(portal_status_message=msg))
      ret_url = '%s/login_form?%s' % (site_url, parameter)
      return REQUEST.RESPONSE.redirect( ret_url )