[Erp5-report] r40159 arnaud.fontaine - in /erp5/trunk/products/ERP5: Document/ tests/

nobody at svn.erp5.org nobody at svn.erp5.org
Wed Nov 10 10:40:07 CET 2010


Author: arnaud.fontaine
Date: Wed Nov 10 10:40:07 2010
New Revision: 40159

URL: http://svn.erp5.org?rev=40159&view=rev
Log:
Add code to allow automatic migration of ZODB Property Sheets during
Business Template installation and a corresponding test


Modified:
    erp5/trunk/products/ERP5/Document/BusinessTemplate.py
    erp5/trunk/products/ERP5/tests/testBusinessTemplate.py

Modified: erp5/trunk/products/ERP5/Document/BusinessTemplate.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BusinessTemplate.py?rev=40159&r1=40158&r2=40159&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BusinessTemplate.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BusinessTemplate.py [utf8] Wed Nov 10 10:40:07 2010
@@ -963,6 +963,11 @@ class ObjectTemplateItem(BaseTemplateIte
       original_reindex_parameters = self.setSafeReindexationMode(context)
       object_key_list = self._getObjectKeyList()
       for path in object_key_list:
+        # We do not need to perform any backup because the object was
+        # created during the Business Template installation
+        if update_dict.get(path) == 'migrate':
+          continue
+
         if update_dict.has_key(path) or force:
           # get action for the oject
           action = 'backup'
@@ -3436,12 +3441,221 @@ class DocumentTemplateItem(BaseTemplateI
     text = file.read()
     self._objects[file_name[:-3]] = text
 
-class PropertySheetTemplateItem(DocumentTemplateItem):
+class PropertySheetTemplateItem(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.
+  """
+  # 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
+  _perform_migration = False
+
+  # 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)
 
+  def __init__(self, id_list, tool_id='portal_property_sheets', **kw):
+    BaseTemplateItem.__init__(self, id_list, **kw)
+
+  @staticmethod
+  def _is_already_migrated(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/'
+    """
+    return len(object_key_list) != 0 and \
+        object_key_list[0].startswith('portal_property_sheets/')
+
+  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
+    """
+    def inner(self, *args, **kw):
+      if self._is_already_migrated(getattr(self, object_dict_name).keys()):
+        return getattr(ObjectTemplateItem, method_name)(self, *args, **kw)
+      else:
+        return getattr(DocumentTemplateItem, method_name)(self, *args, **kw)
+
+    return inner
+
+  export = _filesystemCompatibilityWrapper('export', '_objects')
+  build = _filesystemCompatibilityWrapper('build', '_archive')
+  preinstall = _filesystemCompatibilityWrapper('preinstall', '_objects')
+
+  def _importFile(self, file_name, *args, **kw):
+    if file_name.endswith('.xml'):
+      return ObjectTemplateItem._importFile(self, file_name, *args, **kw)
+    else:
+      return DocumentTemplateItem._importFile(self, file_name, *args, **kw)
+
+  def uninstall(self, *args, **kw):
+    # Only for uninstall, the path of objects can be given as a
+    # parameter, otherwise it fallbacks on '_archive'
+    object_path = kw.get('object_path', None)
+    if object_path is not None:
+      object_keys = [object_path]
+    else:
+      object_keys = self._archive.keys()
+
+    if self._is_already_migrated(object_keys):
+      return ObjectTemplateItem.uninstall(self, *args, **kw)
+    else:
+      return DocumentTemplateItem.uninstall(self, *args, **kw)
+
+  @staticmethod
+  def _getFilesystemPropertySheetPath(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 _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 property_sheet_tool.createPropertySheetFromFilesystemClass(klass)
+
+  def _migrateAllFilesystemPropertySheets(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
+    version 1 of Business Template, the former would be '_objects' and
+    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
+
+    for class_id in migrate_object_dict:
+      # 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_tool:
+        raise RuntimeError('Conflict when migrating Property Sheet %s: ' \
+                           'already exists in portal_property_sheets' % \
+                           class_id)
+
+      filesystem_property_sheet_path = \
+          self._getFilesystemPropertySheetPath(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)
+      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)
+
+      try:
+        new_property_sheet = self._migrateFilesystemPropertySheet(
+          property_sheet_tool,
+          filesystem_property_sheet_path,
+          filesystem_property_sheet_file,
+          class_id)
+
+      finally:
+        filesystem_property_sheet_file.close()
+        os.remove(filesystem_property_sheet_path)
+
+      # Update 'migrate_object_dict' with the new path
+      key = 'portal_property_sheets/%s' % class_id
+
+      migrate_object_dict[key] = new_property_sheet
+      del migrate_object_dict[class_id]
+
+      # Remove old reference in 'object_dict' as it does not make
+      # sense to keep it anymore
+      if class_id in object_dict:
+        object_dict[key] = None
+        del object_dict[class_id]
+
+      # Skip meaningless backup of the object as it has just been
+      # migrated
+      update_parameter_dict[key] = 'migrate'
+
+    transaction.commit()
+
+  def install(self, context, **kw):
+    if not self._perform_migration:
+      return DocumentTemplateItem.install(self, context, **kw)
+
+    # With format 0 of Business Template, the objects are stored in
+    # '_archive' whereas they are stored in '_objects' with format
+    # version 1
+    bt_format_version = context.getTemplateFormatVersion()
+
+    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'))
+    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'))
+
+    return ObjectTemplateItem.install(self, context, **kw)
+
 class ConstraintTemplateItem(DocumentTemplateItem):
   local_file_reader_name = staticmethod(readLocalConstraint)
   local_file_writer_name = staticmethod(writeLocalConstraint)

Modified: erp5/trunk/products/ERP5/tests/testBusinessTemplate.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testBusinessTemplate.py?rev=40159&r1=40158&r2=40159&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/tests/testBusinessTemplate.py [utf8] (original)
+++ erp5/trunk/products/ERP5/tests/testBusinessTemplate.py [utf8] Wed Nov 10 10:40:07 2010
@@ -2356,7 +2356,7 @@ class TestBusinessTemplate(ERP5TypeTestC
     """
     ps_title = 'UnitTest'
     ps_data =  ' \nclass UnitTest: \n  """ \n  Fake property sheet for unit test \n \
-    """ \n  _properties = ( \n  ) \n  _categories = ( \n  ) \n\n'
+    """ \n  _properties = ({"id": "ps_prop1", "type": "string"},) \n  _categories = ( \n  ) \n\n'
     cfg = getConfiguration()
     file_path = os.path.join(cfg.instancehome, 'PropertySheet', ps_title+'.py')
     if os.path.exists(file_path):
@@ -2377,6 +2377,19 @@ class TestBusinessTemplate(ERP5TypeTestC
     self.failUnless(ps_title is not None)
     bt.edit(template_property_sheet_id_list=[ps_title])
 
+  def stepCheckPropertySheetMigration(self, sequence=None, sequence_list=None, **kw):
+    """
+    Check migration of Property Sheets from the Filesystem to ZODB
+    """
+    property_sheet_tool = self.getPortalObject().portal_property_sheets
+    self.failUnless('UnitTest' in property_sheet_tool)
+
+    property_list = property_sheet_tool.UnitTest.contentValues()
+
+    self.assertEquals(len(property_list), 1)
+    self.failUnless(property_list[0].getReference() == 'ps_prop1')
+    self.failUnless(property_list[0].getElementaryType() == 'string')
+
   def stepRemovePropertySheet(self, sequence=None, sequencer_list=None, **kw):
     """
     Remove Property Sheet
@@ -2402,12 +2415,23 @@ class TestBusinessTemplate(ERP5TypeTestC
 
   def stepCheckPropertySheetRemoved(self, sequence=None, sequencer_list=None, **kw):
     """
-    Check presence of Property Sheet
+    Check deletion of Property Sheet
     """
     ps_path = sequence.get('ps_path', None)
     self.failUnless(ps_path is not None)
     self.failIf(os.path.exists(ps_path))
 
+  def stepCheckMigratedPropertySheetRemoved(self,
+                                            sequence=None,
+                                            sequencer_list=None,
+                                            **kw):
+    """
+    Check deletion of migrated Property Sheet
+    """
+    ps_id = sequence.get('ps_title', None)
+    self.failIf(ps_id is None)
+    self.failIf(ps_id in self.getPortalObject().portal_property_sheets)
+
   def stepCreateUpdatedPropertySheet(self, sequence=None, sequence_list=None, **kw):
     """
     Create a Property Sheet
@@ -4234,6 +4258,55 @@ class TestBusinessTemplate(ERP5TypeTestC
     sequence_list.addSequenceString(sequence_string)
     sequence_list.play(self, quiet=quiet)
 
+  @expectedFailure
+  def test_151_BusinessTemplateWithPropertySheetMigration(self, quiet=quiet,
+                                                          run=run_all_test):
+    if not run:
+      return
+
+    if not quiet:
+      message = 'Test Business Template With Property Sheet Migration'
+      ZopeTestCase._print('\n%s ' % message)
+      LOG('Testing... ', 0, message)
+
+    sequence_list = SequenceList()
+    sequence_string = '\
+                       CreatePropertySheet \
+                       CreateNewBusinessTemplate \
+                       UseExportBusinessTemplate \
+                       AddPropertySheetToBusinessTemplate \
+                       CheckModifiedBuildingState \
+                       CheckNotInstalledInstallationState \
+                       BuildBusinessTemplate \
+                       CheckBuiltBuildingState \
+                       CheckNotInstalledInstallationState \
+                       CheckObjectPropertiesInBusinessTemplate \
+                       SaveBusinessTemplate \
+                       CheckBuiltBuildingState \
+                       CheckNotInstalledInstallationState \
+                       RemovePropertySheet \
+                       RemoveBusinessTemplate \
+                       RemoveAllTrashBins \
+                       ImportBusinessTemplate \
+                       UseImportBusinessTemplate \
+                       CheckBuiltBuildingState \
+                       CheckNotInstalledInstallationState \
+                       InstallBusinessTemplate \
+                       Tic \
+                       CheckInstalledInstallationState \
+                       CheckBuiltBuildingState \
+                       CheckNoTrashBin \
+                       CheckSkinsLayers \
+                       CheckPropertySheetMigration \
+                       CheckPropertySheetRemoved \
+                       UninstallBusinessTemplate \
+                       CheckBuiltBuildingState \
+                       CheckNotInstalledInstallationState \
+                       CheckMigratedPropertySheetRemoved \
+                       '
+    sequence_list.addSequenceString(sequence_string)
+    sequence_list.play(self, quiet=quiet)
+
   def test_155_BusinessTemplateUpdateWithPropertySheet(self, quiet=quiet, run=run_all_test):
     if not run: return
     if not quiet:




More information about the Erp5-report mailing list