Commit 1b9b2267 authored by Romain Courteaud's avatar Romain Courteaud

Clean up current implementation.

Modify API of PropertyExistence and CategoryExistence, in order to test
multiple properties/categories.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@3776 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent aaf4e4e0
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Jean-Paul Smets <jp@nexedi.com> # Jean-Paul Smets <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -27,49 +28,36 @@ ...@@ -27,49 +28,36 @@
# #
############################################################################## ##############################################################################
from Constraint import Constraint from PropertyExistence import PropertyExistence
from zLOG import LOG class AttributeEquality(PropertyExistence):
"""
This constraint class allows to check / fix
attribute values.
Configuration example:
{ 'id' : 'title',
'description' : 'Title must be "ObjectTitle"',
'type' : 'AttributeEquality',
'title' : 'ObjectTitle',
},
"""
class AttributeEquality(Constraint): def checkConsistency(self, object, fixit=0):
""" """
This constraint class allows to check / fix This is the check method, we return a list of string,
SetMappedValue object definitions: each string corresponds to an error.
We will make sure that each non None constraint_definition is
- modified attributes satisfied (equality)
- modified base categories
- domain base categories
It is used for example in Transformations to check that elements
of an XMLMatrix include appropriate attributes which participate
in the price calculation.
We consider that a list can be equal to a tuple.
""" """
errors = PropertyExistence.checkConsistency(self, object, fixit=fixit)
def checkConsistency(self, object, fixit = 0): for attribute_name, attribute_value in self.constraint_definition.items():
""" error_message = None
This is the check method, we return a list of string, # If property does not exist, error will be raise by
each string corresponds to an error. # PropertyExistence Constraint.
if object.hasProperty(attribute_name):
We will make sure that each non None constraint_definition is satisfied identical = 1
(equality) if type(attribute_value) in (type(()), type([])):
""" # List type
errors = []
# For each attribute name, we check equality
for attribute_name in self.constraint_definition.keys():
attribute_value = self.constraint_definition[attribute_name]
if attribute_name is "mapped_value_base_category_list":
LOG("checkConsistency", 0, str((object.id, attribute_name,attribute_value)))
error_message = None
if not object.hasProperty(attribute_name):
error_message = "Attribute %s is not defined" % attribute_name
else:
identical = 1
if len(object.getProperty(attribute_name)) != len(attribute_value): if len(object.getProperty(attribute_name)) != len(attribute_value):
identical = 0 identical = 0
else: else:
...@@ -77,14 +65,18 @@ class AttributeEquality(Constraint): ...@@ -77,14 +65,18 @@ class AttributeEquality(Constraint):
if item not in attribute_value: if item not in attribute_value:
identical = 0 identical = 0
break break
if not identical: else:
error_message = "Attribute %s is %s but sould be %s" % (attribute_name, # Other type
object.getProperty(attribute_name), attribute_value) identical = (attribute_value == object.getProperty(attribute_name))
if error_message is not None: if not identical:
if fixit: # Generate error_message
object._setProperty(attribute_name, attribute_value) error_message = "Attribute %s is '%s' but should be '%s'" % \
error_message += " (Fixed)" (attribute_name, object.getProperty(attribute_name),
errors += [(object.getRelativeUrl(), 'AttributeEquality inconsistency', 100, error_message)] attribute_value)
# Generate error
return errors if error_message is not None:
if fixit:
object._setProperty(attribute_name, attribute_value)
error_message += " (Fixed)"
errors.append(self._generateError(object, error_message))
return errors
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -29,40 +30,35 @@ ...@@ -29,40 +30,35 @@
from Constraint import Constraint from Constraint import Constraint
class CategoryExistence(Constraint): class CategoryExistence(Constraint):
""" """
This method check and fix if an object respects the existence of a property. This method check and fix if an object respects the existence of
a category.
Configuration example:
{ 'id' : 'category_existence',
'description' : 'Category causality must be defined',
'type' : 'CategoryExistence',
'causality' : None,
},
"""
For example we can check if every invoice line has a price defined on it. def checkConsistency(self, object, fixit=0):
""" """
This is the check method, we return a list of string,
def __init__(self, **constraint_definition): each string corresponds to an error.
""" """
We need the definition list of the constraint errors = []
""" # For each attribute name, we check if defined
self.constraint_definition = constraint_definition for base_category in self.constraint_definition.keys():
# Check existence of base category
def checkConsistency(self, object, fixit = 0): error_message = "Category existence error for base category '%s': " % \
""" base_category
this is the check method, we return a list of string,
each string corresponds to an error.
"""
errors = []
# Retrieve values inside de PropertySheet (_constraints)
base_category = self.constraint_definition['base_category']
# Check arity and compare it with the min and max
error_message = None
if not object.hasCategory(base_category): if not object.hasCategory(base_category):
error_message = "Category existence error for base category '%s': " % property_id \ error_message += " this document has no such category"
+ " this document has no such category" elif object.getProperty(base_category) is None:
elif object.getProperty(property_id) is None: error_message += " this property was not defined"
error_message = "Category existence error for base category '%s': " % property_id \
+ " this property was not defined"
if error_message:
errors = [(object.getRelativeUrl(), 'CategoryMembershipArity inconsistency',104, error_message)]
else: else:
errors = [] error_message = None
# Raise error
return errors if error_message:
errors.append(self._generateError(object, error_message))
return errors
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -29,54 +30,47 @@ ...@@ -29,54 +30,47 @@
from Constraint import Constraint from Constraint import Constraint
class CategoryMembershipArity(Constraint): class CategoryMembershipArity(Constraint):
""" """
This method check and fix if an object respects the arity. This method check and fix if an object respects the arity.
For example we can check if every Order has at
least one source.
Configuration exemple:
{ 'id' : 'source',
'description' : '',
'type' : 'CategoryMembershipArity',
'min_arity' : '1',
'max_arity' : '1',
'portal_type' : ('Organisation', ),
'base_category' : ('source',)
},
"""
For example we can check if every Organisation has at def checkConsistency(self, object, fixit=0):
least one address.
""" """
This is the check method, we return a list of string,
def __init__(self, **constraint_definition): each string corresponds to an error.
""" We are looking the definition of the constraing where
We need the definition list of the constraint are defined the minimum and the maximum arity, and the
""" list of objects we wants to check the arity.
self.constraint_definition = constraint_definition """
errors = []
def checkConsistency(self, object, fixit = 0): # Retrieve values inside de PropertySheet (_constraints)
""" base_category = self.constraint_definition['base_category']
this is the check method, we return a list of string, min_arity = int(self.constraint_definition['min_arity'])
each string corresponds to an error. max_arity = int(self.constraint_definition['max_arity'])
portal_type = self.constraint_definition['portal_type']
We are looking the definition of the constraing where # Check arity and compare it with the min and max
are defined the minimum and the maximum arity, and the arity = len(object.getCategoryMembershipList(base_category,
list of objects we wants to check the arity. portal_type=portal_type))
""" if (arity < min_arity) or (arity > max_arity):
# Generate error message
errors = [] error_message = "Arrity error for relation '%s'" % \
base_category
# Retrieve values inside de PropertySheet (_constraints) if portal_type is not ():
base_category = self.constraint_definition['base_category'] error_message += " and portal_type: '%s'" % str(portal_type)
min_arity = int(self.constraint_definition['min_arity']) error_message += \
max_arity = int(self.constraint_definition['max_arity']) ", arity is equal to %i but should be between %i and %i" % \
portal_type = self.constraint_definition['portal_type'] (arity, min_arity, max_arity)
# Add error
# Check arity and compare it with the min and max errors.append(self._generateError(object, error_message))
arity = len(object.getCategoryMembershipList(base_category,portal_type=portal_type)) return errors
if arity < min_arity or arity > max_arity:
if portal_type is ():
error_message = "Arrity error for relation '%s'" % base_category \
+ ", arity is equal to " \
+ str(arity) \
+ " but should be between " \
+ str(min_arity) + " and " + str(max_arity)
else:
error_message = "Arrity error for relation '%s' and portal_type : " % base_category \
+ str(portal_type) \
+ ", arity is equal to " \
+ str(arity) \
+ " but should be between " \
+ str(min_arity) + " and " + str(max_arity)
errors += [(object.getRelativeUrl(), 'CategoryMembershipArity inconsistency',104, error_message)]
return errors
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Courteaud Romain <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -32,52 +33,45 @@ class CategoryRelatedMembershipArity(Constraint): ...@@ -32,52 +33,45 @@ class CategoryRelatedMembershipArity(Constraint):
""" """
This method check and fix if an object respects the arity This method check and fix if an object respects the arity
from a category reverse membership point of view. from a category reverse membership point of view.
For example we can check if every Order has at For example we can check if every Order has at
most one Order Applied Rule. most one Order Applied Rule.
Configuration example:
{ 'id' : 'applied_rule',
'description' : 'There must at most one Applied Rule using this order',
'type' : 'CategoryRelatedMembershipArity',
'min_arity' : '1',
'max_arity' : '1',
'portal_type' : ('Applied Rule', ),
'base_category' : ('causality',)
},
""" """
def __init__(self, **constraint_definition): def checkConsistency(self, object, fixit=0):
"""
We need the definition list of the constraint
"""
self.constraint_definition = constraint_definition
def checkConsistency(self, object, fixit = 0):
""" """
this is the check method, we return a list of string, This is the check method, we return a list of string,
each string corresponds to an error. each string corresponds to an error.
We are looking the definition of the constraing where We are looking the definition of the constraing where
are defined the minimum and the maximum arity, and the are defined the minimum and the maximum arity, and the
list of objects we wants to check the arity. list of objects we wants to check the arity.
""" """
errors = [] errors = []
# Retrieve values inside de PropertySheet (_constraints) # Retrieve values inside de PropertySheet (_constraints)
base_category = self.constraint_definition['base_category'] base_category = self.constraint_definition['base_category']
min_arity = int(self.constraint_definition['min_arity']) min_arity = int(self.constraint_definition['min_arity'])
max_arity = int(self.constraint_definition['max_arity']) max_arity = int(self.constraint_definition['max_arity'])
portal_type = self.constraint_definition['portal_type'] portal_type = self.constraint_definition['portal_type']
# Check arity and compare it with the min and max # Check arity and compare it with the min and max
arity = len(object._getRelatedValueList(base_category,portal_type=portal_type)) arity = len(object._getRelatedValueList(base_category,
if arity < min_arity or arity > max_arity: portal_type=portal_type))
if portal_type is (): if (arity < min_arity) or (arity > max_arity):
error_message = "Arrity error for reverse relation '%s'" % base_category \ # Generate error message
+ ", arity is equal to " \ error_message = "Arrity error for reverse relation '%s'" % \
+ str(arity) \ base_category
+ " but should be between " \ if portal_type is not ():
+ str(min_arity) + " and " + str(max_arity) error_message += " and portal_type: '%s'" % str(portal_type)
else: error_message += \
error_message = "Arrity error for reverse relation '%s' and portal_type : " % base_category \ ", arity is equal to %i but should be between %i and %i" % \
+ str(portal_type) \ (arity, min_arity, max_arity)
+ ", arity is equal to " \ # Add error
+ str(arity) \ errors.append(self._generateError(object, error_message))
+ " but should be between " \
+ str(min_arity) + " and " + str(max_arity)
errors += [(object.getRelativeUrl(), 'CategoryRelatedMembershipArity inconsistency',104, error_message)]
return errors return errors
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Jean-Paul Smets <jp@nexedi.com> # Jean-Paul Smets <jp@nexedi.com>
# Courteaud Romain <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -32,7 +33,8 @@ class Constraint: ...@@ -32,7 +33,8 @@ class Constraint:
Default Constraint implementation Default Constraint implementation
""" """
def __init__(self, id=None, description=None, type=None, **constraint_definition): def __init__(self, id=None, description=None, type=None,
**constraint_definition):
""" """
Remove unwanted attributes from constraint definition and keep Remove unwanted attributes from constraint definition and keep
them as instance attributes them as instance attributes
...@@ -42,7 +44,8 @@ class Constraint: ...@@ -42,7 +44,8 @@ class Constraint:
self.type = type self.type = type
self.constraint_definition = constraint_definition self.constraint_definition = constraint_definition
def edit(self, id=None, description=None, type=None, **constraint_definition): def edit(self, id=None, description=None, type=None,
**constraint_definition):
""" """
Remove unwanted attributes from constraint definition and keep Remove unwanted attributes from constraint definition and keep
them as instance attributes them as instance attributes
...@@ -52,7 +55,18 @@ class Constraint: ...@@ -52,7 +55,18 @@ class Constraint:
if type is not None: self.type = type if type is not None: self.type = type
self.constraint_definition.update(constraint_definition) self.constraint_definition.update(constraint_definition)
def checkConsistency(self, object, fixit = 0): def _generateError(self, object, error_message):
"""
Generic method used to generate error in checkConsistency.
"""
error = None
if error_message:
error = (object.getRelativeUrl(),
'%s inconsistency' % self.__class__.__name__,
104, error_message)
return error
def checkConsistency(self, object, fixit=0):
""" """
Default method is to return no error. Default method is to return no error.
""" """
...@@ -64,5 +78,4 @@ class Constraint: ...@@ -64,5 +78,4 @@ class Constraint:
Default method is to call checkConsistency with Default method is to call checkConsistency with
fixit set to 1 fixit set to 1
""" """
return self.checkConsistency(object, fixit = 1) return self.checkConsistency(object, fixit=1)
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -29,40 +30,38 @@ ...@@ -29,40 +30,38 @@
from Constraint import Constraint from Constraint import Constraint
class PropertyExistence(Constraint): class PropertyExistence(Constraint):
""" """
This method check and fix if an object respects the existence of a property. This method check and fix if an object respects the existence of a
property.
For example we can check if every invoice line has a price defined
on it.
Configuration example:
{ 'id' : 'property_existence',
'description' : 'Property price must be defined',
'type' : 'PropertyExistence',
'price' : None,
},
"""
For example we can check if every invoice line has a price defined on it. def checkConsistency(self, object, fixit=0):
""" """
This is the check method, we return a list of string,
def __init__(self, **constraint_definition): each string corresponds to an error.
""" """
We need the definition list of the constraint errors = []
""" # For each attribute name, we check if defined
self.constraint_definition = constraint_definition for property_id in self.constraint_definition.keys():
# Check existence of property
def checkConsistency(self, object, fixit = 0): error_message = \
""" "Property existence error for property '%s': " % property_id
this is the check method, we return a list of string,
each string corresponds to an error.
"""
errors = []
# Retrieve values inside de PropertySheet (_constraints)
property_id = self.constraint_definition['property_id']
# Check arity and compare it with the min and max
error_message = None
if not object.hasProperty(property_id): if not object.hasProperty(property_id):
error_message = "Property existence error for property '%s': " % property_id \ error_message += " this document has no such property"
+ " this document has no such property"
elif object.getProperty(property_id) is None: elif object.getProperty(property_id) is None:
error_message = "Property existence error for property '%s': " % property_id \ error_message += " this property was not defined"
+ " this property was not defined"
if error_message:
errors = [(object.getRelativeUrl(), 'CategoryMembershipArity inconsistency',104, error_message)]
else: else:
errors = [] error_message = None
# Return error
return errors error = self._generateError(object, error_message)
if error is not None:
errors.append(error)
return errors
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com> # Sebastien Robin <seb@nexedi.com>
# Jean-Paul Smets <jp@nexedi.com> # Jean-Paul Smets <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -28,85 +29,89 @@ ...@@ -28,85 +29,89 @@
############################################################################## ##############################################################################
from Constraint import Constraint from Constraint import Constraint
from string import split
class PropertyTypeValidity(Constraint): class PropertyTypeValidity(Constraint):
"""
This constraint class allows to check / fix type of each
attributes define in the PropertySheets.
This Constraint is always created in ERP5Type/Utils.py
"""
# Initialize type dict
_type_dict = {
'string': (type('a'), ),
'text': (type('a'), ),
'int': (type(1), ),
'boolean': (type(1), ),
'float': (type(1.0), ),
'long': (type(1L), ),
'lines': (type([]), type(())),
'tokens': (type([]), type(())),
'selection': (type([]), type(())),
'multiple selection': (type([]), type(())),
}
def _checkPropertiesAttributes(self):
""" """
This constraint class allows to check / fix Make sure instance has no _properties
SetMappedValue object definitions:
- modified attributes
- modified base categories
- domain base categories
It is used for example in Transformations to check that elements
of an XMLMatrix include appropriate attributes which participate
in the price calculation.
We consider that a list can be equal to a tuple.
""" """
# XXX FIXME what is _properties ?
def checkConsistency(self, object, fixit = 0): errors = []
""" if '_properties' in object.__dict__:
This is the check method, we return a list of string, # Remove _properties
each string corresponds to an error. error_message = "Instance has local _properties property"
if fixit:
We will make sure that each non None constraint_definition is satisfied # XXX FIXME we have to set exception name !
(equality) # try:
""" local_properties = object._properties
del object._properties
errors = [] object._local_properties = []
class_property_ids = object.propertyIds()
# Make sure instance has no _properties for p in local_properties:
if '_properties' in object.__dict__: if p['id'] not in class_property_ids:
# Remove _properties object._local_properties.append(p)
error_message = "Instance has local _properties property" error_message += " (Fixed)"
# except:
# error_message += " (ERROR)"
errors.append(self._generateError(object, error_message))
return errors
def checkConsistency(self, object, fixit=0):
"""
This is the check method, we return a list of string,
each string corresponds to an error.
"""
errors = []
# XXX FIXME Is this still useful ?
errors.extend(self._checkPropertiesAttributes())
# For each attribute name, we check type
for property in object.propertyMap():
property_id = property['id']
property_type = property['type']
wrong_type = 0
value = object.getProperty(property_id)
if value is not None:
# Check known type
try:
wrong_type = (type(value) not in self._type_dict[property_type])
except KeyError:
wrong_type = 0
error_message = "Attribute %s is defined with unknown type %s" % \
(property_id, property_type)
errors += [(object.getRelativeUrl(),
'PropertyTypeValidity inconsistency',
100, error_message)]
if wrong_type:
# Type is wrong, so, raise constraint error
error_message = \
"Attribute %s should be of type %s but is of type %s" % \
(property_id, property_type, str(type(value)))
if fixit: if fixit:
try: # XXX FIXME do not use except without exception name !
local_properties = object._properties # try:
del object._properties object.setProperty(property_id, value)
object._local_properties = [] error_message += " (Fixed)"
class_property_ids = object.propertyIds() # except:
for p in local_properties: # error_message += " (ERROR)"
if p['id'] not in class_property_ids: errors.append(self._generateError(object, error_message))
object._local_properties.append(p) return errors
error_message += " (Fixed)"
except:
error_message += " (ERROR)"
errors += [(object.getRelativeUrl(), 'PropertyTypeValidity inconsistency',
100, error_message)]
# For each attribute name, we check equality
for property in object.propertyMap():
property_id = property['id']
property_type = property['type']
wrong_type = 0
value = object.getProperty(property_id)
if value is not None:
if property_type == 'string' or property_type == 'text':
wrong_type = type(value) is not type('a')
elif property_type == 'int' or property_type == 'boolean':
wrong_type = type(value) is not type(1)
elif property_type == 'float':
wrong_type = type(value) is not type(1.0)
elif property_type == 'lines' or property_type == 'tokens'\
or property_type == 'selection' or property_type == 'multiple selection':
wrong_type = type(value) is not type([]) and type(value) is not type(())
if wrong_type:
error_message = "Attribute %s should be of type %s but is of type %s" % (property_id,
property_type, str(type(value)))
if fixit:
try:
object.setProperty(property_id, value)
error_message += " (Fixed)"
except:
error_message += " (ERROR)"
errors += [(object.getRelativeUrl(), 'PropertyTypeValidity inconsistency',
100, error_message)]
return errors
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