From a462aa1c2db86037c6662f9461c693983647738c Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Wed, 3 Nov 2010 13:24:07 +0000 Subject: [PATCH] When uninstalling document item, restore document class from product if any This fixes TestBusinessTemplate.test_168_DocumentUninstallIsEffective git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@39835 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/Document/BusinessTemplate.py | 37 +++++++++-------- product/ERP5/tests/testBusinessTemplate.py | 6 +-- product/ERP5Type/InitGenerator.py | 31 ++++++--------- product/ERP5Type/Utils.py | 46 ++++++++++------------ 4 files changed, 53 insertions(+), 67 deletions(-) diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index b4bfac4efa..fb9c17c5da 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -3380,6 +3380,21 @@ class DocumentTemplateItem(BaseTemplateItem): {path : ['Removed', self.__class__.__name__[:-12]]}) return modified_object_list + def _resetDynamicModules(self): + # before any import, flush all ZODB caches to force a DB reload + # otherwise we could have objects trying to get commited while + # holding reference to a class that is no longer the same one as + # the class in its import location and pickle doesn't tolerate it. + # First we do a savepoint to dump dirty objects to temporary + # storage, so that all references to them can be freed. + transaction.savepoint(optimistic=True) + # Then we need to flush from all caches, not only the one from this + # connection + portal = self.getPortalObject() + portal._p_jar.db().cacheMinimize() + synchronizeDynamicModules(portal, force=True) + gc.collect() + def install(self, context, trashbin, **kw): update_dict = kw.get('object_to_update') force = kw.get('force') @@ -3393,7 +3408,6 @@ class DocumentTemplateItem(BaseTemplateItem): continue text = self._objects[id] path, name = posixpath.split(id) - # This raises an exception if the file already exists. try: self.local_file_writer_name(name, text, create=0) except IOError, error: @@ -3405,19 +3419,7 @@ class DocumentTemplateItem(BaseTemplateItem): if self.local_file_importer_name is None: continue if need_reset: - # before any import, flush all ZODB caches to force a DB reload - # otherwise we could have objects trying to get commited while - # holding reference to a class that is no longer the same one as - # the class in its import location and pickle doesn't tolerate it. - # First we do a savepoint to dump dirty objects to temporary - # storage, so that all references to them can be freed. - transaction.savepoint(optimistic=True) - # Then we need to flush from all caches, not only the one from this - # connection - portal = self.getPortalObject() - portal._p_jar.db().cacheMinimize() - synchronizeDynamicModules(portal, force=True) - gc.collect() + self._resetDynamicModules() need_reset = False self.local_file_importer_name(name) else: @@ -3435,8 +3437,11 @@ class DocumentTemplateItem(BaseTemplateItem): object_keys = [object_path] else: object_keys = self._archive.keys() - for key in object_keys: - self.local_file_remover_name(key) + if object_keys: + if isinstance(self, DocumentTemplateItem): + self._resetDynamicModules() + for key in object_keys: + self.local_file_remover_name(key) BaseTemplateItem.uninstall(self, context, **kw) def export(self, context, bta, **kw): diff --git a/product/ERP5/tests/testBusinessTemplate.py b/product/ERP5/tests/testBusinessTemplate.py index 39546c8db2..2aae1f3d59 100644 --- a/product/ERP5/tests/testBusinessTemplate.py +++ b/product/ERP5/tests/testBusinessTemplate.py @@ -6712,7 +6712,6 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): # check the previously existing instance now behaves as the overriden class self.assertTrue(getattr(portal.another_file, 'isClassOverriden', False)) - @expectedFailure def test_168_DocumentUninstallIsEffective(self): portal = self.portal # Test_167 above needs to have been run @@ -6720,11 +6719,8 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): 'isClassOverriden', False): self.test_167_InstanceAndRelatedClassDefinedInSameBT() - self.uninstallBusinessTemplate('test_data') - transaction.commit() - self.tic() + self.uninstallBusinessTemplate('test_bt') # check both File instances no longer behave like being overriden - self.assertFalse(getattr(portal.some_file, 'isClassOverriden', False)) self.assertFalse(getattr(portal.another_file, 'isClassOverriden', False)) def test_getBusinessTemplateUrl(self): diff --git a/product/ERP5Type/InitGenerator.py b/product/ERP5Type/InitGenerator.py index 4dad060d6f..dfb574fc03 100644 --- a/product/ERP5Type/InitGenerator.py +++ b/product/ERP5Type/InitGenerator.py @@ -30,46 +30,39 @@ import os, re, string, sys +from Products.ERP5Type import document_class_registry from Products.ERP5Type.Globals import package_home, InitializeClass from zLOG import LOG -global product_document_registry -product_document_registry = [] -global product_interactor_registry +product_document_registry = {} product_interactor_registry = [] -global interactor_class_id_registry interactor_class_id_registry = {} def getProductDocumentPathList(): - result = product_document_registry - result.sort() - return result + return sorted((k, os.path.dirname(sys.modules[v.rsplit('.', 1)[0]].__file__)) + for k,v in product_document_registry.iteritems()) -def InitializeDocument(document_class, document_path=None): - global product_document_registry +def InitializeDocument(class_id, class_path): # Register class in ERP5Type.Document - product_document_registry.append(((document_class, document_path))) + product_document_registry[class_id] = class_path def getProductInteractorPathList(): - result = product_interactor_registry - result.sort() - return result + return sorted(product_interactor_registry) def InitializeInteractor(interactor_class, interactor_path=None): - global product_interactor_registry # Register class in ERP5Type.Interactor product_interactor_registry.append(((interactor_class, interactor_path))) def initializeProductDocumentRegistry(): from Utils import importLocalDocument - for (class_id, document_path) in product_document_registry: - importLocalDocument(class_id, path=document_path) + for (class_id, class_path) in product_document_registry.iteritems(): + importLocalDocument(class_id, class_path=class_path) #from Testing import ZopeTestCase #ZopeTestCase._print('Added product document to ERP5Type repository: %s (%s) \n' % (class_id, document_path)) #LOG('Added product document to ERP5Type repository: %s (%s)' % (class_id, document_path), 0, '') #print 'Added product document to ERP5Type repository: %s (%s)' % (class_id, document_path) - + def initializeProductInteractorRegistry(): from Utils import importLocalInteractor for (class_id, interactor_path) in product_interactor_registry: @@ -77,10 +70,8 @@ def initializeProductInteractorRegistry(): importLocalInteractor(class_id, path=interactor_path) def registerInteractorClass(class_id, klass): - global interactor_class_id_registry interactor_class_id_registry[class_id] = klass def installInteractorClassRegistry(): - global interactor_class_id_registry - for class_id, klass in interactor_class_id_registry.items(): + for klass in interactor_class_id_registry.itervalues(): klass().install() diff --git a/product/ERP5Type/Utils.py b/product/ERP5Type/Utils.py index d3de48cb26..a8955f4dfc 100644 --- a/product/ERP5Type/Utils.py +++ b/product/ERP5Type/Utils.py @@ -87,7 +87,7 @@ def simple_decorator(decorator): return new_decorator from Products.ERP5Type import Permissions - +from Products.ERP5Type import document_class_registry from Products.ERP5Type.Accessor.Constant import PropertyGetter as \ PropertyConstantGetter from Products.ERP5Type.Accessor.Constant import Getter as ConstantGetter @@ -470,7 +470,6 @@ def updateGlobals(this_module, global_hook, for module_id in module_id_list: import_method(module_id, path=path, is_erp5_type=is_erp5_type) - from Products.ERP5Type import document_class_registry module_name = this_module.__name__ # Return core document_class list (for ERP5Type only) # this was introduced to permit overriding ERP5Type Document classes @@ -478,14 +477,12 @@ def updateGlobals(this_module, global_hook, path, core_module_id_list = getModuleIdList(product_path, 'Core') for document in core_module_id_list: module_path = '.'.join((module_name, 'Core', document, document)) - document_class_registry[document] = module_path - InitializeDocument(document, document_path=path) + InitializeDocument(document, module_path) # Return document_class list path, module_id_list = getModuleIdList(product_path, 'Document') for document in module_id_list: module_path = '.'.join((module_name, 'Document', document, document)) - document_class_registry[document] = module_path - InitializeDocument(document, document_path=path) + InitializeDocument(document, module_path) # Return interactor_class list path, interactor_id_list = getModuleIdList(product_path, 'Interactor') @@ -803,6 +800,14 @@ def removeLocalDocument(class_id): f = os.path.join(path, "%s.%s" % (class_id, ext)) if os.path.exists(f): os.remove(f) + if document_class_registry.pop(class_id, None): + # restore original class (from product) if any + from Products.ERP5Type.InitGenerator import product_document_registry + product_path = product_document_registry.get(class_id) + if product_path: + importLocalDocument(class_id, class_path=product_path) + else: + pass # XXX Do we need to clean up ? def readLocalDocument(class_id): instance_home = getConfiguration().instancehome @@ -826,22 +831,14 @@ def writeLocalDocument(class_id, text, create=1, instance_home=None): if create: if os.path.exists(path): raise IOError, 'the file %s is already present' % path + # check there is no syntax error (that's the most we can do at this time) + compile(text, path, 'exec') f = open(path, 'w') try: f.write(text) finally: f.close() - module_path = "erp5.document" - classpath = "%s.%s" % (module_path, class_id) - module = imp.load_source(classpath, path) - import erp5.document - setattr(erp5.document, class_id, getattr(module, class_id)) - - # and register correctly the new document - from Products.ERP5Type import document_class_registry - document_class_registry[class_id] = classpath - def setDefaultClassProperties(property_holder): """Initialize default properties for ERP5Type Documents. """ @@ -917,17 +914,16 @@ class PersistentMigrationMixin(object): from Globals import Persistent, PersistentMapping -def importLocalDocument(class_id, path=None): +def importLocalDocument(class_id, path=None, class_path=None): """Imports a document class and registers it in ERP5Type Document repository ( Products.ERP5Type.Document ) """ import Products.ERP5Type.Document import Permissions - from Products.ERP5Type import document_class_registry - if path and 'products' in path.lower(): # XXX - classpath = document_class_registry[class_id] - module_path = classpath.rsplit('.', 1)[0] + if class_path: + assert path is None + module_path = class_path.rsplit('.', 1)[0] module = __import__(module_path, {}, {}, (module_path,)) else: # local document in INSTANCE_HOME/Document/ @@ -937,11 +933,11 @@ def importLocalDocument(class_id, path=None): path = os.path.join(instance_home, "Document") path = os.path.join(path, "%s.py" % class_id) module_path = "erp5.document" - classpath = "%s.%s" % (module_path, class_id) - module = imp.load_source(classpath, path) + class_path = "%s.%s" % (module_path, class_id) + module = imp.load_source(class_path, path) import erp5.document setattr(erp5.document, class_id, getattr(module, class_id)) - document_class_registry[class_id] = classpath + document_class_registry[class_id] = class_path ### Migration module_name = "Products.ERP5Type.Document.%s" % class_id @@ -986,7 +982,6 @@ def importLocalDocument(class_id, path=None): # XXX really? return klass, tuple() - def initializeLocalRegistry(directory_name, import_local_method): """ Initialize local directory. @@ -1049,7 +1044,6 @@ def initializeProduct( context, """ module_name = this_module.__name__ - from Products.ERP5Type import document_class_registry # Content classes are exceptions and should be registered here. # other products were all already registered in updateGlobals() # because getModuleIdList works fine for Document/ and Core/ -- 2.30.9