From 4c03bfefc6ec0f86db5c8f3004708d271b7a73f2 Mon Sep 17 00:00:00 2001 From: Arnaud Fontaine <arnaud.fontaine@nexedi.com> Date: Fri, 20 Jan 2012 15:30:05 +0900 Subject: [PATCH] Make migration code of ZODB Property sheets for Business Template more generic and add Components. --- product/ERP5/Document/BusinessTemplate.py | 307 +++++++++++++--------- 1 file changed, 179 insertions(+), 128 deletions(-) diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index 6315e39332..1db2e3c099 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -3478,67 +3478,56 @@ class DocumentTemplateItem(BaseTemplateItem): text = file.read() self._objects[file_name[:-3]] = text -class PropertySheetTemplateItem(DocumentTemplateItem, - ObjectTemplateItem): +class FilesystemToZodbTemplateItem(DocumentTemplateItem, + ObjectTemplateItem): """ - Property Sheets are now stored in ZODB, rather than the filesystem. - However, Some Business Template may still have filesystem Property - Sheets, which need to be migrated to the ZODB. - - This migration is performed in two steps: - - 1/ Specify explicitly in the web user interface that the Property - Sheets should be migrated. - - 2/ The Property Sheets will all be migrated when installing the - Business Template. - - Therefore, this is an all or nothing migration, meaning that only - methods of DocumentTemplateItem will be called before the migration - has been performed, then ObjectTemplateItem methods afterwards. + Abstract class to allow migration from DocumentTemplateItem to + ObjectTemplateItem, this is useful for migration from filesystem to ZODB for + PropertySheets and Components """ - # If set to False, then the migration of Property Sheets will never - # be performed, required until the code of ZODB Property Sheets is - # stable and completely documented + # If set to False, then the migration from filesystem to ZODB will be + # performed, meaningful only until the code is stable _perform_migration = True - # Only meaningful for filesystem Property Sheets - local_file_reader_name = staticmethod(readLocalPropertySheet) - local_file_writer_name = staticmethod(writeLocalPropertySheet) - local_file_importer_name = staticmethod(importLocalPropertySheet) - local_file_remover_name = staticmethod(removeLocalPropertySheet) + _tool_id = None + + @staticmethod + def _getZodbObjectId(id): + return id + + def __init__(self, id_list, tool_id=None, context=None, **kw): + if tool_id is None: + tool_id = self._tool_id - def __init__(self, id_list, tool_id='portal_property_sheets', context=None, **kw): tool = None if context is not None and len(id_list): # XXX looking up a tool early in the install process might # cause issues. If it does, we'll have to consider moving this # to build() - tool = getattr(context.getPortalObject(), tool_id, None) + tool = getattr(context.getPortalObject(), self._tool_id, None) if tool is not None: - existing_property_sheet_set = set(tool.objectIds()) + existing_set = set(tool.objectIds()) for i, id in enumerate(id_list): - if id in existing_property_sheet_set: - # if the property sheet is on ZODB, use it. - id_list[i] = "%s/%s" % (tool_id, id) + if id in existing_set: + # if the object is on ZODB, use it. + id_list[i] = "%s/%s" % (self._tool_id, self._getZodbObjectId(id)) BaseTemplateItem.__init__(self, id_list, **kw) - @staticmethod - def _is_already_migrated(object_key_list): + def _is_already_migrated(self, object_key_list): """ - The Property Sheets have already been migrated if any keys within - the given object_key_list (either '_objects.keys()' or - '_archive.keys()') contains a key starting by 'portal_property_sheets/' + Objects have already been migrated if any keys within the given + object_key_list (either '_objects.keys()' or '_archive.keys()') contains a + key starting by 'tool_id/' """ return len(object_key_list) != 0 and \ - object_key_list[0].startswith('portal_property_sheets/') + object_key_list[0].startswith(self._tool_id + '/') def _filesystemCompatibilityWrapper(method_name, object_dict_name): """ - Call ObjectTemplateItem method when the Property Sheets have - already been migrated, otherwise fallback on DocumentTemplateItem - method for backward-compatibility + Call ObjectTemplateItem method when the objects have already been + migrated, otherwise fallback on DocumentTemplateItem method for + backward-compatibility """ def inner(self, *args, **kw): if self._is_already_migrated(getattr(self, object_dict_name).keys()): @@ -3548,7 +3537,7 @@ class PropertySheetTemplateItem(DocumentTemplateItem, if method_name == 'preinstall': old_result = result.copy() for k, v in old_result.iteritems(): - if not k.startswith('portal_property_sheets/'): + if not k.startswith(self._tool_id + '/'): result.pop(k) # Magical way to have unique path in case of not yet migrated property # sheets available on preinstall list @@ -3581,53 +3570,31 @@ class PropertySheetTemplateItem(DocumentTemplateItem, else: return DocumentTemplateItem.uninstall(self, *args, **kw) - @staticmethod - def _getFilesystemPropertySheetPath(class_id): + def remove(self, context, **kw): """ - From the given class identifier, return the complete path of the - filesystem Property Sheet class. Only meaningful when the Business - Template has already been installed previously, otherwise the + Conversion of magically uniqued paths to real ones """ - from App.config import getConfiguration - return os.path.join(getConfiguration().instancehome, - "PropertySheet", - "%s.py" % class_id) + remove_object_dict = kw.get('remove_object_dict', {}) + new_remove_dict = dict() + for k,v in remove_object_dict.iteritems(): + if k.startswith(self.getTemplateTypeName()+'/'): + new_remove_dict[self._getPath(k)] = v + kw['remove_object_dict'] = new_remove_dict + ObjectTemplateItem.remove(self, context, **kw) @staticmethod - def _migrateFilesystemPropertySheet(property_sheet_tool, - filesystem_property_sheet_path, - filesystem_property_sheet_file, - class_id): - """ - Migration of a filesystem Property Sheet involves loading the - class from 'instancehome/PropertySheet/<class_id>', then create - the ZODB Property Sheets in portal_property_sheets from its - filesystem definition - """ - # The first parameter of 'load_source' is the module name where - # the class will be stored, thus don't only use the class name as - # it may clash with already loaded module, such as - # BusinessTemplate. - module = imp.load_source( - 'Migrate%sFilesystemPropertySheet' % class_id, - filesystem_property_sheet_path, - filesystem_property_sheet_file) - - try: - klass = getattr(module, class_id) - except AttributeError: - raise AttributeError("filesystem Property Sheet '%s' should " \ - "contain a class with the same name" % \ - class_id) - - return PropertySheetDocument.importFromFilesystemDefinition( - property_sheet_tool, klass) + def _getFilesystemPath(class_id): + raise NotImplementedError - def _migrateAllFilesystemPropertySheets(self, - context, - migrate_object_dict, - object_dict, - update_parameter_dict): + @staticmethod + def _migrateFromFilesystem(tool, filesystem_path, filesystem_file, class_id): + raise NotImplementedError + + def _migrateAllFromFilesystem(self, + context, + migrate_object_dict, + object_dict, + update_parameter_dict): """ Migrate all Property Sheets from 'migrate_object_dict' and, if necessary, remove old references in 'object_dict' too (with format @@ -3635,10 +3602,9 @@ class PropertySheetTemplateItem(DocumentTemplateItem, the latter '_archive'), and finally removing the useless Property Sheet on the filesystem """ - # Migrate all the filesystem Property Sheets of the Business - # Template if any - property_sheet_tool = context.getPortalObject().portal_property_sheets - property_sheet_id_set = set(property_sheet_tool.objectIds()) + # Migrate all the filesystem classes of the Business Template if any + tool = getattr(context.getPortalObject(), self._tool_id) + id_set = set(tool.objectIds()) # careful, that dictionary will change class_id_list = migrate_object_dict.keys() @@ -3646,48 +3612,44 @@ class PropertySheetTemplateItem(DocumentTemplateItem, # If the Property Sheet already exists in ZODB, then skip it, # otherwise it should not be needed anymore once the deletion # code of the filesystem Property Sheets is enabled - if class_id in property_sheet_id_set: + if class_id in id_set: # XXX a Conduit must be able to merge modifications # from FS PropertySheets into ZODB PropertySheets - warn('Conflict when migrating Property Sheet %s: ' \ - 'already exists in portal_property_sheets and '\ - 'cannot be updated automatically for now. ' % class_id, + warn('Conflict when migrating classes %s: already exists in %s and '\ + 'cannot be updated automatically for now.' % (class_id, + self._tool_id), UserWarning) del migrate_object_dict[class_id] if class_id in object_dict: del object_dict[class_id] continue - filesystem_property_sheet_path = \ - self._getFilesystemPropertySheetPath(class_id) + filesystem_path = self._getFilesystemPath(class_id) # A filesystem Property Sheet may already exist in the instance # home if the Business Template has been previously installed, # otherwise it is created - if os.path.exists(filesystem_property_sheet_path): - filesystem_property_sheet_file = open(filesystem_property_sheet_path) + if os.path.exists(filesystem_path): + filesystem_file = open(filesystem_path) else: - filesystem_property_sheet_file = open(filesystem_property_sheet_path, - 'w+') - - filesystem_property_sheet_file.write(migrate_object_dict[class_id]) - filesystem_property_sheet_file.seek(0) + filesystem__file = open(filesystem_path, 'w+') + filesystem_file.write(migrate_object_dict[class_id]) + filesystem_file.seek(0) try: - new_property_sheet = self._migrateFilesystemPropertySheet( - property_sheet_tool, - filesystem_property_sheet_path, - filesystem_property_sheet_file, - class_id) + migrated_object = self._migrateFromFilesystem(tool, + filesystem_path, + filesystem_file, + class_id).aq_base finally: - filesystem_property_sheet_file.close() - os.remove(filesystem_property_sheet_path) + filesystem_file.close() + os.remove(filesystem_path) # Update 'migrate_object_dict' with the new path - key = 'portal_property_sheets/%s' % class_id + key = '%s/%s' % (self._tool_id, migrated_object.getId()) - migrate_object_dict[key] = new_property_sheet.aq_base + migrate_object_dict[key] = migrated_object del migrate_object_dict[class_id] # Remove old reference in 'object_dict' as it does not make @@ -3700,16 +3662,6 @@ class PropertySheetTemplateItem(DocumentTemplateItem, # migrated update_parameter_dict[key] = 'migrate' - def remove(self, context, **kw): - """Conversion of magically uniqued paths to real ones""" - remove_object_dict = kw.get('remove_object_dict', {}) - new_remove_dict = dict() - for k,v in remove_object_dict.iteritems(): - if k.startswith(self.getTemplateTypeName()+'/'): - new_remove_dict[self._getPath(k)] = v - kw['remove_object_dict'] = new_remove_dict - ObjectTemplateItem.remove(self, context, **kw) - def install(self, context, **kw): if not self._perform_migration: return DocumentTemplateItem.install(self, context, **kw) @@ -3721,32 +3673,131 @@ class PropertySheetTemplateItem(DocumentTemplateItem, if bt_format_version == 0 and \ not self._is_already_migrated(self._archive.keys()): - self._migrateAllFilesystemPropertySheets(context, - self._archive, - self._objects, - kw.get('object_to_update')) + self._migrateAllFromFilesystem(context, + self._archive, + self._objects, + kw.get('object_to_update')) elif bt_format_version == 1 and \ not self._is_already_migrated(self._objects.keys()): - self._migrateAllFilesystemPropertySheets(context, - self._objects, - self._archive, - kw.get('object_to_update')) + self._migrateAllFromFilesystem(context, + self._objects, + self._archive, + kw.get('object_to_update')) return ObjectTemplateItem.install(self, context, **kw) +class PropertySheetTemplateItem(FilesystemToZodbTemplateItem): + """ + Property Sheets are now stored in ZODB rather than the filesystem. + However, some Business Templates may still have filesystem Property + Sheets, which need to be migrated to the ZODB. + + This migration is performed in two steps: + + 1/ Specify explicitly in the web user interface that the Property + Sheets should be migrated. + + 2/ The Property Sheets will all be migrated when installing the + Business Template. + + Therefore, this is an all or nothing migration, meaning that only + methods of DocumentTemplateItem will be called before the migration + has been performed, then ObjectTemplateItem methods afterwards. + """ + # Only meaningful for filesystem Property Sheets + local_file_reader_name = staticmethod(readLocalPropertySheet) + local_file_writer_name = staticmethod(writeLocalPropertySheet) + local_file_importer_name = staticmethod(importLocalPropertySheet) + local_file_remover_name = staticmethod(removeLocalPropertySheet) + + _tool_id = 'portal_property_sheets' + + @staticmethod + def _getFilesystemPath(class_id): + """ + From the given class identifier, return the complete path of the + filesystem Property Sheet class. Only meaningful when the Business + Template has already been installed previously, otherwise the + """ + from App.config import getConfiguration + return os.path.join(getConfiguration().instancehome, + "PropertySheet", + "%s.py" % class_id) + + @staticmethod + def _migrateFromFilesystem(tool, + filesystem_path, + filesystem_file, + class_id): + """ + Migration of a filesystem Property Sheet involves loading the + class from 'instancehome/PropertySheet/<class_id>', then create + the ZODB Property Sheets in portal_property_sheets from its + filesystem definition + """ + # The first parameter of 'load_source' is the module name where + # the class will be stored, thus don't only use the class name as + # it may clash with already loaded module, such as + # BusinessTemplate. + module = imp.load_source('Migrate%sFilesystemPropertySheet' % class_id, + filesystem_path, + filesystem_file) + + try: + klass = getattr(module, class_id) + except AttributeError: + raise AttributeError("filesystem Property Sheet '%s' should " \ + "contain a class with the same name" % \ + class_id) + + return PropertySheetDocument.importFromFilesystemDefinition(tool, klass) + class ConstraintTemplateItem(DocumentTemplateItem): local_file_reader_name = staticmethod(readLocalConstraint) local_file_writer_name = staticmethod(writeLocalConstraint) local_file_importer_name = staticmethod(importLocalConstraint) local_file_remover_name = staticmethod(removeLocalConstraint) -class ExtensionTemplateItem(DocumentTemplateItem): +from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent as \ + ExtensionComponentDocument + +class ExtensionTemplateItem(FilesystemToZodbTemplateItem): + """ + Extensions are now stored in ZODB rather than on the filesystem. However, + some Business Templates may still have filesystem Extensions which need to + be migrated to the ZODB. + """ + # XXX-arnau: the code is far from being stable + _perform_migration = False + + _tool_id = 'portal_components' + + # Only meaningful for filesystem Extensions local_file_reader_name = staticmethod(readLocalExtension) local_file_writer_name = staticmethod(writeLocalExtension) # Extension needs no import local_file_importer_name = None local_file_remover_name = staticmethod(removeLocalExtension) + @staticmethod + def _getZodbObjectId(id): + return 'erp5.component.extension.%s' % id + + @staticmethod + def _getFilesystemPath(class_id): + from App.config import getConfiguration + return os.path.join(getConfiguration().instancehome, + "Extensions", + "%s.py" % class_id) + + @staticmethod + def _migrateFromFilesystem(tool, + filesystem_path, + filesystem_file, + class_id): + return ExtensionComponentDocument.importFromFilesystem(tool, + filesystem_path) + class TestTemplateItem(DocumentTemplateItem): local_file_reader_name = staticmethod(readLocalTest) local_file_writer_name = staticmethod(writeLocalTest) -- 2.30.9