diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index 3083d6e94aee353a10b31ceb61e35862e81a8134..468e2a268a06a827e0fe08a030ea485aebfe9a79 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -830,7 +830,11 @@ class Base( CopyContainer, cache_factory='erp5_ui_long')) def _aq_key(self): - return (self.portal_type, self.__class__) + klass_list = self.__class__.__mro__ + i = 0 + while klass_list[i].__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'): + i += 1 + return (self.portal_type, klass_list[i]) def _propertyMap(self): """ Method overload - properties are now defined on the ptype """ @@ -854,7 +858,11 @@ class Base( CopyContainer, Test purpose """ ptype = self.portal_type - klass = self.__class__ + klass_list = self.__class__.__mro__ + i = 0 + while klass_list[i].__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'): + i += 1 + klass = klass_list[i] aq_key = (ptype, klass) # We do not use _aq_key() here for speed initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, \ self.getPortalObject()) @@ -866,7 +874,11 @@ class Base( CopyContainer, # and default properties can be associated per portal type # and per class. Other uses are possible (ex. WebSection). ptype = self.portal_type - klass = self.__class__ + klass_list = self.__class__.__mro__ + i = 0 + while klass_list[i].__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'): + i += 1 + klass = klass_list[i] aq_key = (ptype, klass) # We do not use _aq_key() here for speed # If this is a portal_type property and everything is already defined @@ -898,15 +910,6 @@ class Base( CopyContainer, Base.aq_method_generating.append(aq_key) try: # Proceed with property generation - if self.isTempObject() and len(klass.__bases__) == 1: - # If self is a simple temporary object (e.g. not a composed one), - # generate methods for the base document class rather than for the - # temporary document class. - # Otherwise, instances of the base document class would fail - # in calling such methods, because they are not instances of - # the temporary document class. - klass = klass.__bases__[0] - # Generate class methods initializeClassDynamicProperties(self, klass) diff --git a/product/ERP5Type/ERP5Type.py b/product/ERP5Type/ERP5Type.py index 6b88e076928bb415872cddf6cdbc8d86fb2320a4..252bef761c88a878b57f41700dcadf166dbbbda0 100644 --- a/product/ERP5Type/ERP5Type.py +++ b/product/ERP5Type/ERP5Type.py @@ -29,7 +29,7 @@ import Products from Products.CMFCore.TypesTool import FactoryTypeInformation from Products.CMFCore.Expression import Expression from Products.CMFCore.exceptions import AccessControl_Unauthorized -from Products.CMFCore.utils import _checkPermission, getToolByName +from Products.CMFCore.utils import getToolByName from Products.ERP5Type import interfaces, Constraint, Permissions, PropertySheet from Products.ERP5Type.Base import getClassPropertyList from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod @@ -304,48 +304,24 @@ class ERP5TypeInformation(XMLObject, """ return default - # The following 2 methods should not be used. - _getFactoryMethod = deprecated(FactoryTypeInformation._getFactoryMethod) - _constructInstance = deprecated(FactoryTypeInformation._constructInstance) - - def _queryFactoryMethod(self, container, temp_object=0): - product = self.product - factory = self.factory - if not product or not factory: - return ValueError('Product factory for %s was undefined' - % self.getId()) - try: - p = container.manage_addProduct[product] - except AttributeError: - pass - else: - if temp_object: - factory = factory[:3] == 'add' and 'newTemp' + factory[3:] or '' - m = getattr(p, factory, None) - if m is None: - return ValueError('Product factory for %s was invalid' - % self.getId()) - if temp_object: - return m - permission = self.permission - if permission: - if _checkPermission(permission, container): - return m - else: - try: - # validate() can either raise Unauthorized or return 0 to - # mean unauthorized. - if getSecurityManager().validate(p, p, factory, m): - return m - except zExceptions_Unauthorized, e: - return e - return AccessControl_Unauthorized('Cannot create %s' % self.getId()) - security.declarePublic('isConstructionAllowed') def isConstructionAllowed(self, container): """Test if user is allowed to create an instance in the given container """ - return not isinstance(self._queryFactoryMethod(container), Exception) + permission = self.permission or 'Add portal content' + return getSecurityManager().checkPermission(permission, container) + + security.declarePublic('constructTempInstance') + def constructTempInstance(self, container, id, *args, **kw ): + """ + All ERP5Type.Document.newTempXXXX are constructTempInstance methods + """ + # you should not pass temp_object to constructTempInstance + ob = self.constructInstance(container, id, temp_object=1, *args, **kw) + if container.isTempObject(): + container._setObject(id, ob.aq_base) + return ob + security.declarePublic('constructInstance') def constructInstance(self, container, id, created_by_builder=0, @@ -356,10 +332,37 @@ class ERP5TypeInformation(XMLObject, Call the init_script for the portal_type. Returns the object. """ - m = self._queryFactoryMethod(container, temp_object) - if isinstance(m, Exception): - raise m - ob = m(id, **kw) + if not temp_object and not self.isConstructionAllowed(container): + raise AccessControl_Unauthorized('Cannot create %s' % self.getId()) + + portal = container.getPortalObject() + klass = portal.portal_types.getPortalTypeClass( + self.getId(), + temp=temp_object) + ob = klass(id) + + if temp_object: + ob = ob.__of__(container) + for ignore in ('activate_kw', 'is_indexable', 'reindex_kw'): + kw.pop(ignore, None) + else: + activate_kw = kw.pop('activate_kw', None) + if activate_kw is not None: + ob.__of__(container).setDefaultActivateParameters(**activate_kw) + reindex_kw = kw.pop('reindex_kw', None) + if reindex_kw is not None: + ob.__of__(container).setDefaultReindexParameters(**reindex_kw) + is_indexable = kw.pop('is_indexable', None) + if is_indexable is not None: + ob.isIndexable = is_indexable + container._setObject(id, ob) + ob = container._getOb(id) + # if no activity tool, the object has already an uid + if getattr(aq_base(ob), 'uid', None) is None: + ob.uid = portal.portal_catalog.newUid() + + if kw: + ob._edit(force_update=1, **kw) # Portal type has to be set before setting other attributes # in order to initialize aq_dynamic @@ -375,7 +378,7 @@ class ERP5TypeInformation(XMLObject, # notify workflow after generating local roles, in order to prevent # Unauthorized error on transition's condition - workflow_tool = getToolByName(self.getPortalObject(), 'portal_workflow', None) + workflow_tool = getToolByName(portal, 'portal_workflow', None) if workflow_tool is not None: for workflow in workflow_tool.getWorkflowsFor(ob): workflow.notifyCreated(ob) diff --git a/product/ERP5Type/Utils.py b/product/ERP5Type/Utils.py index 80689f99463db27f8226dc0e157d5bfec0fd8203..3c66a59942996fba94d4a88b6f8f6fcb33a4439b 100644 --- a/product/ERP5Type/Utils.py +++ b/product/ERP5Type/Utils.py @@ -506,72 +506,6 @@ from Products.ERP5Type.Globals import InitializeClass from Accessor.Base import func_code from Products.CMFCore.utils import manage_addContentForm, manage_addContent from AccessControl.PermissionRole import PermissionRole -from MethodObject import Method - -class DocumentConstructor(Method): - func_code = func_code() - func_code.co_varnames = ('folder', 'id', 'REQUEST', 'kw') - func_code.co_argcount = 2 - func_defaults = (None,) - - def __init__(self, klass): - self.klass = klass - - def __call__(self, folder, id, REQUEST=None, - activate_kw=None, is_indexable=None, reindex_kw=None, **kw): - o = self.klass(id) - if activate_kw is not None: - o.__of__(folder).setDefaultActivateParameters(**activate_kw) - if reindex_kw is not None: - o.__of__(folder).setDefaultReindexParameters(**reindex_kw) - if is_indexable is not None: - o.isIndexable = is_indexable - folder._setObject(id, o) - o = folder._getOb(id) - # if no activity tool, the object has already an uid - if getattr(aq_base(o), 'uid', None) is None: - o.uid = folder.portal_catalog.newUid() - if kw: o._edit(force_update=1, **kw) - if REQUEST is not None: - REQUEST['RESPONSE'].redirect( 'manage_main' ) - return o - -class TempDocumentConstructor(DocumentConstructor): - - def __init__(self, klass): - # Create a new class to set permissions specific to temporary objects. - class TempDocument(klass): - isTempDocument = PropertyConstantGetter('isTempDocument', value=True) - __roles__ = None - - # Replace some attributes. - for name in ('isIndexable', 'reindexObject', 'recursiveReindexObject', - 'activate', 'setUid', 'setTitle', 'getTitle', 'getUid'): - setattr(TempDocument, name, getattr(klass, '_temp_%s' % name)) - - # Make some methods public. - for method_id in ('reindexObject', 'recursiveReindexObject', - 'activate', 'setUid', 'setTitle', 'getTitle', - 'edit', 'setProperty', 'getUid', 'setCriterion', - 'setCriterionPropertyList'): - setattr(TempDocument, '%s__roles__' % method_id, None) - - self.klass = TempDocument - - def __call__(self, folder, id, REQUEST=None, - activate_kw=None, is_indexable=None, reindex_kw=None, **kw): - o = self.klass(id) - # Use the real container instead of the factory dispatcher. - # - # XXX some code use this constructor directly instead of - # through the factory system. - if getattr(aq_base(folder), 'Destination', None) is not None: - folder = folder.Destination() - o = o.__of__(folder) - if kw: - o._edit(force_update=1, **kw) - return o - python_file_parser = re.compile('^(.*)\.py$') @@ -942,6 +876,43 @@ def setDefaultClassProperties(property_holder): ) } +class PersistentMigrationMixin(object): + """ + All classes issued from ERP5Type.Document.XXX submodules + will gain with mixin as a base class. + + It allows us to migrate ERP5Type.Document.XXX.YYY classes to + erp5.portal_type.ZZZ namespace + + Note that migration can be disabled by setting the migrate + class attribute to 0/False, as all old objects in the system + should inherit from this mixin + """ + migrate = 1 + + def __setstate__(self, value): + if not PersistentMigrationMixin.migrate: + super(PersistentMigrationMixin, self).__setstate__(value) + return + + portal_type = value.get('portal_type') + if portal_type is None: + portal_type = getattr(self.__class__, 'portal_type', None) + if portal_type is None: + LOG('ERP5Type', PROBLEM, + "no portal type was found for %s (class %s)" \ + % (self, self.__class__)) + super(PersistentMigrationMixin, self).__setstate__(value) + else: + # proceed with migration + import erp5.portal_type + klass = getattr(erp5.portal_type, portal_type) + self.__class__ = klass + self.__setstate__(value) + LOG('ERP5Type', INFO, "Migration for object %s" % self) + +from Globals import Persistent, PersistentMapping + def importLocalDocument(class_id, document_path = None): """Imports a document class and registers it in ERP5Type Document repository ( Products.ERP5Type.Document ) @@ -949,103 +920,72 @@ def importLocalDocument(class_id, document_path = None): import Products.ERP5Type.Document import Permissions - if document_path is None: - instance_home = getConfiguration().instancehome - path = os.path.join(instance_home, "Document") - else: - path = document_path - path = os.path.join(path, "%s.py" % class_id) + from Products.ERP5Type import document_class_registry - module_path = 'Products.ERP5Type.Document.' + class_id - document_module = sys.modules.get(module_path) - # Import Document Class and Initialize it - f = open(path) - try: - document_module = imp.load_source(module_path, path, f) - document_class = getattr(document_module, class_id) - document_constructor = DocumentConstructor(document_class) - document_constructor_name = "add%s" % class_id - document_constructor.__name__ = document_constructor_name - except Exception: - f.close() - if document_module is not None: - sys.modules[module_path] = document_module - raise + classpath = document_class_registry.get(class_id) + if classpath is None: + # if the document was not registered before, it means that it is + # a local document in INSTANCE_HOME/Document/ + # (created by ClassTool?) + if document_path is None: + instance_home = getConfiguration().instancehome + path = os.path.join(instance_home, "Document") + else: + path = document_path + path = os.path.join(path, "%s.py" % class_id) + module_path = "erp5.document" + classpath = "%s.%s" % (module_path, class_id) + try: + module = imp.load_source(classpath, path) + except: + raise AttributeError("document was not registered: %s, %s" % (class_id, document_path)) + document_class_registry[class_id] = classpath else: - f.close() - setattr(Products.ERP5Type.Document, class_id, document_module) - setattr(Products.ERP5Type.Document, document_constructor_name, - document_constructor) - setDefaultClassProperties(document_class) - ModuleSecurityInfo('Products.ERP5Type.Document').declareProtected( - Permissions.AddPortalContent, document_constructor_name,) - InitializeClass(document_class) - - # Temp documents are created as standard classes with a different constructor - # which patches some methods are the instance level to prevent reindexing - temp_document_constructor = TempDocumentConstructor(document_class) + module_path = classpath.rsplit('.', 1)[0] + module = __import__(module_path, {}, {}, (module_path,)) + + ### Migration + module_name = "Products.ERP5Type.Document.%s" % class_id + + # Most of Document modules define a single class + # (ERP5Type.Document.Person.Person) + # but some (eek) need to act as module to find other documents, + # e.g. ERP5Type.Document.BusinessTemplate.SkinTemplateItem + # + def migrate_me_document_loader(document_name): + klass = getattr(module, document_name) + if issubclass(klass, (Persistent, PersistentMapping)): + setDefaultClassProperties(klass) + InitializeClass(klass) + + class MigrateMe(PersistentMigrationMixin, klass): + pass + MigrateMe.__name__ = document_name + MigrateMe.__module__ = module_name + return MigrateMe + else: + return klass + from Dynamic.dynamicmodule import dynamicmodule + document_module = dynamicmodule(module_name, migrate_me_document_loader) + + setattr(Products.ERP5Type.Document, class_id, document_module) + + ### newTempFoo + from Products.ERP5Type.ERP5Type import ERP5TypeInformation + klass = getattr(module, class_id) + temp_type = ERP5TypeInformation(klass.portal_type) + temp_document_constructor = temp_type.constructTempInstance + temp_document_constructor_name = "newTemp%s" % class_id - temp_document_constructor.__name__ = temp_document_constructor_name setattr(Products.ERP5Type.Document, temp_document_constructor_name, temp_document_constructor) ModuleSecurityInfo('Products.ERP5Type.Document').declarePublic( temp_document_constructor_name,) # XXX Probably bad security - # Update Meta Types - new_meta_types = [] - for meta_type in Products.meta_types: - if meta_type['name'] != document_class.meta_type: - new_meta_types.append(meta_type) - else: - # Update new_meta_types - instance_class = None - new_meta_types.append( - { 'name': document_class.meta_type, - 'action': ('manage_addProduct/%s/%s' % ( - 'ERP5Type', document_constructor_name)), - 'product': 'ERP5Type', - 'permission': document_class.add_permission, - 'visibility': 'Global', - 'interfaces': document_class.__implements__, - 'instance': instance_class, - 'container_filter': None - },) - Products.meta_types = tuple(new_meta_types) - # Update Constructors - m = Products.ERP5Type._m - if hasattr(document_class, 'factory_type_information'): - constructors = ( manage_addContentForm - , manage_addContent - , document_constructor - , temp_document_constructor - , ('factory_type_information', - document_class.factory_type_information) ) - else: - constructors = ( manage_addContentForm - , manage_addContent - , document_constructor - , temp_document_constructor ) - initial = constructors[0] - m[initial.__name__]=manage_addContentForm - default_permission = ('Manager',) - pr=PermissionRole(document_class.add_permission, default_permission) - m[initial.__name__+'__roles__']=pr - for method in constructors[1:]: - if isinstance(method, tuple): - name, method = method - else: - name=os.path.split(method.__name__)[-1] - if name != 'factory_type_information': - # Add constructor to product dispatcher - m[name]=method - else: - # Append fti to product dispatcher - if not m.has_key(name): m[name] = [] - m[name].append(method) - m[name+'__roles__']=pr + # XXX really? + return klass, tuple() - return document_class, constructors def initializeLocalRegistry(directory_name, import_local_method, path_arg_name='path'): @@ -1132,26 +1072,12 @@ def initializeProduct( context, product_name = module_name.split('.')[-1] - # Define content constructors for Document content classes (RAD) - initializeDefaultConstructors(content_classes) - extra_content_constructors = [] - for content_class in content_classes: - if hasattr(content_class, 'add' + content_class.__name__): - extra_content_constructors += [ - getattr(content_class, 'add' + content_class.__name__)] - if hasattr(content_class, 'newTemp' + content_class.__name__): - extra_content_constructors += [ - getattr(content_class, 'newTemp' + content_class.__name__)] - # Define FactoryTypeInformations for all content classes contentFactoryTypeInformations = [] for content in content_classes: if hasattr(content, 'factory_type_information'): contentFactoryTypeInformations.append(content.factory_type_information) - # Aggregate - content_constructors = list(content_constructors) + list(extra_content_constructors) - # Try to make some standard directories available try: @@ -1242,31 +1168,6 @@ def createConstraintList(property_holder, constraint_definition): # Constructor initialization ##################################################### -def initializeDefaultConstructors(klasses): - for klass in klasses: - if getattr(klass, 'isRADContent', 0) and hasattr(klass, 'security'): - setDefaultConstructor(klass) - klass.security.declareProtected(Permissions.AddPortalContent, - 'add' + klass.__name__) - -def setDefaultConstructor(klass): - """ - Create the default content creation method - """ - document_constructor_name = 'add' + klass.__name__ - if not hasattr(klass, document_constructor_name): - document_constructor = DocumentConstructor(klass) - setattr(klass, document_constructor_name, document_constructor) - document_constructor.__name__ = document_constructor_name - - temp_document_constructor_name = 'newTemp' + klass.__name__ - if not hasattr(klass, temp_document_constructor_name): - temp_document_constructor = TempDocumentConstructor(klass) - setattr(klass, temp_document_constructor_name, temp_document_constructor) - temp_document_constructor.__name__ = temp_document_constructor_name - klass.security.declarePublic(temp_document_constructor_name) - - def createExpressionContext(object, portal=None): """ Return a context used for evaluating a TALES expression. diff --git a/product/ERP5Type/__init__.py b/product/ERP5Type/__init__.py index e3b6b56fab96526f1b4a26dfe6ee9fa39b005241..65e65f61eeeffc94ead8e17ab76e67e2a5a36b14 100644 --- a/product/ERP5Type/__init__.py +++ b/product/ERP5Type/__init__.py @@ -98,6 +98,10 @@ def initialize( context ): portal_tools = portal_tools, content_constructors = content_constructors, content_classes = content_classes) + + from Dynamic import portaltypeclass + portaltypeclass.initializeDynamicModules() + # Register our Workflow factories directly (if on CMF 2) Products.ERP5Type.Workflow.registerAllWorkflowFactories(context) # We should register local constraints at some point diff --git a/product/ERP5Type/tests/testMigration.py b/product/ERP5Type/tests/testMigration.py index eb6361c6a7bd4b9bcaf328ec7ee127b9571d3e2b..d2dbb5e074c8baa7e8063ae9384817bf49ba13fd 100644 --- a/product/ERP5Type/tests/testMigration.py +++ b/product/ERP5Type/tests/testMigration.py @@ -4,7 +4,6 @@ import unittest import transaction from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase -from Products.ERP5Type.tests.backportUnittest import skip class TestNewStyleClasses(ERP5TypeTestCase): @@ -127,8 +126,6 @@ class TestNewStyleClasses(ERP5TypeTestCase): # reset the type person_type.setTypeClass('Person') -TestNewStyleClasses = skip("portal type classes code is not yet committed")(TestNewStyleClasses) - def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestNewStyleClasses))