diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index 4cda1d814ecd34a3ba3b7f250a62aaa87628c0aa..35c97e40fec5c1984114e45c98cadce16ad1c01a 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -325,92 +325,21 @@ class PropertyHolder(object): return [x for x in self.__dict__.items() if x[0] not in PropertyHolder.RESERVED_PROPERTY_SET] - # Accessor generation - def createAccessor(self, id): - """ - Invokes appropriate factory and create an accessor - """ - fake_accessor = getattr(self, id) - ptype = getattr(self, 'portal_type', None) - if ptype is None: - ptype = self._portal_type - if fake_accessor is PropertyHolder.WORKFLOW_METHOD_MARKER: - # Case 1 : a workflow method only - accessor = Base._doNothing - else: - # Case 2 : a workflow method over an accessor - (accessor_class, accessor_args, key) = fake_accessor - accessor = accessor_class(id, key, *accessor_args) - for wf_id, tr_id, once in self.workflow_method_registry.get(id, ()): - if not isinstance(accessor, WorkflowMethod): - accessor = WorkflowMethod(accessor) - if once: - accessor.registerTransitionOncePerTransaction(ptype, wf_id, tr_id) - else: - accessor.registerTransitionAlways(ptype, wf_id, tr_id) - else: - if once: - accessor.registerTransitionOncePerTransaction(ptype, wf_id, tr_id) - else: - accessor.registerTransitionAlways(ptype, wf_id, tr_id) - setattr(self, id, accessor) - return accessor - - def registerAccessor(self, id, key, accessor_class, accessor_args): - """ - Saves in a dictionary all parameters required to create an accessor - The goal here is to minimize memory occupation. We have found the following: - - - the size of a tuple with simple types and the size - of None are the same (a pointer) - - - the size of a pointer to a class is the same as the - size of None - - - the python caching system for tuples is efficient for tuples - which contain simple types (string, int, etc.) but innefficient - for tuples which contain a pointer - - - as a result, it is better to create separate dicts if - values contain pointers and single dict if value is - a tuple of simple types - - Parameters: - - id -- The id the accessor (ex. getFirstName) - - key -- The id of the property (ex. first_name) or the id of the - method for Alias accessors - """ - #LOG('registerAccessor', 0, "%s %s %s" % (id , self._portal_type, accessor_args)) - # First we try to compress the information required - # to build a new accessor in such way that - # if the same information is provided twice, we - # shall keep it once only - new_accessor_args = [] - for arg in accessor_args: - if type(arg) is types.ListType: - new_accessor_args.append(tuple(arg)) - else: - new_accessor_args.append(arg) - accessor_args = tuple(new_accessor_args) - original_registration_tuple = (accessor_class, accessor_args, key) - registration_tuple = method_registration_cache.get(original_registration_tuple) - if registration_tuple is None: - registration_tuple = original_registration_tuple - method_registration_cache[registration_tuple] = registration_tuple - # Use the cached tuple (same value, different pointer) - setattr(self, id, registration_tuple) - def registerWorkflowMethod(self, id, wf_id, tr_id, once_per_transaction=0): - #LOG('registerWorkflowMethod', 0, "%s %s %s %s %s" % (self._portal_type, id, wf_id, tr_id, once_per_transaction)) - signature = (wf_id, tr_id, once_per_transaction) - signature_list = self.workflow_method_registry.get(id, ()) - if signature not in signature_list: - self.workflow_method_registry[id] = signature_list + (signature,) - if getattr(self, id, None) is None: - setattr(self, id, PropertyHolder.WORKFLOW_METHOD_MARKER) - self.createAccessor(id) + portal_type = self.portal_type + + workflow_method = getattr(self, id, None) + if workflow_method is None: + workflow_method = WorkflowMethod(Base._doNothing) + setattr(self, id, workflow_method) + if once_per_transaction: + workflow_method.registerTransitionOncePerTransaction(portal_type, + wf_id, + tr_id) + else: + workflow_method.registerTransitionAlways(portal_type, + wf_id, + tr_id) def declareProtected(self, permission, accessor_name): """ @@ -520,23 +449,6 @@ def getClassPropertyList(klass): if p not in ps_list]) return ps_list -def initializeClassDynamicProperties(self, klass): - if klass not in Base.aq_method_generated: - # Recurse to superclasses - for super_klass in klass.__bases__: - if getattr(super_klass, 'isRADContent', 0): - initializeClassDynamicProperties(self, super_klass) - # Initialize default properties - from Utils import setDefaultClassProperties - if not getattr(klass, 'isPortalContent', None): - if getattr(klass, 'isRADContent', 0): - setDefaultClassProperties(klass) - # Mark as generated - Base.aq_method_generated.add(klass) - -def initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal): - raise ValueError("No reason to go through this no more with portal type classes") - def initializePortalTypeDynamicWorkflowMethods(ptype_klass, portal_workflow): """We should now make sure workflow methods are defined and also make sure simulation state is defined.""" @@ -567,10 +479,8 @@ def initializePortalTypeDynamicWorkflowMethods(ptype_klass, portal_workflow): if not hasattr(ptype_klass, method_id): method = getter(method_id, wf_id) # Attach to portal_type - setattr(ptype_klass, method_id, method) - ptype_klass.security.declareProtected( - Permissions.AccessContentsInformation, - method_id ) + ptype_klass.registerAccessor(method, + Permissions.AccessContentsInformation) storage = dc_workflow_dict transitions = wf.transitions @@ -806,12 +716,6 @@ class Base( CopyContainer, self._setDescription(value) self.reindexObject() - security.declareProtected( Permissions.AccessContentsInformation, 'test_dyn' ) - def test_dyn(self): - """ - """ - initializeClassDynamicProperties(self, self.__class__) - security.declarePublic('provides') def provides(cls, interface_name): """ @@ -848,18 +752,6 @@ class Base( CopyContainer, pformat(rev1.__dict__), pformat(rev2.__dict__))) - def initializePortalTypeDynamicProperties(self): - """ - Test purpose - """ - ptype = self.portal_type - klass = self.__class__ - aq_key = self._aq_key() - initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, \ - self.getPortalObject()) - from Products.ERP5Form.PreferenceTool import createPreferenceToolAccessorList - createPreferenceToolAccessorList(self.getPortalObject()) - def _aq_dynamic(self, id): # ahah! disabled, thanks to portal type classes return None diff --git a/product/ERP5Type/Core/PropertySheet.py b/product/ERP5Type/Core/PropertySheet.py index 100fcc945f182f5e6d319c8c767e381302d933fc..8820d2564e3bb9c562b48aef3e6baf24787fba96 100644 --- a/product/ERP5Type/Core/PropertySheet.py +++ b/product/ERP5Type/Core/PropertySheet.py @@ -48,6 +48,7 @@ class PropertySheet(Folder): security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) + # TODO: REMOVE security.declareProtected(Permissions.AccessContentsInformation, 'exportToFilesystemDefinition') def exportToFilesystemDefinition(self): @@ -86,22 +87,15 @@ class PropertySheet(Folder): return (properties, categories, constraints) security.declarePrivate('createAccessorHolder') - def createAccessorHolder(self): + def createAccessorHolder(self, expression_context, portal): """ - Create a new accessor holder from the Property Sheet (the - accessors are created through a Property Holder) + Create a new accessor holder from the Property Sheet """ - property_holder = PropertyHolder(self.getId()) + accessor_holder = AccessorHolderType(self.getId()) - # Prepare the Property Holder - property_holder._properties, \ - property_holder._categories, \ - property_holder._constraints = self.exportToFilesystemDefinition() + self.applyOnAccessorHolder(accessor_holder, expression_context, portal) - return AccessorHolderType.fromPropertyHolder( - property_holder, - self.getPortalObject(), - 'erp5.accessor_holder') + return accessor_holder @staticmethod def _guessFilesystemPropertyPortalType(attribute_dict): diff --git a/product/ERP5Type/dynamic/accessor_holder.py b/product/ERP5Type/dynamic/accessor_holder.py index 9ea8af4e82c27c7116a3f46b8ec03ad53a6fc3ef..9a80ef6b9e9db846414469cdf82db1f8e4379e26 100644 --- a/product/ERP5Type/dynamic/accessor_holder.py +++ b/product/ERP5Type/dynamic/accessor_holder.py @@ -33,102 +33,74 @@ Accessor Holders, that is, generation of methods for ERP5 * Utils, Property Sheet Tool can be probably be cleaned up as well by moving specialized code here. """ -import sys +from types import ModuleType from Products.ERP5Type import Permissions -from Products.ERP5Type.Base import PropertyHolder, Base -from Products.ERP5Type.Utils import createRelatedAccessors, createExpressionContext -from Products.ERP5Type.Utils import setDefaultClassProperties, setDefaultProperties +from Products.ERP5Type.Utils import createExpressionContext from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Utils import UpperCase from Products.ERP5Type.Accessor import Related, RelatedValue +from AccessControl import ClassSecurityInfo -from zLOG import LOG, ERROR, INFO +from zLOG import LOG, ERROR, INFO, WARNING class AccessorHolderType(type): _skip_permission_tuple = (Permissions.AccessContentsInformation, Permissions.ModifyPortalContent) - def _next_registerAccessor(cls, + def registerAccessor(cls, accessor, - permission): + permission=None): accessor_name = accessor.__name__ setattr(cls, accessor_name, accessor) + if permission is None: + return # private accessors do not need declarative security if accessor_name[0] != '_' and \ permission not in AccessorHolderType._skip_permission_tuple: cls.security.declareProtected(permission, accessor_name) - @classmethod - def fromPropertyHolder(meta_type, - property_holder, - portal=None, - accessor_holder_module_name=None, - initialize=True): + def __new__(meta_class, class_name, base_tuple=(object,), attribute_dict={}): + # we dont want to add several times to the same list, so make sure + # that duplicate attributes just point to the same list object + constraint_list = [] + attribute_dict.update(_categories=[], + _constraints=constraint_list, + constraints=constraint_list, + security=ClassSecurityInfo(), + _properties=[]) + + return super(AccessorHolderType, meta_class).__new__(meta_class, + class_name, + base_tuple, + attribute_dict) + + def _finalize(cls): + cls.security.apply(cls) + InitializeClass(cls) + +class AccessorHolderModuleType(ModuleType): + def registerAccessorHolder(self, accessor_holder): """ - Create a new accessor holder class from the given Property Holder - within the given accessor holder module + Add an accessor holder to the module """ - property_sheet_id = property_holder.__name__ - context = portal.portal_property_sheets - if initialize: - setDefaultClassProperties(property_holder) + # Set the module of the given accessor holder properly + accessor_holder.__module__ = self.__name__ - try: - setDefaultProperties(property_holder, - object=context, - portal=portal) - except: - LOG("Tool.PropertySheetTool", ERROR, - "Could not generate accessor holder class for %s (module=%s)" % \ - (property_sheet_id, accessor_holder_module_name), - error=sys.exc_info()) - - raise - - # Create the new accessor holder class and set its module properly - accessor_holder_class = meta_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, - security = property_holder.security - )) - - # Set all the accessors (defined by a tuple) from the Property - # Holder to the new accessor holder class (code coming from - # createAccessor in Base.PropertyHolder) - for id, fake_accessor in property_holder._getPropertyHolderItemList(): - if callable(fake_accessor): - # not so fake ;) - setattr(accessor_holder_class, id, fake_accessor) - continue - if not isinstance(fake_accessor, tuple): - continue - - if fake_accessor is PropertyHolder.WORKFLOW_METHOD_MARKER: - # Case 1 : a workflow method only - accessor = Base._doNothing - else: - # Case 2 : a workflow method over an accessor - (accessor_class, accessor_args, key) = fake_accessor - accessor = accessor_class(id, key, *accessor_args) + # Finalize the class as no accessors is added from now on + accessor_holder._finalize() - # Add the accessor to the accessor holder - setattr(accessor_holder_class, id, accessor) + self.__setattr__(accessor_holder.__name__, accessor_holder) - property_holder.security.apply(accessor_holder_class) - InitializeClass(accessor_holder_class) - return accessor_holder_class + def clear(self): + """ + Clear the content of the module + """ + for klass in self.__dict__.values(): + if isinstance(klass, AccessorHolderType): + delattr(self, klass.__name__) -def _generateBaseAccessorHolder(portal, - accessor_holder_module): +def _generateBaseAccessorHolder(portal): """ Create once an accessor holder that contains all accessors common to all portal types: erp5.accessor_holder.BaseAccessorHolder @@ -142,83 +114,31 @@ def _generateBaseAccessorHolder(portal, class added to a portal type class, and that it will always be added, to all living ERP5 objects. """ + import erp5.accessor_holder + base_accessor_holder_id = 'BaseAccessorHolder' - accessor_holder = getattr(accessor_holder_module, - base_accessor_holder_id, - None) - if accessor_holder is not None: - return accessor_holder + try: + return getattr(erp5.accessor_holder, base_accessor_holder_id) + except AttributeError: + # The accessor holder does not already exist + pass # When setting up the site, there will be no portal_categories - portal_categories = getattr(portal, 'portal_categories', None) - if portal_categories is None: + category_tool = getattr(portal, 'portal_categories', None) + if category_tool is None: return None - base_category_list = portal_categories.objectIds() + base_category_id_list = category_tool.objectIds() - property_holder = PropertyHolder(base_accessor_holder_id) + accessor_holder = AccessorHolderType(base_accessor_holder_id) - econtext = createExpressionContext(portal_categories, portal) - createRelatedAccessors(portal_categories, - property_holder, - econtext, - base_category_list) + for base_category_id in base_category_id_list: + applyCategoryAsRelatedValueAccessor(accessor_holder, + base_category_id, + category_tool) - accessor_holder = AccessorHolderType.fromPropertyHolder( - property_holder, - portal, - 'erp5.accessor_holder', - initialize=False) - setattr(accessor_holder_module, base_accessor_holder_id, accessor_holder) - return accessor_holder - -def _generatePreferenceToolAccessorHolder(portal, accessor_holder_list, - accessor_holder_module): - """ - Generate a specific Accessor Holder that will be put on the Preference Tool. - (This used to happen in ERP5Form.PreferenceTool._aq_dynamic) - - We iterate over all properties that do exist on the system, select the - preferences out of those, and generate the getPreferred.* accessors. - """ - property_holder = PropertyHolder('PreferenceTool') - - from Products.ERP5Type.Accessor.TypeDefinition import list_types - from Products.ERP5Type.Utils import convertToUpperCase - from Products.ERP5Form.PreferenceTool import PreferenceMethod - - for accessor_holder in accessor_holder_list: - for prop in accessor_holder._properties: - if not prop.get('preference'): - continue - # XXX read_permission and write_permissions defined at - # property sheet are not respected by this. - # only properties marked as preference are used - - # properties have already been 'converted' and _list is appended - # to list_types properties - attribute = prop['id'] - if attribute.endswith('_list'): - attribute = prop['base_id'] - attr_list = [ 'get%s' % convertToUpperCase(attribute)] - if prop['type'] == 'boolean': - attr_list.append('is%s' % convertToUpperCase(attribute)) - if prop['type'] in list_types : - attr_list.append('get%sList' % convertToUpperCase(attribute)) - read_permission = prop.get('read_permission') - for attribute_name in attr_list: - method = PreferenceMethod(attribute_name, prop.get('default')) - setattr(property_holder, attribute_name, method) - if read_permission: - property_holder.declareProtected(read_permission, attribute_name) - - accessor_holder = AccessorHolderType.fromPropertyHolder( - property_holder, - portal, - 'erp5.accessor_holder', - initialize=False) - setattr(accessor_holder_module, 'PreferenceTool', accessor_holder) + erp5.accessor_holder.registerAccessorHolder(accessor_holder) return accessor_holder related_accessor_definition_dict = { @@ -417,3 +337,94 @@ def getAccessorHolderList(site, portal_type_name, property_sheet_value_list): # "Created accessor holder for %s" % property_sheet_name) return accessor_holder_list + +from Products.ERP5Type.Base import getClassPropertyList + +def createAllAccessorHolderList(site, + portal_type_name, + portal_type, + type_class): + """ + Create the accessor holder list with the given ZODB Property Sheets + """ + from erp5 import accessor_holder as accessor_holder_module + + property_sheet_name_set = set() + accessor_holder_list = [] + + # Get the accessor holders of the Portal Type + if portal_type is not None: + accessor_holder_list.extend(portal_type.getAccessorHolderList()) + + portal_type_property_sheet_name_set = set( + [ accessor_holder.__name__ for accessor_holder in accessor_holder_list ]) + + else: + portal_type_property_sheet_name_set = set() + + # XXX: Only kept for backward-compatibility as Preference and System + # Preference have Preference Type as portal type, which define + # getTypePropertySheetList properly and, likewise, Preference Tool + # has Preference Tool Type as its portal type + if portal_type_name in ("Preference Tool", + "Preference", + "System Preference"): + if portal_type is None or \ + not portal_type.getPortalType().startswith(portal_type_name): + # The Property Sheet Tool may be None if the code is updated but + # the BT has not been upgraded yet with portal_property_sheets + try: + zodb_property_sheet_name_set = set(site.portal_property_sheets.objectIds()) + + except AttributeError: + if not getattr(site, '_v_bootstrapping', False): + LOG("ERP5Type.dynamic", WARNING, + "Property Sheet Tool was not found. Please update erp5_core " + "Business Template") + + else: + for property_sheet in zodb_property_sheet_name_set: + if property_sheet.endswith('Preference'): + property_sheet_name_set.add(property_sheet) + + # XXX a hook to add per-portal type accessor holders maybe? + if portal_type_name == "Preference Tool": + from Products.ERP5Form.Document.PreferenceToolType import \ + _generatePreferenceToolAccessorHolder + + accessor_holder_class = _generatePreferenceToolAccessorHolder( + portal_type_name, accessor_holder_list) + + accessor_holder_list.insert(0, accessor_holder_class) + + # Get the Property Sheets defined on the document and its bases + # recursively + for property_sheet in getClassPropertyList(type_class): + # If the Property Sheet is a string, then this is a ZODB + # Property Sheet + # + # NOTE: The Property Sheets of a document should be given as a + # string from now on + if not isinstance(property_sheet, basestring): + property_sheet = property_sheet.__name__ + + property_sheet_name_set.add(property_sheet) + + property_sheet_name_set = property_sheet_name_set - \ + portal_type_property_sheet_name_set + + document_accessor_holder_list = \ + getAccessorHolderList(site, portal_type_name, + getPropertySheetValueList(site, + property_sheet_name_set)) + + accessor_holder_list.extend(document_accessor_holder_list) + + # useless if Base Category is not yet here or if we're + # currently generating accessors for Base Categories + accessor_holder_class = _generateBaseAccessorHolder(site) + + if accessor_holder_class is not None: + accessor_holder_list.append(accessor_holder_class) + + return accessor_holder_list diff --git a/product/ERP5Type/dynamic/lazy_class.py b/product/ERP5Type/dynamic/lazy_class.py index c92078d976ddb98384dca83c180d782d710ee5bc..173135edaa8188ba422593521cbbbf6b7dcb7633 100644 --- a/product/ERP5Type/dynamic/lazy_class.py +++ b/product/ERP5Type/dynamic/lazy_class.py @@ -7,8 +7,8 @@ from Products.ERP5Type.Accessor.Constant import Getter as ConstantGetter from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Base import Base as ERP5Base from Products.ERP5Type.Base import PropertyHolder, initializePortalTypeDynamicWorkflowMethods -from Products.ERP5Type.Utils import createAllCategoryAccessors, \ - createExpressionContext, UpperCase, setDefaultProperties +from Products.ERP5Type.Utils import UpperCase +from Products.ERP5Type.Core.CategoryProperty import CategoryProperty from ExtensionClass import ExtensionClass, pmc_init_of from zope.interface import classImplements @@ -130,11 +130,11 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): cls.security = ClassSecurityInfo() @classmethod - def getSubclassList(metacls, cls): + def getSubclassList(meta_class, cls): """ Returns classes deriving from cls """ - return metacls.subclass_register.get(cls, []) + return meta_class.subclass_register.get(cls, []) def getAccessorHolderPropertyList(cls): """ @@ -145,10 +145,12 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): """ cls.loadClass() property_dict = {} + for klass in cls.mro(): - if klass.__module__ == 'erp5.accessor_holder': + if klass.__module__.startswith('erp5.accessor_holder'): for property in klass._properties: property_dict.setdefault(property['id'], property) + return property_dict.values() def resetAcquisition(cls): @@ -210,36 +212,12 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): raise AttributeError def generatePortalTypeAccessors(cls, site, portal_type_category_list): - createAllCategoryAccessors(site, - cls, - portal_type_category_list, - createExpressionContext(site, site)) - - # Properties defined on the portal type itself are generated in - # erp5.portal_type directly, but this is unusual case (only - # PDFTypeInformation seems to use it) - portal_type_property_list = getattr(cls, '_properties', None) - if portal_type_property_list: - setDefaultProperties(cls) - - # make sure that category accessors from the portal type definition - # are generated, no matter what - # XXX this code is duplicated here, in PropertySheetTool, and in Base - # and anyway is ugly, as tuple-like registration does not help - for id, fake_accessor in cls._getPropertyHolderItemList(): - if not isinstance(fake_accessor, tuple): - continue - - if fake_accessor is PropertyHolder.WORKFLOW_METHOD_MARKER: - # Case 1 : a workflow method only - accessor = ERP5Base._doNothing - else: - # Case 2 : a workflow method over an accessor - (accessor_class, accessor_args, key) = fake_accessor - accessor = accessor_class(id, key, *accessor_args) - - # Add the accessor to the accessor holder - setattr(cls, id, accessor) + category_tool = getattr(site, 'portal_categories', None) + for category_id in portal_type_category_list: + # we need to generate only categories defined on portal type + CategoryProperty.applyDefinitionOnAccessorHolder(cls, + category_id, + category_tool) portal_workflow = getattr(site, 'portal_workflow', None) if portal_workflow is None: @@ -259,9 +237,8 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): for group in ERP5TypeInformation.defined_group_list: value = cls.__name__ in site._getPortalGroupedTypeSet(group) accessor_name = 'is' + UpperCase(group) + 'Type' - setattr(cls, accessor_name, ConstantGetter(accessor_name, group, value)) - cls.declareProtected(Permissions.AccessContentsInformation, - accessor_name) + method = ConstantGetter(accessor_name, group, value) + cls.registerAccessor(method, Permissions.AccessContentsInformation) from Products.ERP5Type.Cache import initializePortalCachingProperties initializePortalCachingProperties(site) @@ -274,7 +251,7 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): cls.loadClass() result = PropertyHolder._getPropertyHolderItemList(cls) for parent in cls.mro(): - if parent.__module__ == 'erp5.accessor_holder': + if parent.__module__.startswith('erp5.accessor_holder'): for x in parent.__dict__.items(): if x[0] not in PropertyHolder.RESERVED_PROPERTY_SET: result.append(x) diff --git a/product/ERP5Type/dynamic/portal_type_class.py b/product/ERP5Type/dynamic/portal_type_class.py index 2ca935db6df43f9cd932d311438f2af0c11e1d55..d6539705221ee73040b0c3d726f61e32dcf29a49 100644 --- a/product/ERP5Type/dynamic/portal_type_class.py +++ b/product/ERP5Type/dynamic/portal_type_class.py @@ -32,16 +32,14 @@ import os import inspect from types import ModuleType -from dynamic_module import registerDynamicModule -from accessor_holder import _generateBaseAccessorHolder, \ - _generatePreferenceToolAccessorHolder - +from Products.ERP5Type.dynamic.dynamic_module import registerDynamicModule from Products.ERP5Type.mixin.temporary import TemporaryDocumentMixin from Products.ERP5Type.Base import Base, resetRegisteredWorkflowMethod 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.Accessor.Constant import PropertyGetter as ConstantGetter +from Products.ERP5Type.dynamic.accessor_holder import AccessorHolderModuleType, \ + createAllAccessorHolderList from zLOG import LOG, ERROR, INFO, WARNING @@ -59,71 +57,6 @@ def _importClass(classpath): except StandardError: raise ImportError('Could not import document class %s' % classpath) -def _createAccessorHolderList(site, - portal_type_name, - property_sheet_name_set): - """ - Create the accessor holder list with the given ZODB Property Sheets - """ - from erp5 import accessor_holder - - getPropertySheet = site.portal_property_sheets._getOb - accessor_holder_list = [] - - if "Base" in property_sheet_name_set: - # useless if Base Category is not yet here or if we're currently - # generating accessors for Base Categories - accessor_holder_class = _generateBaseAccessorHolder(site, accessor_holder) - - if accessor_holder_class is not None: - accessor_holder_list.append(accessor_holder_class) - - for property_sheet_name in property_sheet_name_set: - # LOG("ERP5Type.dynamic", INFO, - # "Getting accessor holder for " + property_sheet_name) - - try: - # Get the already generated accessor holder - accessor_holder_list.append(getattr(accessor_holder, property_sheet_name)) - - except AttributeError: - try: - property_sheet = getPropertySheet(property_sheet_name) - except KeyError: - LOG("ERP5Type.dynamic", WARNING, - "Ignoring missing Property Sheet " + property_sheet_name) - - continue - - # Generate the accessor holder as it has not been done yet - try: - accessor_holder_class = property_sheet.createAccessorHolder() - except Exception: - LOG("ERP5Type.dynamic", ERROR, - "Invalid Property Sheet " + property_sheet_name) - raise - - accessor_holder_list.append(accessor_holder_class) - - setattr(accessor_holder, property_sheet_name, accessor_holder_class) - - # LOG("ERP5Type.dynamic", INFO, - # "Created accessor holder for %s" % property_sheet_name) - - # XXX a hook to add per-portal type accessor holders maybe? - if portal_type_name == "Preference Tool": - accessor_holder_class = \ - _generatePreferenceToolAccessorHolder(site, - accessor_holder_list, - accessor_holder) - - accessor_holder_list.insert(0, accessor_holder_class) - - # LOG("ERP5Type.dynamic", INFO, - # "Got accessor holder for %s: %s" % (property_sheet_name, accessor_holder_list)) - - return accessor_holder_list - # Loading Cache Factory portal type would generate the accessor holder # for Cache Factory, itself defined with Standard Property thus # loading the portal type Standard Property, itself defined with @@ -177,7 +110,6 @@ def generatePortalTypeClass(site, portal_type_name): portal_type_category_list = [] attribute_dict = dict(portal_type=portal_type_name, - _properties=[], _categories=[], constraints=[]) @@ -269,80 +201,18 @@ def generatePortalTypeClass(site, portal_type_name): property_sheet_generating_portal_type_set.add(portal_type_name) - property_sheet_tool = getattr(site, 'portal_property_sheets', None) - - property_sheet_name_set = set() - - # The Property Sheet Tool may be None if the code is updated but - # the BT has not been upgraded yet with portal_property_sheets - if property_sheet_tool is None: - if not getattr(site, '_v_bootstrapping', False): - LOG("ERP5Type.dynamic", WARNING, - "Property Sheet Tool was not found. Please update erp5_core " - "Business Template") - zodb_property_sheet_name_set = set() - else: - zodb_property_sheet_name_set = set(property_sheet_tool.objectIds()) - if portal_type is not None: - # Get the Property Sheets defined on the portal_type and use the - # ZODB Property Sheet rather than the filesystem - for property_sheet in portal_type.getTypePropertySheetList(): - if property_sheet in zodb_property_sheet_name_set: - property_sheet_name_set.add(property_sheet) - - # PDFTypeInformation document class, for example, defines a - # method which generates dynamically properties and this is - # heavily used by egov - update_definition_dict = getattr(portal_type, - 'updatePropertySheetDefinitionDict', - None) - - if update_definition_dict is not None and not \ - update_definition_dict.__module__.startswith('Products.ERP5Type.ERP5Type'): - try: - update_definition_dict(attribute_dict) - except AttributeError: - pass - - # Only kept for backward-compatibility as Preference and System - # Preference have Preference Type as portal type, which define - # getTypePropertySheetList properly and, likewise, Preference Tool - # has Preference Tool Type as its portal type - if portal_type_name in ("Preference Tool", - "Preference", - "System Preference"): - if portal_type is None or \ - not portal_type.getPortalType().startswith(portal_type_name): - for property_sheet in zodb_property_sheet_name_set: - if property_sheet.endswith('Preference'): - property_sheet_name_set.add(property_sheet) - - # Get the Property Sheets defined on the document and its bases - # recursively - from Products.ERP5Type.Base import getClassPropertyList - for property_sheet in getClassPropertyList(klass): - # If the Property Sheet is a string, then this is a ZODB - # Property Sheet - # - # NOTE: The Property Sheets of a document should be given as a - # string from now on - if not isinstance(property_sheet, basestring): - property_sheet = property_sheet.__name__ - if property_sheet in zodb_property_sheet_name_set: - property_sheet_name_set.add(property_sheet) - - if property_sheet_name_set: - # Initialize ZODB Property Sheets accessor holders - accessor_holder_list = _createAccessorHolderList(site, + # Initialize ZODB Property Sheets accessor holders + accessor_holder_list = createAllAccessorHolderList(site, portal_type_name, - property_sheet_name_set) + portal_type, + klass) - base_category_set = set(attribute_dict['_categories']) - for accessor_holder in accessor_holder_list: - base_category_set.update(accessor_holder._categories) - attribute_dict['constraints'].extend(accessor_holder.constraints) + base_category_set = set(attribute_dict['_categories']) + for accessor_holder in accessor_holder_list: + base_category_set.update(accessor_holder._categories) + attribute_dict['constraints'].extend(accessor_holder.constraints) - attribute_dict['_categories'] = list(base_category_set) + attribute_dict['_categories'] = list(base_category_set) property_sheet_generating_portal_type_set.remove(portal_type_name) @@ -388,15 +258,29 @@ def initializeDynamicModules(): for example classes created through ClassTool that are in $INSTANCE_HOME/Document erp5.accessor_holder - holds accessors of ZODB Property Sheets + holds accessor holders common to ZODB Property Sheets and Portal Types + erp5.accessor_holder.property_sheet + holds accessor holders of ZODB Property Sheets + erp5.accessor_holder.portal_type + holds accessors holders of Portal Types """ erp5 = ModuleType("erp5") sys.modules["erp5"] = erp5 erp5.document = ModuleType("erp5.document") sys.modules["erp5.document"] = erp5.document - erp5.accessor_holder = ModuleType("erp5.accessor_holder") + erp5.accessor_holder = AccessorHolderModuleType("erp5.accessor_holder") sys.modules["erp5.accessor_holder"] = erp5.accessor_holder + erp5.accessor_holder.property_sheet = \ + AccessorHolderModuleType("erp5.accessor_holder.property_sheet") + + sys.modules["erp5.accessor_holder.property_sheet"] = \ + erp5.accessor_holder.property_sheet + + erp5.accessor_holder.portal_type = registerDynamicModule( + 'erp5.accessor_holder.portal_type', + AccessorHolderModuleType) + portal_type_container = registerDynamicModule('erp5.portal_type', generateLazyPortalTypeClass) @@ -502,10 +386,14 @@ def synchronizeDynamicModules(context, force=False): inspect.isclass): klass.restoreGhostState() - # Clear accessor holders of ZODB Property Sheets - for property_sheet_id in erp5.accessor_holder.__dict__.keys(): - if not property_sheet_id.startswith('__'): - delattr(erp5.accessor_holder, property_sheet_id) + # Clear accessor holders of ZODB Property Sheets and Portal Types + erp5.accessor_holder.clear() + erp5.accessor_holder.property_sheet.clear() + + for name in erp5.accessor_holder.portal_type.__dict__.keys(): + if name[0] != '_': + delattr(erp5.accessor_holder.portal_type, name) + finally: Base.aq_method_lock.release() diff --git a/product/ERP5Type/tests/testDynamicClassGeneration.py b/product/ERP5Type/tests/testDynamicClassGeneration.py index 4e3c0289e715acf5567d33174ceb2055c0fae5bb..6f0bf9ce72247a98adfb2e40d19611f9f0298cb2 100644 --- a/product/ERP5Type/tests/testDynamicClassGeneration.py +++ b/product/ERP5Type/tests/testDynamicClassGeneration.py @@ -595,14 +595,16 @@ class TestZodbPropertySheet(ERP5TypeTestCase): # The accessor holder will be generated once the new Person will # be created as Person type has test Property Sheet - self.failIfHasAttribute(erp5.accessor_holder, 'TestMigration') + self.failIfHasAttribute(erp5.accessor_holder.property_sheet, + 'TestMigration') new_person = portal.person_module.newContent( id='testAssignZodbPropertySheet', portal_type='Person') - self.assertHasAttribute(erp5.accessor_holder, 'TestMigration') + self.assertHasAttribute(erp5.accessor_holder.property_sheet, + 'TestMigration') - self.assertTrue(erp5.accessor_holder.TestMigration in \ + self.assertTrue(erp5.accessor_holder.property_sheet.TestMigration in \ erp5.portal_type.Person.mro()) # Check that the accessors have been properly created for all @@ -677,7 +679,7 @@ class TestZodbPropertySheet(ERP5TypeTestCase): new_person = portal.person_module.newContent( id='testAssignZodbPropertySheet', portal_type='Person') - self.failIfHasAttribute(erp5.accessor_holder, 'TestMigration') + self.failIfHasAttribute(erp5.accessor_holder.property_sheet, 'TestMigration') self.failIfHasAttribute(new_person, 'getTestStandardPropertyAssign') finally: @@ -687,15 +689,18 @@ class TestZodbPropertySheet(ERP5TypeTestCase): def _checkAddPropertyToZodbPropertySheet(self, new_property_function, added_accessor_name): - import erp5.accessor_holder + import erp5.accessor_holder.property_sheet - self.failIfHasAttribute(erp5.accessor_holder, 'TestMigration') + self.failIfHasAttribute(erp5.accessor_holder.property_sheet, + 'TestMigration') new_property_function('add') self._forceTestAccessorHolderGeneration() - self.assertHasAttribute(erp5.accessor_holder, 'TestMigration') - self.assertHasAttribute(erp5.accessor_holder.TestMigration, + self.assertHasAttribute(erp5.accessor_holder.property_sheet, + 'TestMigration') + + self.assertHasAttribute(erp5.accessor_holder.property_sheet.TestMigration, added_accessor_name) def testAddStandardPropertyToZodbPropertySheet(self): @@ -738,15 +743,18 @@ class TestZodbPropertySheet(ERP5TypeTestCase): change_setter_func, new_value, changed_accessor_name): - import erp5.accessor_holder + import erp5.accessor_holder.property_sheet - self.failIfHasAttribute(erp5.accessor_holder, 'TestMigration') + self.failIfHasAttribute(erp5.accessor_holder.property_sheet, + 'TestMigration') change_setter_func(new_value) self._forceTestAccessorHolderGeneration() - self.assertHasAttribute(erp5.accessor_holder, 'TestMigration') - self.assertHasAttribute(erp5.accessor_holder.TestMigration, + self.assertHasAttribute(erp5.accessor_holder.property_sheet, + 'TestMigration') + + self.assertHasAttribute(erp5.accessor_holder.property_sheet.TestMigration, changed_accessor_name) def testChangeStandardPropertyOfZodbPropertySheet(self): @@ -798,7 +806,7 @@ class TestZodbPropertySheet(ERP5TypeTestCase): Delete the given property from the test Property Sheet and check whether its corresponding accessor is not there anymore """ - import erp5.accessor_holder + import erp5.accessor_holder.property_sheet self.failIfHasAttribute(erp5.accessor_holder, 'TestMigration') @@ -807,8 +815,8 @@ class TestZodbPropertySheet(ERP5TypeTestCase): self.test_property_sheet.deleteContent(property_id) self._forceTestAccessorHolderGeneration() - self.assertHasAttribute(erp5.accessor_holder, 'TestMigration') - self.failIfHasAttribute(erp5.accessor_holder.TestMigration, + self.assertHasAttribute(erp5.accessor_holder.property_sheet, 'TestMigration') + self.failIfHasAttribute(erp5.accessor_holder.property_sheet.TestMigration, accessor_name) def testDeleteStandardPropertyFromZodbPropertySheet(self):