Commit 4b1179b8 authored by Arnaud Fontaine's avatar Arnaud Fontaine

Implement constraints for ZODB Property Sheets and generate accessor

holder classes for filesystem Property Sheets too.



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@39348 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f8907838
##############################################################################
#
# 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.Constraint import Constraint
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
class CategoryExistenceConstraint(Constraint):
"""
This constraint checks whether a category has been defined on this
object (without acquisition). This is only relevant for ZODB
Property Sheets (filesystem Property Sheets rely on
Products.ERP5Type.Constraint.CategoryExistence instead).
"""
meta_type = 'ERP5 Category Existence Constraint'
portal_type = 'Category Existence Constraint'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
_message_id_list = [ 'message_category_not_set',
'message_category_not_associated_with_portal_type' ]
message_category_not_set = "Category existence error for base"\
" category ${base_category}, this category is not defined"
message_category_not_associated_with_portal_type = "Category existence"\
" error for base category ${base_category}, this"\
" document has no such category"
def _calculateArity(self, obj, base_category, portal_type):
return len(obj.getCategoryMembershipList(base_category,
portal_type=portal_type))
security.declareProtected(Permissions.AccessContentsInformation,
'checkConsistency')
def checkConsistency(self, obj, fixit=0):
"""
Check the object's consistency.
"""
error_list = []
if not self.test(obj):
return []
portal_type = self.getConstraintPortalTypeList() or ()
# For each attribute name, we check if defined
for base_category in self.getConstraintBaseCategoryList() or ():
mapping = dict(base_category=base_category)
# Check existence of base category
if base_category not in obj.getBaseCategoryList():
error_message = 'message_category_not_associated_with_portal_type'
elif self._calculateArity(obj, base_category, portal_type) == 0:
error_message = 'message_category_not_set'
else:
error_message = None
# Raise error
if error_message:
error_list.append(
self._generateError(obj,
self._getMessage(error_message), mapping))
return error_list
class CategoryAcquiredExistenceConstraint(CategoryExistenceConstraint):
"""
This constraint checks whether a category has been defined on this
object (with acquisition). This is only relevant for ZODB Property
Sheets (filesystem Property Sheets rely on
Products.ERP5Type.Constraint.CategoryExistence instead).
"""
def _calculateArity(self, obj, base_category, portal_type):
return len(obj.getAcquiredCategoryMembershipList(base_category,
portal_type=portal_type))
##############################################################################
#
# Copyright (c) 2002-2010 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
# Jean-Paul Smets <jp@nexedi.com>
# Courteaud Romain <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.interfaces import IConstraint
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
from zope.interface import implements
from Products.ERP5Type.Core.Predicate import Predicate
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5Type.Utils import UpperCase
class Constraint(Predicate):
"""
Constraint implementation (only relevant for ZODB Property sheets,
use Products.ERP5Type.Constraint instead for filesystem Property
Sheets) relying on Predicate
"""
meta_type = 'ERP5 Constraint'
portal_type = 'Constraint'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
__allow_access_to_unprotected_subobjects__ = 1
implements( IConstraint, )
_message_id_list = []
def _getMessage(self, message_id):
"""
Get the message corresponding to this message_id.
"""
return getattr(self, 'get' + UpperCase(message_id))()
def _generateError(self, obj, error_message, mapping={}):
"""
Generic method used to generate error in checkConsistency.
"""
if error_message is not None:
msg = ConsistencyMessage(self,
object_relative_url=obj.getRelativeUrl(),
message=error_message,
mapping=mapping)
return msg
security.declareProtected(Permissions.AccessContentsInformation,
'checkConsistency')
def checkConsistency(self, obj, fixit=0, **kw):
"""
Default method is to return no error.
"""
errors = []
return errors
security.declareProtected(Permissions.AccessContentsInformation,
'fixConsistency')
def fixConsistency(self, obj, **kw):
"""
Default method is to call checkConsistency with fixit set to 1
"""
return self.checkConsistency(obj, fixit=1, **kw)
##############################################################################
#
# 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.Constraint import Constraint
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
class PropertyExistenceConstraint(Constraint):
"""
This constraint checks whether a property has been defined on this
object. This is only relevant for ZODB Property Sheets (filesystem
Property Sheets rely on Products.ERP5Type.Constraint.PropertyExistence
instead).
For example, if we would like to check whether an invoice line has a
'price' property defined on it, we would create a 'Property
Existence Constraint' within that property sheet and add 'price' to
the 'Properties' field, then set the 'Predicate' if necessary (known
as 'condition' for filesystem Property Sheets).
"""
meta_type = 'ERP5 Property Existence Constraint'
portal_type = 'Property Existence Constraint'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Define by default error messages
_message_id_list = ['message_no_such_property',
'message_property_not_set']
message_no_such_property = "Property existence error for property "\
"${property_id}, this document has no such property"
message_property_not_set = "Property existence error for property "\
"${property_id}, this property is not defined"
security.declareProtected(Permissions.AccessContentsInformation,
'checkConsistency')
def checkConsistency(self, obj, fixit=0):
"""
Check the object's consistency.
"""
error_list = []
if not self.test(obj):
return []
# For each attribute name, we check if defined
for property_id in self.getConstraintPropertyList() or ():
# Check existence of property
mapping = dict(property_id=property_id)
if not obj.hasProperty(property_id):
error_message_id = "message_no_such_property"
elif obj.getProperty(property_id) is None:
# If value is '', attribute is considered a defined
# XXX is this the default API ?
error_message_id = "message_property_not_set"
else:
error_message_id = None
if error_message_id:
error_list.append(self._generateError(obj,
self._getMessage(error_message_id), mapping))
return error_list
......@@ -38,8 +38,9 @@ import lazyclass
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Utils import setDefaultClassProperties
from Products.ERP5Type import document_class_registry, mixin_class_registry
from Products.ERP5Type import PropertySheet as FilesystemPropertySheet
from ExtensionClass import Base as ExtensionBase
from zLOG import LOG, ERROR, BLATHER
from zLOG import LOG, ERROR, INFO
def _import_class(classpath):
try:
......@@ -57,22 +58,49 @@ def _import_class(classpath):
raise ImportError('Could not import document class %s' % classpath)
def _create_accessor_holder_class(property_sheet_tool,
property_sheet_module,
property_sheet_name):
"""
If the given Property Sheet exists in portal_property_sheets, then
generate its accessor holder
"""
try:
return property_sheet_tool.createPropertySheetAccessorHolder(
getattr(property_sheet_tool,
property_sheet_name))
except AttributeError:
# Not too critical
LOG("ERP5Type.Dynamic", ERROR,
"Ignoring missing Property Sheet " + property_sheet_name)
def _fill_accessor_holder_list(accessor_holder_list,
create_accessor_holder_func,
property_sheet_name_set,
accessor_holder_module,
property_sheet_module):
"""
Fill the accessor holder list with the given Property Sheets (which
could be coming either from the filesystem or ZODB)
"""
for property_sheet_name in property_sheet_name_set:
try:
# Get the already generated accessor holder
accessor_holder_list.append(getattr(accessor_holder_module,
property_sheet_name))
except AttributeError:
# Generate the accessor holder as it has not been done yet
try:
accessor_holder_class = \
create_accessor_holder_func(getattr(property_sheet_module,
property_sheet_name))
except AttributeError:
# Not too critical
LOG("ERP5Type.Dynamic", ERROR,
"Ignoring missing Property Sheet " + property_sheet_name)
else:
setattr(accessor_holder_module, property_sheet_name,
accessor_holder_class)
accessor_holder_list.append(accessor_holder_class)
return None
LOG("ERP5Type.Dynamic", INFO,
"Created accessor holder for %s in %s" % (property_sheet_name,
accessor_holder_module))
def portal_type_factory(portal_type_name):
"""
......@@ -134,33 +162,27 @@ def portal_type_factory(portal_type_name):
"Could not load interfaces or Mixins for portal type %s" \
% portal_type)
# Initialize Property Sheets accessor holders
import erp5.accessor_holder
# Get the ZODB Property Sheets for this Portal Type
property_sheet_name_set = set(
portal_type.getNewStyleTypePropertySheetList() or [])
import erp5
for property_sheet_name in property_sheet_name_set:
try:
# Get the already generated accessor holder
accessor_holder_list.append(getattr(erp5.accessor_holder,
property_sheet_name))
except AttributeError:
# Generate the accessor holder as it has not been done yet
accessor_holder_class = \
_create_accessor_holder_class(site.portal_property_sheets,
property_sheet_name)
if accessor_holder_class is not None:
setattr(erp5.accessor_holder, property_sheet_name,
accessor_holder_class)
# Initialize filesystem Property Sheets accessor holders
_fill_accessor_holder_list(
accessor_holder_list,
site.portal_property_sheets.createFilesystemPropertySheetAccessorHolder,
set(portal_type.getTypePropertySheetList() or ()),
erp5.filesystem_accessor_holder,
FilesystemPropertySheet)
accessor_holder_list.append(accessor_holder_class)
# Initialize ZODB Property Sheets accessor holders
_fill_accessor_holder_list(
accessor_holder_list,
site.portal_property_sheets.createZodbPropertySheetAccessorHolder,
set(portal_type.getTypeZodbPropertySheetList() or ()),
erp5.zodb_accessor_holder,
site.portal_property_sheets)
LOG("ERP5Type.Dynamic", BLATHER,
"Created accessor holder for %s" % property_sheet_name)
LOG("ERP5Type.Dynamic", INFO,
"%s: accessor_holder_list: %s" % (portal_type_name,
accessor_holder_list))
if type_class is not None:
type_class = document_class_registry.get(type_class)
......@@ -176,7 +198,7 @@ def portal_type_factory(portal_type_name):
baseclasses = [type_class] + accessor_holder_list + mixin_class_list
LOG("ERP5Type.Dynamic", BLATHER,
LOG("ERP5Type.Dynamic", INFO,
"Portal type %s loaded with bases %s" \
% (portal_type_name, repr(baseclasses)))
......@@ -193,8 +215,13 @@ def initializeDynamicModules():
holds document classes that have no physical import path,
for example classes created through ClassTool that are in
$INSTANCE_HOME/Document
erp5.accessor_holder
holds accessors of ZODB Property Sheet
erp5.zodb_accessor_holder
holds accessors of ZODB Property Sheets
erp5.filesystem_accessor_holder
holds accessors of filesystem Property Sheets
XXX: there should be only one accessor_holder once the code is
stable and all the Property Sheets have been migrated
"""
def portal_type_loader(portal_type_name):
"""
......@@ -206,8 +233,11 @@ def initializeDynamicModules():
sys.modules["erp5"] = erp5
erp5.document = ModuleType("erp5.document")
sys.modules["erp5.document"] = erp5.document
erp5.accessor_holder = ModuleType("erp5.accessor_holder")
sys.modules["erp5.accessor_holder"] = erp5.accessor_holder
erp5.zodb_accessor_holder = ModuleType("erp5.zodb_accessor_holder")
sys.modules["erp5.zodb_accessor_holder"] = erp5.zodb_accessor_holder
erp5.filesystem_accessor_holder = ModuleType("erp5.filesystem_accessor_holder")
sys.modules["erp5.filesystem_accessor_holder"] = erp5.filesystem_accessor_holder
portal_type_container = dynamicmodule.dynamicmodule('erp5.portal_type',
portal_type_loader)
......@@ -249,6 +279,18 @@ def initializeDynamicModules():
erp5.temp_portal_type = dynamicmodule.dynamicmodule('erp5.temp_portal_type',
temp_portal_type_loader)
def _clear_accessor_holder_module(module):
"""
Clear the given accessor holder module (either for filesystem or
ZODB)
XXX: Merge into synchronizeDynamicModules as soon as we get rid of
these two accessor holder modules
"""
for property_sheet_id in module.__dict__.keys():
if not property_sheet_id.startswith('__'):
delattr(module, property_sheet_id)
last_sync = 0
def synchronizeDynamicModules(context, force=False):
"""
......@@ -262,7 +304,7 @@ def synchronizeDynamicModules(context, force=False):
and send out an invalidation to other nodes
"""
return # XXX disabled for now
LOG("ERP5Type.Dynamic", BLATHER, "Resetting dynamic classes")
LOG("ERP5Type.Dynamic", INFO, "Resetting dynamic classes")
portal = context.getPortalObject()
......@@ -291,6 +333,7 @@ def synchronizeDynamicModules(context, force=False):
type(ExtensionBase).__init__(klass, klass)
# Clear accessor holders of ZODB Property Sheets
for accessor_name in erp5.accessor_holder.__dict__.keys():
if not accessor_name.startswith('__'):
delattr(erp5.accessor_holder, accessor_name)
_clear_accessor_holder_module(erp5.zodb_accessor_holder)
# Clear accessor holders of filesystem Property Sheets
_clear_accessor_holder_module(erp5.filesystem_accessor_holder)
......@@ -38,12 +38,6 @@ from Products.ERP5Type.Utils import setDefaultClassProperties, setDefaultPropert
from zLOG import LOG, ERROR, BLATHER
class AccessorHolder(object):
"""
Base class of an accessor holder class for Property Sheets
"""
pass
class PropertySheetTool(BaseTool):
"""
Provides a configurable registry of property sheets
......@@ -87,8 +81,6 @@ class PropertySheetTool(BaseTool):
"""
Create a new Property Sheet in portal_property_sheets from a given
filesystem-based Property Sheet definition.
XXX: Implement constraints
"""
new_property_sheet = self.newContent(id=klass.__name__,
portal_type='Property Sheet')
......@@ -151,7 +143,8 @@ class PropertySheetTool(BaseTool):
Export a given ZODB Property Sheet to its filesystem definition as
tuple (properties, categories, constraints)
XXX: Implement constraints
XXX: Move this code and the accessor generation code (from Utils)
within their respective documents
"""
properties = []
constraints = []
......@@ -170,26 +163,21 @@ class PropertySheetTool(BaseTool):
elif portal_type == "Dynamic Category Property":
categories.append(property.exportToFilesystemDefinition())
return (properties, categories, constraints, )
elif portal_type.endswith('Constraint'):
from Acquisition import aq_base
constraints.append(aq_base(property.asContext()))
security.declarePrivate('createPropertySheetAccessorHolder')
def createPropertySheetAccessorHolder(self, property_sheet):
"""
Create a new accessor holder from the given Property Sheet (the
accessors are created through a Property Holder)
return (properties, categories, constraints)
XXX: Workflows?
def _createCommonPropertySheetAccessorHolder(self,
property_holder,
property_sheet_id,
accessor_holder_module_name):
"""
Create a new accessor holder class from the given Property Holder
within the given accessor holder module (when the migration will
be finished, there should only be one accessor holder module)
"""
property_holder = PropertyHolder()
definition_tuple = \
self.exportPropertySheetToFilesystemDefinitionTuple(property_sheet)
# Prepare the Property Holder
property_holder._properties, \
property_holder._categories, \
property_holder._constraints = definition_tuple
setDefaultClassProperties(property_holder)
try:
......@@ -197,16 +185,26 @@ class PropertySheetTool(BaseTool):
except:
import traceback
LOG("Tool.PropertySheetTool", ERROR,
"Could not generate accessor holder class for %s: %s" % \
(property_sheet.getTitle(), traceback.format_exc()))
"Could not generate accessor holder class for %s (module=%s): %s" %\
(property_sheet_id,
accessor_holder_module_name,
traceback.format_exc()))
return None
# Create the new accessor holder class and set its module properly
accessor_holder_class = type(property_sheet.getId(),
(AccessorHolder,), {})
accessor_holder_class.__module__ = 'erp5.accessor_holder'
accessor_holder_class = type(property_sheet_id, (object,), dict(
__module__ = accessor_holder_module_name,
constraints = property_holder.constraints,
# The following attributes have been defined only because they
# are being used in ERP5Type.Utils when getting all the
# property_sheets of the property_holder (then, they are added
# to the local properties, categories and constraints lists)
_properties = property_holder._properties,
# Necessary for getBaseCategoryList
_categories = property_holder._categories,
_constraints = property_holder._constraints
))
# Set all the accessors (defined by a tuple) from the Property
# Holder to the new accessor holder class (code coming from
......@@ -227,3 +225,46 @@ class PropertySheetTool(BaseTool):
setattr(accessor_holder_class, id, accessor)
return accessor_holder_class
security.declarePrivate('createFilesystemPropertySheetAccessorHolder')
def createFilesystemPropertySheetAccessorHolder(self, property_sheet):
"""
Create a new accessor holder from the given filesystem Property
Sheet (the accessors are created through a Property Holder)
XXX: Workflows?
XXX: Remove as soon as the migration is finished
"""
property_holder = PropertyHolder()
property_holder._properties = getattr(property_sheet, '_properties', [])
property_holder._categories = getattr(property_sheet, '_categories', [])
property_holder._constraints = getattr(property_sheet, '_constraints', [])
return self._createCommonPropertySheetAccessorHolder(
property_holder,
property_sheet.__name__,
'erp5.filesystem_accessor_holder')
security.declarePrivate('createZodbPropertySheetAccessorHolder')
def createZodbPropertySheetAccessorHolder(self, property_sheet):
"""
Create a new accessor holder from the given ZODB Property Sheet
(the accessors are created through a Property Holder)
XXX: Workflows?
"""
definition_tuple = \
self.exportPropertySheetToFilesystemDefinitionTuple(property_sheet)
property_holder = PropertyHolder()
# Prepare the Property Holder
property_holder._properties, \
property_holder._categories, \
property_holder._constraints = definition_tuple
return self._createCommonPropertySheetAccessorHolder(
property_holder,
property_sheet.getId(),
'erp5.zodb_accessor_holder')
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