Commit f9081640 authored by Arnaud Fontaine's avatar Arnaud Fontaine

* Allow import of Filesystem Constraints to ZODB Constraints for

  Property Sheets and export of ZODB Constraints to Filesystem
  Constraints.
* Merge CategoryAcquiredExistenceConstraint into CategoryExistenceConstraint
  and CategoryAcquiredMembershipArityConstraint into 
  CategoryMembershipArityConstraint.
* Fix constraint_portal_type for CategoryExistenceConstraint and
  CategoryMembershipArityConstraint for ZODB Constraints which must be
  a TALES Expression, like filesystem Constraints.
* Add tests to check import and export of ZODB Constraints.



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@42929 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 611b7dcd
...@@ -32,7 +32,7 @@ from Products.ERP5Type import PropertySheet ...@@ -32,7 +32,7 @@ from Products.ERP5Type import PropertySheet
class AccountingTransactionBalanceConstraint(ConstraintMixin): class AccountingTransactionBalanceConstraint(ConstraintMixin):
""" """
Check that accounting transaction total debit and total credit are equals. Check that accounting transaction total debit and total credit are equal.
This is only relevant for ZODB Property Sheets (filesystem Property This is only relevant for ZODB Property Sheets (filesystem Property
Sheets rely on Products.ERP5.Constraint.AccountingTransactionBalance Sheets rely on Products.ERP5.Constraint.AccountingTransactionBalance
...@@ -41,6 +41,8 @@ class AccountingTransactionBalanceConstraint(ConstraintMixin): ...@@ -41,6 +41,8 @@ class AccountingTransactionBalanceConstraint(ConstraintMixin):
meta_type = 'ERP5 Accounting Transaction Balance Constraint' meta_type = 'ERP5 Accounting Transaction Balance Constraint'
portal_type = 'Accounting Transaction Balance Constraint' portal_type = 'Accounting Transaction Balance Constraint'
__compatibility_class_name__ = 'AccountingTransactionBalance'
property_sheets = ConstraintMixin.property_sheets + \ property_sheets = ConstraintMixin.property_sheets + \
(PropertySheet.AccountingTransactionBalanceConstraint,) (PropertySheet.AccountingTransactionBalanceConstraint,)
...@@ -89,3 +91,6 @@ class AccountingTransactionBalanceConstraint(ConstraintMixin): ...@@ -89,3 +91,6 @@ class AccountingTransactionBalanceConstraint(ConstraintMixin):
break break
return error_list return error_list
_message_id_tuple = ('message_transaction_not_balanced_for_source',
'message_transaction_not_balanced_for_destination')
...@@ -43,6 +43,8 @@ class ResourceMeasuresConsistencyConstraint(ConstraintMixin): ...@@ -43,6 +43,8 @@ class ResourceMeasuresConsistencyConstraint(ConstraintMixin):
meta_type = 'ERP5 Resource Measures Consistency Constraint' meta_type = 'ERP5 Resource Measures Consistency Constraint'
portal_type = 'Resource Measures Consistency Constraint' portal_type = 'Resource Measures Consistency Constraint'
__compatibility_class_name__ = 'ResourceMeasuresConsistency'
property_sheets = ConstraintMixin.property_sheets + \ property_sheets = ConstraintMixin.property_sheets + \
(PropertySheet.ResourceMeasuresConsistencyConstraint,) (PropertySheet.ResourceMeasuresConsistencyConstraint,)
...@@ -103,3 +105,9 @@ class ResourceMeasuresConsistencyConstraint(ConstraintMixin): ...@@ -103,3 +105,9 @@ class ResourceMeasuresConsistencyConstraint(ConstraintMixin):
error('message_missing_metric_type', metric_type=quantity) error('message_missing_metric_type', metric_type=quantity)
return error_list return error_list
_message_id_tuple = ('message_measure_no_quantity_unit',
'message_measure_no_quantity',
'message_duplicate_metric_type',
'message_duplicate_default_measure',
'message_missing_metric_type')
...@@ -42,6 +42,8 @@ class TradeModelLineCellConsistencyConstraint(ConstraintMixin): ...@@ -42,6 +42,8 @@ class TradeModelLineCellConsistencyConstraint(ConstraintMixin):
meta_type = 'ERP5 Trade Model Line Cell Consistency Constraint' meta_type = 'ERP5 Trade Model Line Cell Consistency Constraint'
portal_type = 'Trade Model Line Cell Consistency Constraint' portal_type = 'Trade Model Line Cell Consistency Constraint'
__compatibility_class_name__ = 'TradeModelLineCellConsistency'
property_sheets = ConstraintMixin.property_sheets + \ property_sheets = ConstraintMixin.property_sheets + \
(PropertySheet.TradeModelLineCellConsistencyConstraint,) (PropertySheet.TradeModelLineCellConsistencyConstraint,)
...@@ -57,3 +59,20 @@ class TradeModelLineCellConsistencyConstraint(ConstraintMixin): ...@@ -57,3 +59,20 @@ class TradeModelLineCellConsistencyConstraint(ConstraintMixin):
mapping=dict(line=document.getTitle()))] mapping=dict(line=document.getTitle()))]
return [] return []
_message_id_tuple = ('message_cell_inexistance',)
@staticmethod
def _convertFromFilesystemDefinition(base_id):
"""
@see ERP5Type.mixin.constraint.ConstraintMixin._convertFromFilesystemDefinition
"""
yield dict(base_id=base_id)
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(TradeModelLineCellConsistencyConstraint,
self).exportToFilesystemDefinitionDict()
filesystem_definition_dict['base_id'] = self.getBaseId()
return filesystem_definition_dict
...@@ -41,6 +41,8 @@ class TransactionQuantityValueFeasabilityConstraint(ConstraintMixin): ...@@ -41,6 +41,8 @@ class TransactionQuantityValueFeasabilityConstraint(ConstraintMixin):
meta_type = 'ERP5 Transaction Quantity Value Feasability Constraint' meta_type = 'ERP5 Transaction Quantity Value Feasability Constraint'
portal_type = 'Transaction Quantity Value Feasability Constraint' portal_type = 'Transaction Quantity Value Feasability Constraint'
__compatibility_class_name__ = 'TransactionQuantityValueFeasability'
def _checkConsistency(self, object, fixit=0): def _checkConsistency(self, object, fixit=0):
""" """
Check if the quantity of the transaction is possible Check if the quantity of the transaction is possible
......
...@@ -38,6 +38,8 @@ class TransactionQuantityValueValidityConstraint(ConstraintMixin): ...@@ -38,6 +38,8 @@ class TransactionQuantityValueValidityConstraint(ConstraintMixin):
meta_type = 'ERP5 Transaction Quantity Value Validity Constraint' meta_type = 'ERP5 Transaction Quantity Value Validity Constraint'
portal_type = 'Transaction Quantity Value Validity Constraint' portal_type = 'Transaction Quantity Value Validity Constraint'
__compatibility_class_name__ = 'TransactionQuantityValueValidity'
def _checkConsistency(self, object, fixit=0): def _checkConsistency(self, object, fixit=0):
""" """
Check if the quantity of the transaction is greater than the Check if the quantity of the transaction is greater than the
......
...@@ -35,6 +35,7 @@ from Products.ERP5Type.Core.PropertyExistenceConstraint import \ ...@@ -35,6 +35,7 @@ from Products.ERP5Type.Core.PropertyExistenceConstraint import \
from Products.ERP5Type.mixin.constraint import ConstraintMixin from Products.ERP5Type.mixin.constraint import ConstraintMixin
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet
from Products.CMFCore.Expression import Expression
class AttributeEqualityConstraint(PropertyExistenceConstraint): class AttributeEqualityConstraint(PropertyExistenceConstraint):
""" """
...@@ -58,6 +59,8 @@ class AttributeEqualityConstraint(PropertyExistenceConstraint): ...@@ -58,6 +59,8 @@ class AttributeEqualityConstraint(PropertyExistenceConstraint):
meta_type = 'ERP5 Attribute Equality Constraint' meta_type = 'ERP5 Attribute Equality Constraint'
portal_type = 'Attribute Equality Constraint' portal_type = 'Attribute Equality Constraint'
__compatibility_class_name__ = 'AttributeEquality'
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
...@@ -67,7 +70,7 @@ class AttributeEqualityConstraint(PropertyExistenceConstraint): ...@@ -67,7 +70,7 @@ class AttributeEqualityConstraint(PropertyExistenceConstraint):
def _checkConsistency(self, obj, fixit=False): def _checkConsistency(self, obj, fixit=False):
""" """
Check the object's consistency. Check the object's consistency
""" """
attribute_name = self.getConstraintAttributeName() attribute_name = self.getConstraintAttributeName()
...@@ -113,3 +116,32 @@ class AttributeEqualityConstraint(PropertyExistenceConstraint): ...@@ -113,3 +116,32 @@ class AttributeEqualityConstraint(PropertyExistenceConstraint):
return [error] return [error]
return [] return []
_message_id_tuple = ('message_property_not_set',
'message_invalid_attribute_value',
'message_invalid_attribute_value_fixed')
@staticmethod
def _convertFromFilesystemDefinition(**property_dict):
"""
@see ERP5Type.mixin.constraint.ConstraintMixin._convertFromFilesystemDefinition
One constraint per property is created. Filesystem definition example:
{ 'id' : 'title',
'description' : 'Title must be "ObjectTitle"',
'type' : 'AttributeEquality',
'title' : 'ObjectTitle',
'condition' : 'python: object.getPortalType() == 'Foo',
}
"""
for name, value in property_dict.iteritems():
yield dict(name=value)
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(AttributeEqualityConstraint,
self).exportToFilesystemDefinitionDict()
filesystem_definition_dict[self.getConstraintAttributeName()] = \
Expression(self.getConstraintAttributeValue())
return filesystem_definition_dict
##############################################################################
#
# Copyright (c) 2002-2010 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# Arnaud Fontaine <arnaud.fontaine@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.Core.CategoryMembershipArityConstraint \
import CategoryMembershipArityConstraint
class CategoryAcquiredMembershipArityConstraint(CategoryMembershipArityConstraint):
"""
This constraint checks if an object respects the arity with
Acquisition.
This is only relevant for ZODB Property Sheets (filesystem Property
Sheets rely on Products.ERP5Type.Constraint.CategoryAcquiredMembershipArity
instead).
For example, if we would like to check whether the object respects a
minimum arity of '1' and a maximum arity of '1 for the Portal Type
'Organisation' and the Base Category 'source', then we would create
a 'Category Acquired Membership Arity Constraint' within that
Property Sheet and set 'Minimum arity' to '1', 'Maximum Arity' to
'1', 'Portal Types' to 'Organisation', 'Base Categories' to
'source', then set the 'Predicate' if necessary (known as
'condition' for filesystem Property Sheets).
"""
meta_type = 'ERP5 Category Acquired Membership Arity Constraint'
portal_type = 'Category Acquired Membership Arity Constraint'
def _calculateArity(self, obj, base_category_list, portal_type_list):
return len(obj.getAcquiredCategoryMembershipList(
base_category_list, portal_type=portal_type_list))
...@@ -29,13 +29,15 @@ ...@@ -29,13 +29,15 @@
############################################################################## ##############################################################################
from Products.ERP5Type.mixin.constraint import ConstraintMixin from Products.ERP5Type.mixin.constraint import ConstraintMixin
from Products.CMFCore.Expression import Expression
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet
class CategoryExistenceConstraint(ConstraintMixin): class CategoryExistenceConstraint(ConstraintMixin):
""" """
This constraint checks whether a category has been defined on this This constraint checks whether a category has been defined on this
object (without acquisition). object (with or without acquisition depending on use_acquisition
value).
This is only relevant for ZODB Property Sheets (filesystem Property This is only relevant for ZODB Property Sheets (filesystem Property
Sheets rely on Products.ERP5Type.Constraint.CategoryExistence Sheets rely on Products.ERP5Type.Constraint.CategoryExistence
...@@ -44,6 +46,8 @@ class CategoryExistenceConstraint(ConstraintMixin): ...@@ -44,6 +46,8 @@ class CategoryExistenceConstraint(ConstraintMixin):
meta_type = 'ERP5 Category Existence Constraint' meta_type = 'ERP5 Category Existence Constraint'
portal_type = 'Category Existence Constraint' portal_type = 'Category Existence Constraint'
__compatibility_class_name__ = 'CategoryExistence'
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
...@@ -52,7 +56,12 @@ class CategoryExistenceConstraint(ConstraintMixin): ...@@ -52,7 +56,12 @@ class CategoryExistenceConstraint(ConstraintMixin):
(PropertySheet.CategoryExistenceConstraint,) (PropertySheet.CategoryExistenceConstraint,)
def _calculateArity(self, obj, base_category, portal_type_list): def _calculateArity(self, obj, base_category, portal_type_list):
return len(obj.getCategoryMembershipList(base_category, if self.getUseAcquisition():
category_membership_list_function = obj.getAcquiredCategoryMembershipList
else:
category_membership_list_function = obj.getCategoryMembershipList
return len(category_membership_list_function(base_category,
portal_type=portal_type_list)) portal_type=portal_type_list))
def _checkConsistency(self, obj, fixit=0): def _checkConsistency(self, obj, fixit=0):
...@@ -81,13 +90,54 @@ class CategoryExistenceConstraint(ConstraintMixin): ...@@ -81,13 +90,54 @@ class CategoryExistenceConstraint(ConstraintMixin):
return error_list return error_list
class CategoryAcquiredExistenceConstraint(CategoryExistenceConstraint): _message_id_tuple = ('message_category_not_set',
'message_category_not_associated_with_portal_type')
@staticmethod
def _preConvertBaseFromFilesystemDefinition(filesystem_definition_dict):
""" """
This constraint checks whether a category has been defined on this CategoryExistence and CategoryAcquiredExistence filesystem
object (with acquisition). This is only relevant for ZODB Property Constraints have been merged into a single Document for ZODB
Sheets (filesystem Property Sheets rely on Constraint by adding 'use_acquisition' attribute
Products.ERP5Type.Constraint.CategoryExistence instead).
""" """
def _calculateArity(self, obj, base_category, portal_type_list): return dict(use_acquisition=(filesystem_definition_dict['type'] == \
return len(obj.getAcquiredCategoryMembershipList( 'CategoryAcquiredExistence'))
base_category, portal_type=portal_type_list))
@staticmethod
def _convertFromFilesystemDefinition(portal_type=(),
**base_category_dict):
"""
@see ERP5Type.mixin.constraint.ConstraintMixin._convertFromFilesystemDefinition
Filesystem definition example:
{ 'id' : 'category_existence',
'description' : 'Category causality must be defined',
'type' : 'CategoryExistence',
'portal_type' : ('Person', 'Organisation'),
'causality' : None,
'condition' : 'python: object.getPortalType() == 'Foo',
}
"""
constraint_portal_type_str = isinstance(portal_type, Expression) and \
portal_type.text or 'python: ' + repr(portal_type)
yield dict(constraint_base_category_list=base_category_dict.keys(),
constraint_portal_type=constraint_portal_type_str)
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(CategoryExistenceConstraint,
self).exportToFilesystemDefinitionDict()
# There is only one ZODB Constraint class for filesystem
# Constraints CategoryExistence and CategoryAcquiredExistence
# constraints
if self.getUseAcquisition():
filesystem_definition_dict['type'] = 'CategoryAcquiredExistence'
filesystem_definition_dict['portal_type'] = \
Expression(self.getConstraintPortalType())
for category in self.getConstraintBaseCategoryList():
filesystem_definition_dict[category] = 1
return filesystem_definition_dict
...@@ -29,12 +29,14 @@ ...@@ -29,12 +29,14 @@
############################################################################## ##############################################################################
from Products.ERP5Type.mixin.constraint import ConstraintMixin from Products.ERP5Type.mixin.constraint import ConstraintMixin
from Products.CMFCore.Expression import Expression
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet
class CategoryMembershipArityConstraint(ConstraintMixin): class CategoryMembershipArityConstraint(ConstraintMixin):
""" """
This constraint checks if an object respects the arity. This constraint checks if an object respects the arity (with or
without acquisition depending on use_acquisition value).
This is only relevant for ZODB Property Sheets (filesystem Property This is only relevant for ZODB Property Sheets (filesystem Property
Sheets rely on Products.ERP5Type.Constraint.CategoryMembershipArity Sheets rely on Products.ERP5Type.Constraint.CategoryMembershipArity
...@@ -52,6 +54,8 @@ class CategoryMembershipArityConstraint(ConstraintMixin): ...@@ -52,6 +54,8 @@ class CategoryMembershipArityConstraint(ConstraintMixin):
meta_type = 'ERP5 Category Membership Arity Constraint' meta_type = 'ERP5 Category Membership Arity Constraint'
portal_type = 'Category Membership Arity Constraint' portal_type = 'Category Membership Arity Constraint'
__compatibility_class_name__ = 'CategoryMembershipArity'
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
...@@ -102,3 +106,73 @@ class CategoryMembershipArityConstraint(ConstraintMixin): ...@@ -102,3 +106,73 @@ class CategoryMembershipArityConstraint(ConstraintMixin):
return [self._generateError(obj, return [self._generateError(obj,
self._getMessage(message_id), self._getMessage(message_id),
mapping)] mapping)]
_message_id_tuple = ('message_arity_too_small',
'message_arity_not_in_range',
'message_arity_with_portal_type_too_small',
'message_arity_with_portal_type_not_in_range')
@staticmethod
def _preConvertBaseFromFilesystemDefinition(filesystem_definition_dict):
"""
CategoryAcquiredMembershipArity and CategoryMembershipArity
filesystem Constraints have been merged into a single Document for
ZODB Constraint by adding 'use_acquisition' attribute
"""
return dict(use_acquisition=(filesystem_definition_dict['type'] == \
'CategoryAcquiredMembershipArity'))
@staticmethod
def _convertFromFilesystemDefinition(min_arity,
portal_type=(),
max_arity=None,
base_category=()):
"""
@see ERP5Type.mixin.constraint.ConstraintMixin._convertFromFilesystemDefinition
Filesystem definition example:
{ 'id' : 'source',
'description' : '',
'type' : 'CategoryMembershipArity',
'min_arity' : '1',
'max_arity' : '1',
'portal_type' : ('Organisation', ),
'base_category' : ('source',)
'condition' : 'python: object.getPortalType() == 'Foo',
}
"""
constraint_portal_type_str = isinstance(portal_type, Expression) and \
portal_type.text or 'python: ' + repr(portal_type)
zodb_property_dict = dict(
min_arity=int(min_arity),
constraint_portal_type=constraint_portal_type_str,
constraint_base_category_list=base_category)
if max_arity is not None:
zodb_property_dict['max_arity'] = int(max_arity)
yield zodb_property_dict
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(CategoryMembershipArityConstraint,
self).exportToFilesystemDefinitionDict()
# There is only one ZODB Constraint class for filesystem
# Constraints CategoryMembershipArity and
# CategoryAcquiredMembershipArity constraints
if self.getUseAcquisition():
filesystem_definition_dict['type'] = 'CategoryAcquiredMembershipArity'
filesystem_definition_dict['min_arity'] = str(self.getMinArity())
if self.hasMaxArity():
filesystem_definition_dict['max_arity'] = str(self.getMaxArity())
filesystem_definition_dict['portal_type'] = \
Expression(self.getConstraintPortalType())
filesystem_definition_dict['base_category'] = self.getConstraintBaseCategoryList()
return filesystem_definition_dict
...@@ -57,6 +57,8 @@ class CategoryRelatedMembershipArityConstraint(CategoryMembershipArityConstraint ...@@ -57,6 +57,8 @@ class CategoryRelatedMembershipArityConstraint(CategoryMembershipArityConstraint
meta_type = 'ERP5 Category Related Membership Arity Constraint' meta_type = 'ERP5 Category Related Membership Arity Constraint'
portal_type = 'Category Related Membership Arity Constraint' portal_type = 'Category Related Membership Arity Constraint'
__compatibility_class_name__ = 'CategoryRelatedMembershipArity'
property_sheets = CategoryMembershipArityConstraint.property_sheets + \ property_sheets = CategoryMembershipArityConstraint.property_sheets + \
(PropertySheet.CategoryRelatedMembershipArityConstraint,) (PropertySheet.CategoryRelatedMembershipArityConstraint,)
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
############################################################################## ##############################################################################
from Products.ERP5Type.mixin.constraint import ConstraintMixin from Products.ERP5Type.mixin.constraint import ConstraintMixin
from Products.CMFCore.Expression import Expression
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet
...@@ -53,6 +54,8 @@ class ContentExistenceConstraint(ConstraintMixin): ...@@ -53,6 +54,8 @@ class ContentExistenceConstraint(ConstraintMixin):
meta_type = 'ERP5 Content Existence Constraint' meta_type = 'ERP5 Content Existence Constraint'
portal_type = 'Content Existence Constraint' portal_type = 'Content Existence Constraint'
__compatibility_class_name__ = 'ContentExistence'
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
...@@ -87,3 +90,32 @@ class ContentExistenceConstraint(ConstraintMixin): ...@@ -87,3 +90,32 @@ class ContentExistenceConstraint(ConstraintMixin):
return [self._generateError(obj, return [self._generateError(obj,
self._getMessage(message_id), self._getMessage(message_id),
mapping)] mapping)]
_message_id_tuple = ('message_no_subobject',
'message_no_subobject_portal_type')
@staticmethod
def _convertFromFilesystemDefinition(portal_type=()):
"""
@see ERP5Type.mixin.constraint.ConstraintMixin._convertFromFilesystemDefinition
Filesystem definition example:
{ 'id' : 'line',
'description' : 'Object have to contain a Line',
'type' : 'ContentExistence',
'portal_type' : ('Line', ),
}
"""
constraint_portal_type_str = isinstance(portal_type, Expression) and \
portal_type.text or 'python: ' + repr(portal_type)
yield dict(constraint_portal_type=constraint_portal_type_str)
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(ContentExistenceConstraint,
self).exportToFilesystemDefinitionDict()
filesystem_definition_dict['portal_type'] = \
Expression(self.getConstraintPortalType())
return filesystem_definition_dict
...@@ -50,6 +50,8 @@ class PropertyExistenceConstraint(ConstraintMixin): ...@@ -50,6 +50,8 @@ class PropertyExistenceConstraint(ConstraintMixin):
meta_type = 'ERP5 Property Existence Constraint' meta_type = 'ERP5 Property Existence Constraint'
portal_type = 'Property Existence Constraint' portal_type = 'Property Existence Constraint'
__compatibility_class_name__ = 'PropertyExistence'
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
...@@ -71,7 +73,7 @@ class PropertyExistenceConstraint(ConstraintMixin): ...@@ -71,7 +73,7 @@ class PropertyExistenceConstraint(ConstraintMixin):
def _checkConsistency(self, obj, fixit=False): def _checkConsistency(self, obj, fixit=False):
""" """
Check the object's consistency. Check the object's consistency
""" """
error_list = [] error_list = []
# For each attribute name, we check if defined # For each attribute name, we check if defined
...@@ -83,3 +85,29 @@ class PropertyExistenceConstraint(ConstraintMixin): ...@@ -83,3 +85,29 @@ class PropertyExistenceConstraint(ConstraintMixin):
dict(property_id=property_id))) dict(property_id=property_id)))
return error_list return error_list
_message_id_tuple = ('message_no_such_property',)
@staticmethod
def _convertFromFilesystemDefinition(**property_dict):
"""
@see ERP5Type.mixin.constraint.ConstraintMixin._convertFromFilesystemDefinition
Filesystem definition example:
{ 'id' : 'property_existence',
'description' : 'Property price must be defined',
'type' : 'PropertyExistence',
'price' : None,
'condition' : 'python: object.getPortalType() == 'Foo',
}
"""
yield dict(constraint_property_list=property_dict.keys())
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(PropertyExistenceConstraint,
self).exportToFilesystemDefinitionDict()
for constraint_property in self.getConstraintPropertyList():
filesystem_definition_dict[constraint_property] = None
return filesystem_definition_dict
...@@ -44,6 +44,8 @@ class PropertyTypeValidityConstraint(ConstraintMixin): ...@@ -44,6 +44,8 @@ class PropertyTypeValidityConstraint(ConstraintMixin):
meta_type = 'ERP5 Property Type Validity Constraint' meta_type = 'ERP5 Property Type Validity Constraint'
portal_type = 'Property Type Validity Constraint' portal_type = 'Property Type Validity Constraint'
__compatibility_class_name__ = 'PropertyTypeValidity'
property_sheets = ConstraintMixin.property_sheets + \ property_sheets = ConstraintMixin.property_sheets + \
(PropertySheet.PropertyTypeValidityConstraint,) (PropertySheet.PropertyTypeValidityConstraint,)
...@@ -133,3 +135,8 @@ class PropertyTypeValidityConstraint(ConstraintMixin): ...@@ -133,3 +135,8 @@ class PropertyTypeValidityConstraint(ConstraintMixin):
obj.setProperty(property_id, oldvalue) obj.setProperty(property_id, oldvalue)
return error_list return error_list
_message_id_tuple = ('message_unknown_type',
'message_incorrect_type',
'message_incorrect_type_fix_failed',
'message_incorrect_type_fixed')
...@@ -61,6 +61,8 @@ class TALESConstraint(ConstraintMixin): ...@@ -61,6 +61,8 @@ class TALESConstraint(ConstraintMixin):
meta_type = 'ERP5 TALES Constraint' meta_type = 'ERP5 TALES Constraint'
portal_type = 'TALES Constraint' portal_type = 'TALES Constraint'
__compatibility_class_name__ = 'TALESConstraint'
property_sheets = ConstraintMixin.property_sheets + \ property_sheets = ConstraintMixin.property_sheets + \
(PropertySheet.TALESConstraint,) (PropertySheet.TALESConstraint,)
...@@ -86,3 +88,18 @@ class TALESConstraint(ConstraintMixin): ...@@ -86,3 +88,18 @@ class TALESConstraint(ConstraintMixin):
mapping=dict(error=str(e)))] mapping=dict(error=str(e)))]
return [] return []
_message_id_tuple = ('message_expression_false',
'message_expression_error')
@staticmethod
def _convertFromFilesystemDefinition(expression):
yield dict(expression=expression)
def exportToFilesystemDefinitionDict(self):
filesystem_definition_dict = super(TALESConstraint,
self).exportToFilesystemDefinitionDict()
filesystem_definition_dict['expression'] = self.getExpression()
return filesystem_definition_dict
...@@ -78,6 +78,10 @@ class PropertySheetTool(BaseTool): ...@@ -78,6 +78,10 @@ class PropertySheetTool(BaseTool):
return 'Standard Property' return 'Standard Property'
_merged_portal_type_dict = {
'CategoryAcquiredExistence': 'Category Existence Constraint',
'CategoryAcquiredMembershipArity': 'Category Membership Arity Constraint'}
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'createPropertySheetFromFilesystemClass') 'createPropertySheetFromFilesystemClass')
def createPropertySheetFromFilesystemClass(self, klass): def createPropertySheetFromFilesystemClass(self, klass):
...@@ -85,7 +89,9 @@ class PropertySheetTool(BaseTool): ...@@ -85,7 +89,9 @@ class PropertySheetTool(BaseTool):
Create a new Property Sheet in portal_property_sheets from a given Create a new Property Sheet in portal_property_sheets from a given
filesystem-based Property Sheet definition. filesystem-based Property Sheet definition.
""" """
new_property_sheet = self.newContent(id=klass.__name__, new_property_sheet_name = klass.__name__
new_property_sheet = self.newContent(id=new_property_sheet_name,
portal_type='Property Sheet') portal_type='Property Sheet')
types_tool = self.getPortalObject().portal_types types_tool = self.getPortalObject().portal_types
...@@ -112,6 +118,41 @@ class PropertySheetTool(BaseTool): ...@@ -112,6 +118,41 @@ class PropertySheetTool(BaseTool):
portal_type_class.importFromFilesystemDefinition(new_property_sheet, portal_type_class.importFromFilesystemDefinition(new_property_sheet,
category) category)
# Get filesystem Constraint names to be able to map them properly
# to ZODB Constraint Portal Types as some filesystem constraint
# names are 'NAMEConstraint' or 'NAME'
from Products.ERP5Type import Constraint as FilesystemConstraint
filesystem_constraint_class_name_list = [
class_name for class_name in FilesystemConstraint.__dict__ \
if class_name[0] != '_' ]
# Mapping between the filesystem 'type' field and Portal Types ID
portal_type_dict = {}
for search_result in types_tool.searchFolder(id='% Constraint'):
portal_type_id = search_result.getId()
constraint_class_name = portal_type_id.replace(' ', '')
if constraint_class_name not in filesystem_constraint_class_name_list:
constraint_class_name = constraint_class_name.replace('Constraint', '')
if constraint_class_name not in filesystem_constraint_class_name_list:
raise ValueError, "PropertySheet %s: Constraint %s: No Portal " \
"Type defined for type '%s'" % (new_property_sheet_name,
constraint['id'],
constraint['type'])
portal_type_dict[constraint_class_name] = portal_type_id
portal_type_dict.update(self._merged_portal_type_dict)
for constraint in getattr(klass, '_constraints', ()):
portal_type = portal_type_dict[constraint['type']]
portal_type_class = types_tool.getPortalTypeClass(portal_type)
# Create the new constraint
portal_type_class.importFromFilesystemDefinition(new_property_sheet,
constraint)
return new_property_sheet return new_property_sheet
security.declareProtected(Permissions.ManagePortal, security.declareProtected(Permissions.ManagePortal,
......
...@@ -52,6 +52,15 @@ class ConstraintMixin(Predicate): ...@@ -52,6 +52,15 @@ class ConstraintMixin(Predicate):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
# IDs of error messages defined in each Constraint, only used when
# importing or exporting from/to filesystem Constraint
_message_id_tuple = ()
# Store the filesystem name of the ZODB Constraint as there are
# several different naming for filesystem constraints (*only* useful
# for exportToFilesystemDefinitionDict)
__compatibility_class_name__ = None
__allow_access_to_unprotected_subobjects__ = 1 __allow_access_to_unprotected_subobjects__ = 1
implements( IConstraint, ) implements( IConstraint, )
...@@ -104,6 +113,15 @@ class ConstraintMixin(Predicate): ...@@ -104,6 +113,15 @@ class ConstraintMixin(Predicate):
wrapping removed) in order to maintain compatibility and be able wrapping removed) in order to maintain compatibility and be able
to use setDefaultProperties to use setDefaultProperties
NOTE: A filesystem constraint is defined by a dict, then depending
on the 'type' of the constraint, the appropriate class in
ERP5Type.Constraint is instanciated with the dict. This is no
longer needed for ZODB Constraints because they are already
Documents.
@see exportToFilesystemDefinitionDict() to export a ZODB
Constraint as a dict.
XXX: remove as soon as the code is stable XXX: remove as soon as the code is stable
""" """
return self.asContext() return self.asContext()
...@@ -118,3 +136,106 @@ class ConstraintMixin(Predicate): ...@@ -118,3 +136,106 @@ class ConstraintMixin(Predicate):
return None return None
return Expression(expression_string)(createExpressionContext(obj)) return Expression(expression_string)(createExpressionContext(obj))
@staticmethod
def _preConvertBaseFromFilesystemDefinition(filesystem_definition_dict):
"""
Call before actually converting the attributes common to all
constraints
"""
return dict()
@staticmethod
def _convertFromFilesystemDefinition(*args, **kw):
"""
Convert a filesystem property dict to a ZODB Property dict which
will be given to newContent().
Only attributes specific to this constraint will be given as
parameters, e.g. not the ones common to all constraints such as
'id', 'description', 'type' and 'condition' and error messages
defined in '_message_id_tuple' class attribute.
@see importFromFilesystemDefinition
"""
yield dict()
security.declareProtected(Permissions.AccessContentsInformation,
'importFromFilesystemDefinition')
@classmethod
def importFromFilesystemDefinition(cls, context, filesystem_definition_dict):
"""
Import the filesystem definition to a ZODB Constraint, without its
condition as it is now a Predicate and has to be converted
manually.
Several ZODB Constraints may be created to handle Constraints such
as Attribute Equality which defines an attribute name and its
expected value, thus ending up creating one ZODB Constraint per
attribute/expected value
"""
# Copy the dictionnary as it is going to be modified to remove all
# the common properties in order to have a dictionnary containing
# only properties specific to the current constraint
filesystem_definition_copy_dict = filesystem_definition_dict.copy()
# This dict only contains definition attributes common to *all*
# ZODB Constraints
base_constraint_definition_dict = \
cls._preConvertBaseFromFilesystemDefinition(filesystem_definition_copy_dict)
base_constraint_definition_dict['portal_type'] = cls.portal_type
base_constraint_definition_dict['reference'] = \
filesystem_definition_copy_dict.pop('id')
base_constraint_definition_dict['description'] = \
filesystem_definition_copy_dict.pop('description', '')
if 'condition' in filesystem_definition_copy_dict:
base_constraint_definition_dict['test_tales_expression'] = \
filesystem_definition_copy_dict.pop('condition')
# The type is meaningless for ZODB Constraints as it is the portal
# type itself
filesystem_definition_copy_dict.pop('type')
# Add specific error messages defined on the constraint document
for message_name in cls._message_id_tuple:
if message_name in filesystem_definition_copy_dict:
base_constraint_definition_dict[message_name] = \
filesystem_definition_copy_dict.pop(message_name)
# Call the method defined in the Constraint document which returns
# N dictionnaries containing only attributes specific to the
# Constraint
constraint_definition_generator = \
cls._convertFromFilesystemDefinition(**filesystem_definition_copy_dict)
# Create all the constraint in the current ZODB Property Sheet
for constraint_definition_dict in constraint_definition_generator:
constraint_definition_dict.update(base_constraint_definition_dict)
context.newContent(**constraint_definition_dict)
security.declareProtected(Permissions.AccessContentsInformation,
'exportToFilesystemDefinitionDict')
def exportToFilesystemDefinitionDict(self):
"""
Export the ZODB Constraint as a filesystem definition dict,
meaningful *only* for testing that a filesystem Constraint has
been properly converted
@see exportToFilesystemDefinition()
"""
filesystem_definition_dict = dict(
id=self.getReference(),
description=self.getDescription(),
type=self.__compatibility_class_name__)
if self.hasTestTalesExpression():
filesystem_definition_dict['condition'] = self.getTestTalesExpression()
for message_id in self._message_id_tuple:
filesystem_definition_dict[message_id] = self._getMessage(message_id)
return filesystem_definition_dict
...@@ -1029,16 +1029,17 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase): ...@@ -1029,16 +1029,17 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase):
# Some fields may not defined a default value (such as 'id') # Some fields may not defined a default value (such as 'id')
continue continue
def _checkPropertyField(self, def _checkPropertyOrConstraintField(self,
property_sheet_name, property_sheet_name,
field_name, field_name,
filesystem_value, filesystem_value,
zodb_value): zodb_value):
""" """
Check whether the given filesystem property value and the given Check whether the given filesystem property or constraint value
ZODB property value are equal and the given ZODB property value are equal
""" """
if isinstance(zodb_value, (list, tuple)): if isinstance(zodb_value, (list, tuple)) and \
isinstance(filesystem_value, (list, tuple)):
self.failIfDifferentSet( self.failIfDifferentSet(
zodb_value, filesystem_value, zodb_value, filesystem_value,
msg="%s: %s: filesystem value: %s, ZODB value: %s" % \ msg="%s: %s: filesystem value: %s, ZODB value: %s" % \
...@@ -1067,43 +1068,44 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase): ...@@ -1067,43 +1068,44 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase):
(property_sheet_name, field_name, filesystem_value, (property_sheet_name, field_name, filesystem_value,
zodb_value)) zodb_value))
def _checkPropertyDefinitionTuple(self, def _checkPropertyOrConstraintDefinitionTuple(self,
property_sheet_name, property_sheet_name,
filesystem_property_tuple, filesystem_property_tuple,
zodb_property_tuple): zodb_property_tuple):
""" """
Check whether all properties have been properly converted from Check whether all properties or constraints have been properly
the filesystem to the ZODB Property Sheet converted from the filesystem to the ZODB Property Sheet
""" """
# Check whether all the properties are present in the given ZODB # Check whether all the properties or constraints are present in
# Property Sheet # the given ZODB Property Sheet
self.assertEqual( self.assertEqual(
len(filesystem_property_tuple), len(zodb_property_tuple), len(filesystem_property_tuple), len(zodb_property_tuple),
msg="%s: too many properties: filesystem: %s, ZODB: %s" % \ msg="%s: too many properties: filesystem: %s, ZODB: %s" % \
(property_sheet_name, filesystem_property_tuple, zodb_property_tuple)) (property_sheet_name, filesystem_property_tuple, zodb_property_tuple))
# Map filesystem property IDs to their definition # Map filesystem property or constraint IDs to their definition
filesystem_property_id_dict = {} filesystem_property_id_dict = {}
for property_dict in filesystem_property_tuple: for property_dict in filesystem_property_tuple:
filesystem_property_id_dict[property_dict['id']] = property_dict filesystem_property_id_dict[property_dict['id']] = property_dict
# Check each property defined in ZODB against the filesystem dict # Check each property or constraint defined in ZODB against the
# defined before # filesystem dict defined before
for zodb_property_dict in zodb_property_tuple: for zodb_property_dict in zodb_property_tuple:
# Meaningful to ensure that there is no missing field within a # Meaningful to ensure that there is no missing field within a
# property # property or constraint
validated_field_counter = 0 validated_field_counter = 0
filesystem_property_dict = \ filesystem_property_dict = \
filesystem_property_id_dict[zodb_property_dict['id']] filesystem_property_id_dict[zodb_property_dict['id']]
# Check each property field # Check each property or constraint field
for field_name, zodb_value in zodb_property_dict.iteritems(): for field_name, zodb_value in zodb_property_dict.iteritems():
if field_name in filesystem_property_dict: if field_name in filesystem_property_dict:
self._checkPropertyField(property_sheet_name, self._checkPropertyOrConstraintField(
field_name, property_sheet_name, field_name,
filesystem_property_dict[field_name], filesystem_property_dict[field_name],
zodb_value) zodb_value)
# As we are using accessors when exporting the ZODB Property # As we are using accessors when exporting the ZODB Property
# Sheet to its filesystem definition, there may be additional # Sheet to its filesystem definition, there may be additional
# fields set to their default value # fields set to their default value
...@@ -1162,8 +1164,6 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase): ...@@ -1162,8 +1164,6 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase):
Create Property Sheets on portal_property_sheets from their Create Property Sheets on portal_property_sheets from their
definition on the filesystem and then test that they are definition on the filesystem and then test that they are
equivalent equivalent
TODO: Constraints
""" """
portal = self.getPortalObject().portal_property_sheets portal = self.getPortalObject().portal_property_sheets
...@@ -1172,6 +1172,7 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase): ...@@ -1172,6 +1172,7 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase):
for name, klass in PropertySheet.__dict__.iteritems(): for name, klass in PropertySheet.__dict__.iteritems():
if name[0] == '_' or isinstance(klass, basestring): if name[0] == '_' or isinstance(klass, basestring):
continue continue
filesystem_property_sheet = klass filesystem_property_sheet = klass
property_sheet_name = name property_sheet_name = name
...@@ -1183,13 +1184,13 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase): ...@@ -1183,13 +1184,13 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase):
zodb_property_sheet = portal.createPropertySheetFromFilesystemClass( zodb_property_sheet = portal.createPropertySheetFromFilesystemClass(
filesystem_property_sheet) filesystem_property_sheet)
zodb_property_tuple, zodb_category_tuple, zodb_constraint_tuple = \ zodb_property_tuple, zodb_category_tuple, zodb_constraint_class_tuple = \
portal.exportPropertySheetToFilesystemDefinitionTuple( portal.exportPropertySheetToFilesystemDefinitionTuple(
zodb_property_sheet) zodb_property_sheet)
self._checkPropertyDefinitionTuple(property_sheet_name, self._checkPropertyOrConstraintDefinitionTuple(
getattr(filesystem_property_sheet, property_sheet_name,
'_properties', []), getattr(filesystem_property_sheet, '_properties', []),
zodb_property_tuple) zodb_property_tuple)
self._checkCategoryTuple(property_sheet_name, self._checkCategoryTuple(property_sheet_name,
...@@ -1197,6 +1198,12 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase): ...@@ -1197,6 +1198,12 @@ class TestZodbImportFilesystemPropertySheet(ERP5TypeTestCase):
'_categories', []), '_categories', []),
zodb_category_tuple) zodb_category_tuple)
self._checkPropertyOrConstraintDefinitionTuple(
property_sheet_name, getattr(filesystem_property_sheet,
'_constraints', []),
[ zodb_constraint_class.exportToFilesystemDefinitionDict() \
for zodb_constraint_class in zodb_constraint_class_tuple ])
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestPortalTypeClass)) suite.addTest(unittest.makeSuite(TestPortalTypeClass))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment