Commit cf0460f5 authored by Arnaud Fontaine's avatar Arnaud Fontaine

Add Attribute Equality Constraint for ZODB Property Sheets


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@40636 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f5f7e4f6
##############################################################################
#
# Copyright (c) 2002-2010 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
# Jean-Paul Smets <jp@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.mixin.constraint import ConstraintMixin
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.CMFCore.Expression import Expression
from Products.ERP5Type.Utils import createExpressionContext
class AttributeEqualityConstraint(ConstraintMixin):
"""
This constraint checks the values of a given attribute name on this
object. This is only relevant for ZODB Property Sheets (filesystem
Property Sheets rely on Products.ERP5Type.Constraint.PropertyExistence
instead). Note that the attribute expected value is now a TALES
Expression to be able to use any Python type and not only strings.
For example, if we would like to check whether the attribute 'title'
has 'ObjectTitle' as its value, we would create a 'Attribute
Equality Constraint' within that Property Sheet and set 'title' as
the 'Attribute Name' and 'python: "ObjectTitle"' as the 'Attribute
Value', then set the 'Predicate' if necessary (known as 'condition'
for filesystem Property Sheets).
"""
meta_type = 'ERP5 Attribute Equality Constraint'
portal_type = 'Attribute Equality Constraint'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
property_sheets = (PropertySheet.SimpleItem,
PropertySheet.Predicate,
PropertySheet.Reference,
PropertySheet.AttributeEqualityConstraint)
# Define by default error messages
_message_id_list = ['message_invalid_attribute_value',
'message_invalid_attribute_value_fixed']
message_invalid_attribute_value = "Attribute ${attribute_name} "\
"value is ${current_value} but should be ${expected_value}"
message_invalid_attribute_value_fixed = "Attribute ${attribute_name} "\
"value is ${current_value} but should be ${expected_value} (Fixed)"
security.declareProtected(Permissions.AccessContentsInformation,
'checkConsistency')
def checkConsistency(self, obj, fixit=False):
"""
Check the object's consistency.
"""
if not self.test(obj):
return []
attribute_name = self.getConstraintAttributeName()
# If property does not exist, error will be raised by
# PropertyExistence Constraint, but the value has to be set at
# least once as there is no need to perform any check if it is the
# default value
if obj.hasProperty(attribute_name):
identical = True
# The expected value of the attribute is a TALES Expression
attribute_expected_value_expression = Expression(
self.getConstraintAttributeValue())
attribute_expected_value = attribute_expected_value_expression(
createExpressionContext(obj))
attribute_value = obj.getProperty(attribute_name)
if isinstance(attribute_expected_value, (list, tuple)):
# List type
if len(attribute_value) != len(attribute_expected_value):
identical = False
else:
for item in attribute_value:
if item not in attribute_expected_value:
identical = False
break
else:
# Other primitive type
identical = (attribute_expected_value == attribute_value)
if not identical:
# Generate error and fix it if required
if fixit:
obj._setProperty(attribute_name, attribute_expected_value)
message_id = 'message_invalid_attribute_value_fixed'
else:
message_id = 'message_invalid_attribute_value'
error = self._generateError(
obj, self._getMessage(message_id),
dict(attribute_name=attribute_name,
attribute_value=attribute_value,
expected_value=attribute_expected_value))
return [error]
return []
##############################################################################
#
# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
# 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.
#
##############################################################################
class AttributeEqualityConstraint:
"""
Define an Attribute Equality Constraint for ZODB Property Sheets
"""
_properties = (
{ 'id': 'constraint_attribute_name',
'type': 'string',
'description' : 'Attribute name whose values are checked' },
{ 'id': 'constraint_attribute_value',
'type': 'string',
'description' : 'Valid values of the Attribute' },
{ 'id': 'message_invalid_attribute_value',
'type': 'string',
'description' : 'Error message when the attribute value is invalid' },
{ 'id': 'message_invalid_attribute_value_fixed',
'type': 'string',
'description' : 'Error message when the attribute value is invalid but has been fixed' },
)
...@@ -19,3 +19,4 @@ from AcquiredProperty import AcquiredProperty ...@@ -19,3 +19,4 @@ from AcquiredProperty import AcquiredProperty
from DynamicCategoryProperty import DynamicCategoryProperty from DynamicCategoryProperty import DynamicCategoryProperty
from CategoryExistenceConstraint import CategoryExistenceConstraint from CategoryExistenceConstraint import CategoryExistenceConstraint
from PropertyExistenceConstraint import PropertyExistenceConstraint from PropertyExistenceConstraint import PropertyExistenceConstraint
from AttributeEqualityConstraint import AttributeEqualityConstraint
...@@ -42,6 +42,10 @@ class ConstraintMixin(Predicate): ...@@ -42,6 +42,10 @@ class ConstraintMixin(Predicate):
Mixin Constraint implementation (only relevant for ZODB Property Mixin Constraint implementation (only relevant for ZODB Property
sheets, use Products.ERP5Type.Constraint instead for filesystem sheets, use Products.ERP5Type.Constraint instead for filesystem
Property Sheets) relying on Predicate Property Sheets) relying on Predicate
@todo: Add code to import constraints requiring a new TALES
Expression field in predicate to be able to import
'condition' properly
""" """
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
......
...@@ -284,7 +284,10 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -284,7 +284,10 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
id=base_category_id, portal_type='Base Category') id=base_category_id, portal_type='Base Category')
# Create a dummy sub-category # Create a dummy sub-category
new_base_category.newContent(reference='sub_category', new_base_category.newContent(reference='sub_category1',
portal_type='Category')
new_base_category.newContent(reference='sub_category2',
portal_type='Category') portal_type='Category')
if operation_type == 'change': if operation_type == 'change':
...@@ -343,6 +346,25 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -343,6 +346,25 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
# XXX # XXX
# constraint_portal_type=('TODO',)) # constraint_portal_type=('TODO',))
def _newAttributeEqualityConstraint(self):
"""
Create a new Attribute Equality Constraint within test Property
Sheet
"""
# For testing primitive type as attribute value
self.test_property_sheet.newContent(
reference='test_attribute_equality_constraint',
portal_type='Attribute Equality Constraint',
constraint_attribute_name='title',
constraint_attribute_value='python: "my_valid_title"')
# For testing list type as attribute value
self.test_property_sheet.newContent(
reference='test_attribute_list_equality_constraint',
portal_type='Attribute Equality Constraint',
constraint_attribute_name='categories_list',
constraint_attribute_value='python: ("sub_category1", "sub_category2")')
def afterSetUp(self): def afterSetUp(self):
""" """
Create a test Property Sheet (and its properties) Create a test Property Sheet (and its properties)
...@@ -369,6 +391,10 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -369,6 +391,10 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
# Sheet # Sheet
self._newCategoryExistenceConstraint() self._newCategoryExistenceConstraint()
# Create an Attribute Equality Constraint in the test Property
# Sheet
self._newAttributeEqualityConstraint()
# Create all the test Properties # Create all the test Properties
for operation_type in ('change', 'delete', 'assign'): for operation_type in ('change', 'delete', 'assign'):
self._newStandardProperty(operation_type) self._newStandardProperty(operation_type)
...@@ -503,19 +529,19 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -503,19 +529,19 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
# Category Property # Category Property
self.assertHasAttribute(new_person, 'setTestCategoryPropertyAssign') self.assertHasAttribute(new_person, 'setTestCategoryPropertyAssign')
new_person.setTestCategoryPropertyAssign('sub_category') new_person.setTestCategoryPropertyAssign('sub_category1')
self.assertEquals(new_person.getTestCategoryPropertyAssign(), self.assertEquals(new_person.getTestCategoryPropertyAssign(),
'sub_category') 'sub_category1')
# Dynamic Category Property # Dynamic Category Property
self.assertHasAttribute(new_person, self.assertHasAttribute(new_person,
'setTestDynamicCategoryPropertyAssign') 'setTestDynamicCategoryPropertyAssign')
new_person.setTestDynamicCategoryPropertyAssign('sub_category') new_person.setTestDynamicCategoryPropertyAssign('sub_category1')
self.assertEquals(new_person.getTestDynamicCategoryPropertyAssign(), self.assertEquals(new_person.getTestDynamicCategoryPropertyAssign(),
'sub_category') 'sub_category1')
finally: finally:
# Perform a commit here because Workflow interactions keeps a # Perform a commit here because Workflow interactions keeps a
...@@ -729,10 +755,6 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -729,10 +755,6 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
constraint_reference, constraint_reference,
setter_function, setter_function,
value): value):
"""
Check whether the given constraint has been properly defined and
checkConsistency() is correct
"""
constraint = self._getConstraintByReference(constraint_reference) constraint = self._getConstraintByReference(constraint_reference)
self.failIfEqual(None, constraint) self.failIfEqual(None, constraint)
self.assertEquals(1, len(constraint.checkConsistency(self.test_module))) self.assertEquals(1, len(constraint.checkConsistency(self.test_module)))
...@@ -745,9 +767,8 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -745,9 +767,8 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
Take the test module and check whether the Property Existence Take the test module and check whether the Property Existence
Constraint is available. Until the property has been set to a Constraint is available. Until the property has been set to a
value, the constraint should fail value, the constraint should fail
@see ERP5Type.Base.Base.hasProperty
""" """
# See ERP5Type.Base.Base.hasProperty()
self._checkConstraint('test_property_existence_constraint', self._checkConstraint('test_property_existence_constraint',
self.test_module.setTestStandardPropertyConstraint, self.test_module.setTestStandardPropertyConstraint,
'foobar') 'foobar')
...@@ -760,7 +781,38 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -760,7 +781,38 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
""" """
self._checkConstraint('test_category_existence_constraint', self._checkConstraint('test_category_existence_constraint',
self.test_module.setTestCategoryPropertyConstraint, self.test_module.setTestCategoryPropertyConstraint,
'sub_category') 'sub_category1')
def testAttributeEqualityConstraint(self):
"""
Take the test module and check whether the Attribute Equality
Constraint is available. Until the attribute to be checked has
been set to its expected value, the constraint should fail. The
purpose is to test only primitive types (e.g. not list)
"""
# As checkConsistency calls hasProperty before checking the value,
# the property to be tested has to be set at least once (whatever
# the value)
self.test_module.setTitle('invalid_value')
self._checkConstraint('test_attribute_equality_constraint',
self.test_module.setTitle,
'my_valid_title')
def testAttributeListEqualityConstraint(self):
"""
Take the test module and check whether the Attribute Equality
Constraint is available. Until the attribute to be checked has
been set to its expected value (a list of categories), the
constraint should fail. The purpose is to test only list types
@see testAttributeEqualityConstraint
"""
self.test_module.setCategoryList(('sub_category1',))
self._checkConstraint('test_attribute_list_equality_constraint',
self.test_module.setCategoryList,
('sub_category1', 'sub_category2'))
TestZodbPropertySheet = skip("ZODB Property Sheets code is not enabled yet")( TestZodbPropertySheet = skip("ZODB Property Sheets code is not enabled yet")(
TestZodbPropertySheet) TestZodbPropertySheet)
......
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