Commit e30bf3be authored by Alain Takoudjou's avatar Alain Takoudjou

Improve slapos_upgrader, add some scripts which allow to fix site after upgrade

parent fb500f47
......@@ -112,3 +112,102 @@ def ERP5Site_deleteVifibAccounting(self):
][0].Base_createCloneDocument(batch_mode=1)
bt5.activate().install(force=1, update_catalog=0)
return 'Done.'
def upgradeObjectClass(self, test_before, from_class, to_class, test_after,
test_only=0):
"""
Upgrade the class of all objects inside this particular folder:
test_before and test_after have to be a method with one parameter.
from_class and to_class can be classes (o.__class___) or strings like:
'Products.ERP5Type.Document.Folder.Folder'
XXX Some comments by Seb:
- it is not designed to work for modules with thousands of objects,
so it totally unusable when you have millions of objects
- it is totally unsafe. There is even such code inside :
self.manage_delObjects(id of original object)
commit()
self._setObject(new object instance)
So it is possible to definitely loose data.
- There is no proof that upgrade is really working. With such a
dangerous operation, it would be much more safer to have a proof,
something like the "fix point" after doing a synchronization. Such
checking should even be done before doing commit (like it might
be possible to export objects in the xml format used for exports
before and after, and run a diff).
"""
from zLOG import LOG, WARNING
from Acquisition import aq_base, aq_parent, aq_inner
import transaction
LOG("upgradeObjectClass: folder ", 0, self.getId())
test_list = []
def getClassFromString(a_klass):
from_module = '.'.join(a_klass.split('.')[:-1])
real_klass = a_klass.split('.')[-1]
# XXX It is possible that API Change for Python 2.6.
mod = __import__(from_module, globals(), locals(), [real_klass])
return getattr(mod, real_klass)
if isinstance(from_class, type('')):
from_class = getClassFromString(from_class)
if isinstance(to_class, type('')):
to_class = getClassFromString(to_class)
for o in self.listFolderContents():
if not test_before(o):
continue
# Make sure this sub object is not the same as object
if o.getPhysicalPath() != self.getPhysicalPath():
id = o.getId()
obase = aq_base(o)
# Check if the subobject have to also be upgraded
if hasattr(obase,'upgradeObjectClass'):
test_list += o.upgradeObjectClass(test_before=test_before, \
from_class=from_class, to_class=to_class,
test_after=test_after, test_only=test_only)
# Test if we must apply the upgrade
if test_before(o) is not None:
LOG("upgradeObjectClass: id ", 0, id)
klass = obase.__class__
LOG("upgradeObjectClass: klass ", 0 ,str(klass))
LOG("upgradeObjectClass: from_class ", 0 ,str(from_class))
if klass == from_class and not test_only:
try:
newob = to_class(obase.id)
newob.id = obase.id # This line activates obase.
except AttributeError:
newob = to_class(id)
newob.id = id
keys = obase.__dict__.keys()
for k in keys:
if k not in ('id', 'meta_type', '__class__'):
setattr(newob,k,obase.__dict__[k])
LOG("upgradeObjectClass: ",0,"Delete old object: %s" % str(id))
self.manage_delObjects(id)
LOG("upgradeObjectClass: ",0,"add new object: %s" % str(newob.id))
self._setObject(id, newob)
transaction.commit()
LOG("upgradeObjectClass: ",0,"newob.__class__: %s" % str(newob.__class__))
object_to_test = self._getOb(id)
test_list += test_after(object_to_test)
if klass == from_class and test_only:
test_list += test_after(o)
return test_list
def checkUpgradeObjectClass(self, test_method):
portal_type = self.getPortalType()
mod = __import__("erp5.portal_type", globals(), locals(), [portal_type])
new_class = getattr(mod, portal_type)
if self.__class__ == new_class:
return "Object Class for '%s' is already fixed" % portal_type
return upgradeObjectClass(self.getPortalObject(), test_method,
self.__class__, new_class, test_method)
\ No newline at end of file
......@@ -6,10 +6,22 @@
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>SlapOSUpgrader</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>extension.erp5.SlapOSUpgrader</string> </value>
......@@ -33,7 +45,12 @@
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
<tuple>
<string>W:165, 6: Redefining built-in \'id\' (redefined-builtin)</string>
<string>W:143, 2: Unused variable \'aq_parent\' (unused-variable)</string>
<string>W:142, 2: Unused variable \'WARNING\' (unused-variable)</string>
<string>W:143, 2: Unused variable \'aq_inner\' (unused-variable)</string>
</tuple>
</value>
</item>
<item>
......@@ -43,13 +60,28 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
......@@ -62,7 +94,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -71,7 +103,7 @@
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>if obj.getId() == \'portal_tests\':\n
return obj\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>obj</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_beforeTestMethod</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>checkUpgradeObjectClass</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>SlapOSUpgrader</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_checkUpgradeObjectClass</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>"""\n
This script should returns always two list of Business Template.\n
- The first list is to resolve dependencies and upgrade.\n
- The second list is what you want to keep. This is useful if we want to keep \n
a old business template without updating it and without removing it\n
"""\n
portal = context.getPortalObject()\n
portal_templates = portal.portal_templates\n
\n
#installed_bt5_list = [bt5.getTitle() for bt5 in portal_templates.getInstalledBusinessTemplateList()]\n
available_bt5_list = [bt5 for bt5 in \n
portal_templates.getRepositoryBusinessTemplateList(newest_only=True)]\n
signature = portal.ERP5Site_getUpgraderSignature()\n
bt5_id_list = []\n
for bt5_id in signature.get(\'required_bt5_id_list\', []):\n
available_bt5 = [bt5 for bt5 in available_bt5_list if bt5.title == bt5_id][0]\n
if available_bt5:\n
bt5_id_list.append(available_bt5.getTitle())\n
\n
keep_bt5_id_list = [\'erp5_ui_test\',\n
\'erp5_ui_test_core\',\n
\'slapos_category\',\n
\'erp5_secure_payment\']\n
return bt5_id_list, keep_bt5_id_list\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getUpgradeBusinessTemplateList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -69,6 +69,8 @@ ALARM_DICT = {\n
\n
REQUIRED_BT5_ID_LIST = [\n
# core of core\n
\'erp5_mysql_innodb_catalog\',\n
\'erp5_full_text_myisam_catalog\',\n
\'erp5_mysql_innodb_catalog\',\n
\'erp5_core\',\n
\'erp5_property_sheets\',\n
......@@ -95,43 +97,11 @@ REQUIRED_BT5_ID_LIST = [\n
\'erp5_km\',\n
\'erp5_simulation\',\n
\'erp5_trade\',\n
\'erp5_ui_test_core\',\n
\'erp5_system_event\',\n
\'erp5_ui_test\',\n
\'erp5_secure_payment\',\n
\'erp5_payzen_secure_payment\',\n
\'erp5_web\',\n
\'erp5_project\',\n
\'erp5_credential_oauth2\',\n
# vifib deinstallation\n
\'vifib_web_ui_test\',\n
\'vifib_test\',\n
\'vifib_erp5\',\n
# \'vifib_configurator\', # not used\n
\'vifib_slapos_accounting\',\n
\'vifib_upgrader\',\n
\'vifib_data_web\',\n
\'vifib_data_simulation\',\n
\'vifib_data_payzen\',\n
\'vifib_data_category\',\n
\'vifib_data\',\n
\'vifib_slapos_rest_api\',\n
\'vifib_slapos_rest_api_v1\',\n
\'vifib_slapos_rest_api_tool_portal_type\',\n
\'vifib_web\',\n
\'vifib_agent\',\n
\'vifib_l10n_fr\',\n
\'vifib_slapos_capacity\',\n
\'vifib_slapos_core\',\n
\'vifib_software_pdm\',\n
\'vifib_forge_release\',\n
\'vifib_open_trade\',\n
\'vifib_payzen\',\n
\'vifib_crm\',\n
\'vifib_invoicing\',\n
\'vifib_core\',\n
\'vifib_slap\',\n
\'vifib_base\',\n
\'vifib_mysql_innodb_catalog\',\n
# new erp5 business templates\n
\'erp5_promise\',\n
\'erp5_web_shacache\',\n
......@@ -141,6 +111,9 @@ REQUIRED_BT5_ID_LIST = [\n
\'slapos_configurator\',\n
\'slapos_cache\',\n
\'slapos_cloud\',\n
\'slapos_crm\',\n
\'slapos_hypermedia\',\n
\'slapos_jio\',\n
\'slapos_slap_tool\',\n
\'slapos_category\',\n
\'slapos_rest_api_tool_portal_type\',\n
......@@ -156,13 +129,13 @@ REINSTALLABLE_BT5_ID_LIST = ()\n
\n
# items to keep even if marked by BT5 to \'Remove\'\n
KEEP_ORIGINAL_DICT = {\n
\'vifib_data_payzen\': ( \'portal_secure_payments\', ),\n
\'vifib_slapos_core\': ( \'hosting_subscription_module\', \'software_installation_module\', \'software_instance_module\',\n
\'erp5_secure_payment\': ( \'portal_secure_payments\', ),\n
\'slapos_cloud\': ( \'hosting_subscription_module\', \'software_installation_module\', \'software_instance_module\',\n
\'portal_types/Hosting Subscription\', \'portal_types/Hosting Subscription Module\', \'portal_types/Slave Instance\',\n
\'portal_types/Software Installation\', \'portal_types/Software Installation Module\', \'portal_types/Software Instance\',\n
\'portal_types/Software Instance Module\'),\n
\'vifib_open_trade\': ( \'portal_types/Subscription Item Root Simulation Rule\', ),\n
\'vifib_payzen\': (\'portal_types/Payzen Event\', \'portal_types/Payzen Event Message\',),\n
\'slapos_accounting\': ( \'portal_types/Subscription Item Root Simulation Rule\', ),\n
\'slapos_payzen\': (\'portal_types/Payzen Event\', \'portal_types/Payzen Event Message\',),\n
}\n
\n
# Items which need validation at upgrade time\n
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
\n
portal_classes = getattr(portal, \'portal_classes\', None)\n
if portal_classes:\n
portal.manage_delObjects([\'portal_classes\'])\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_postUpgradeFixUpPortalClasses</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
\n
return context.Base_checkUpgradeObjectClass(portal.portal_tests, context.Base_beforeTestMethod)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_upgradePortalTestsClass</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
portal_type = \'Template Tool\'\n
tag = \'upgrade_upgrader_%s\' % random.randint(0, 2000)\n
method_kw = {\'bt5_list\':[\'erp5_upgrader\', \'slapos_upgrader\'],\n
\'deprecated_after_script_dict\': None,\n
\'deprecated_reinstall_set\': None,\n
\'dry_run\': False,\n
\'delete_orphaned\': False,\n
\'keep_bt5_id_set\': [],\n
\'update_catalog\': False}\n
\n
\n
portal.portal_catalog.searchAndActivate(\n
portal_type=portal_type,\n
method_id=\'upgradeSite\',\n
method_kw=method_kw,\n
activate_kw=dict(tag=tag, priority=2))\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_upgradeUpgraderBusinessTemplate</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_zFixUpgradeDatabase</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>DROP PROCEDURE IF EXISTS upgrade_fix_database;\n
\n
DELIMITER //\n
\n
CREATE PROCEDURE upgrade_fix_database()\n
BEGIN\n
\n
/* Create table accounting_transaction */\n
CREATE TABLE IF NOT EXISTS `accounting_transaction` (\n
`uid` BIGINT UNSIGNED NOT NULL,\n
`order_id` TINYINT UNSIGNED NOT NULL,\n
\n
`section_uid` BIGINT UNSIGNED,\n
`mirror_section_uid` BIGINT UNSIGNED,\n
`resource_uid` BIGINT UNSIGNED,\n
\n
`project_uid` BIGINT UNSIGNED,\n
`payment_uid` BIGINT UNSIGNED,\n
\n
`accounting_transaction_title` VARCHAR(255),\n
`reference` VARCHAR(255),\n
`specific_reference` VARCHAR(255),\n
\n
`operation_date` datetime default NULL,\n
\n
`total_debit` real,\n
`total_credit` real,\n
\n
PRIMARY KEY (`uid`, `order_id`),\n
KEY (`section_uid`, `mirror_section_uid`)\n
-- TODO: keys\n
) ENGINE=InnoDB;\n
\n
/* Alter table catalog */\n
-- add a column grouping_date safely\n
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()\n
AND COLUMN_NAME=\'grouping_date\' AND TABLE_NAME=\'catalog\') ) THEN\n
ALTER TABLE `catalog` ADD `grouping_date` datetime AFTER `grouping_reference`;\n
END IF;\n
\n
\n
/* Alter table stock */\n
-- add a column is_accountable safely\n
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()\n
AND COLUMN_NAME=\'is_accountable\' AND TABLE_NAME=\'stock\') ) THEN\n
ALTER TABLE `stock` ADD COLUMN `is_accountable` BOOLEAN AFTER `is_cancellation`;\n
END IF;\n
\n
-- add a column explanation_uid safely\n
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()\n
AND COLUMN_NAME=\'explanation_uid\' AND TABLE_NAME=\'stock\') ) THEN\n
ALTER TABLE `stock` ADD COLUMN `explanation_uid` BIGINT UNSIGNED AFTER `order_id`;\n
END IF;\n
\n
-- add a column funding_uid safely\n
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()\n
AND COLUMN_NAME=\'funding_uid\' AND TABLE_NAME=\'stock\') ) THEN\n
ALTER TABLE `stock` ADD COLUMN `funding_uid` BIGINT UNSIGNED AFTER `order_id`;\n
END IF;\n
\n
-- add a column payment_request_uid safely\n
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()\n
AND COLUMN_NAME=\'payment_request_uid\' AND TABLE_NAME=\'stock\') ) THEN\n
ALTER TABLE `stock` ADD COLUMN `payment_request_uid` BIGINT UNSIGNED AFTER `order_id`;\n
END IF;\n
\n
END//\n
\n
DELIMITER ;\n
\n
CALL upgrade_fix_database();</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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