Commit 6b82db85 authored by Arnaud Fontaine's avatar Arnaud Fontaine

Add code to allow automatic migration of ZODB Property Sheets during

Business Template installation and a corresponding test



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@40159 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 403cef29
......@@ -963,6 +963,11 @@ class ObjectTemplateItem(BaseTemplateItem):
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(BaseTemplateItem):
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)
......
......@@ -2356,7 +2356,7 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
"""
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(ERP5TypeTestCase, LogInterceptor):
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(ERP5TypeTestCase, LogInterceptor):
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(ERP5TypeTestCase, LogInterceptor):
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:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment