From 6c6b77a0cdc4980221b6ca57c78677f613b1a878 Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Mon, 3 Jul 2017 21:52:55 +0200 Subject: [PATCH] Prevent ERP5 site creation if SQL databases aren't empty, add an option to force /reviewed-on https://lab.nexedi.com/nexedi/erp5/merge_requests/314 --- product/ERP5/ERP5Site.py | 62 ++++++++++---------- product/ERP5/dtml/addERP5Site.dtml | 4 ++ product/ERP5/tests/testERP5Site.py | 67 ++++++++++++++++++++++ product/ERP5Type/tests/ERP5TypeTestCase.py | 27 +++++---- 4 files changed, 115 insertions(+), 45 deletions(-) create mode 100644 product/ERP5/tests/testERP5Site.py diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index 0818fddd54..9956347cc7 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -73,6 +73,7 @@ def manage_addERP5Site(self, cmf_activity_sql_connection_string='test test', light_install=0, reindex=1, + sql_reset=0, RESPONSE=None): ''' Adds a portal instance. @@ -87,7 +88,8 @@ def manage_addERP5Site(self, cmf_activity_sql_connection_string, create_activities=create_activities, light_install=light_install, - reindex=reindex) + reindex=reindex, + sql_reset=sql_reset) gen.setupDefaultProperties(p, title, description, @@ -1967,43 +1969,37 @@ class ERP5Generator(PortalGenerator): if not p.hasObject('portal_catalog'): addTool('ERP5 Catalog', None) - if 1: - # Add Default SQL connection - if not p.hasObject('erp5_sql_connection'): - addSQLConnection = p.manage_addProduct['ZMySQLDA'].\ - manage_addZMySQLConnection - addSQLConnection('erp5_sql_connection', - 'ERP5 SQL Server Connection', - p.erp5_sql_connection_string) - - # Add Deferred SQL Connections - if not p.hasObject('erp5_sql_deferred_connection'): - addSQLConnection = p.manage_addProduct['ZMySQLDA'].\ - manage_addZMySQLConnection - addSQLConnection('erp5_sql_deferred_connection', - 'ERP5 SQL Server Deferred Connection', - p.erp5_sql_deferred_connection_string, - deferred=True) - - # Add Activity SQL Connections - if not p.hasObject('cmf_activity_sql_connection'): - addSQLConnection = p.manage_addProduct['CMFActivity'].\ - manage_addActivityConnection - addSQLConnection('cmf_activity_sql_connection', - 'CMF Activity SQL Server Connection', - p.cmf_activity_sql_connection_string) - # Warning : This transactionless connection is created with + sql_reset = kw.get('sql_reset', 0) + def addSQLConnection(id, title, **kw): + if p.hasObject(id): + return + # Warning : The transactionless connection is created with # the activity connection string and not the catalog's because # it's not compatible with the hot reindexing feature. # Though, it has nothing to do with activities. # The only difference compared to activity connection is the # minus prepended to the connection string. - if not p.hasObject('erp5_sql_transactionless_connection'): - addSQLConnection = p.manage_addProduct['ZMySQLDA'].\ - manage_addZMySQLConnection - addSQLConnection('erp5_sql_transactionless_connection', - 'ERP5 Transactionless SQL Server Connection', - '-%s' % p.cmf_activity_sql_connection_string) + if id == 'erp5_sql_transactionless_connection': + connection_string = '-' + p.cmf_activity_sql_connection_string + else: + connection_string = getattr(p, id + '_string') + manage_add(id, title, connection_string, **kw) + if not sql_reset and p[id]().tables(): + raise Exception("Database %r is not empty." % connection_string) + + # Add Z MySQL Connections + manage_add = p.manage_addProduct['ZMySQLDA'].manage_addZMySQLConnection + addSQLConnection('erp5_sql_connection', + 'ERP5 SQL Server Connection') + addSQLConnection('erp5_sql_deferred_connection', + 'ERP5 SQL Server Deferred Connection', + deferred=True) + addSQLConnection('erp5_sql_transactionless_connection', + 'ERP5 Transactionless SQL Server Connection') + # Add Activity SQL Connections + manage_add = p.manage_addProduct['CMFActivity'].manage_addActivityConnection + addSQLConnection('cmf_activity_sql_connection', + 'CMF Activity SQL Server Connection') # Add ERP5Form Tools addERP5Tool(p, 'portal_selections', 'Selection Tool') diff --git a/product/ERP5/dtml/addERP5Site.dtml b/product/ERP5/dtml/addERP5Site.dtml index 51d500b48f..b6d01e3abc 100644 --- a/product/ERP5/dtml/addERP5Site.dtml +++ b/product/ERP5/dtml/addERP5Site.dtml @@ -97,6 +97,10 @@ label { <label class="form-label" for="cmf_activity_sql_connection_string">CMF Activity Database</label> <input class="form-element" name="cmf_activity_sql_connection_string" id="cmf_activity_sql_connection_string" type="text" size="40" value="test test"/> </div> + <div> + <label class="form-label" for="sql_reset">Drop any existing table</label> + <input type="checkbox" name="sql_reset:int" value="1" id="sql_reset"/> + </div> <div> <p>The connection strings used for Z MySQL Database Connections are of the form:</p> <blockquote><code>database[@host[:port]] [user [password [unix_socket]]]</code></blockquote> diff --git a/product/ERP5/tests/testERP5Site.py b/product/ERP5/tests/testERP5Site.py new file mode 100644 index 0000000000..47dc41efc2 --- /dev/null +++ b/product/ERP5/tests/testERP5Site.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2014 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly advised to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +############################################################################## + +import unittest +from Products.ERP5Type.tests.ERP5TypeTestCase import ( + ERP5TypeTestCase, failed_portal_installation) + +class TestERP5Site(ERP5TypeTestCase): + + def getPortalName(self): + return self._testMethodName + + def setUp(self): + pass + + def test_00_fillSQL(self): + """ + A empty test that is run first, only to make sure that the other test + tries to create an ERP5 Site with a non-empty SQL database. + """ + super(TestERP5Site, self).setUp() + self.portal + self.assertNotIn(self.getPortalName(), failed_portal_installation) + + def test_01_do_not_wipe_SQL_data_by_default(self): + """ + Unless wanted explicitely by the user, creating an ERP5 Site must fail + if the given SQL parameters give access to a database that already contains + data. This prevents existing SQL databases from being wiped mistakenly. + """ + kw = self._getSiteCreationParameterDict() + del kw['sql_reset'] + self._getSiteCreationParameterDict = lambda: kw + self.assertRaisesRegexp(Exception, "not empty", + super(TestERP5Site, self).setUp) + self.assertFalse(hasattr(self, 'portal')) + self.assertIn(self.getPortalName(), failed_portal_installation) + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestERP5Site)) + return suite diff --git a/product/ERP5Type/tests/ERP5TypeTestCase.py b/product/ERP5Type/tests/ERP5TypeTestCase.py index 03a4280fae..66551b9e08 100644 --- a/product/ERP5Type/tests/ERP5TypeTestCase.py +++ b/product/ERP5Type/tests/ERP5TypeTestCase.py @@ -211,7 +211,6 @@ DateTime._parse_args = _parse_args class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase): """Mixin class for ERP5 based tests. """ - def dummy_test(self): ZopeTestCase._print('All tests are skipped when --save option is passed ' 'with --update_business_templates or without --load') @@ -1041,6 +1040,18 @@ class ERP5TypeCommandLineTestCase(ERP5TypeTestCaseMixin): if not quiet: ZopeTestCase._print('done (%.3fs)\n' % (time.time() - start)) + def _getSiteCreationParameterDict(self): + kw = _getConnectionStringDict() + # manage_addERP5Site does not accept the following 2 arguments + for k in ('erp5_sql_deferred_connection_string', + 'erp5_sql_transactionless_connection_string'): + kw.pop(k, None) + email_from_address = os.environ.get('email_from_address') + if email_from_address is not None: + kw['email_from_address'] = email_from_address + kw['sql_reset'] = 1 + return kw + def setUpERP5Site(self, business_template_list=(), quiet=0, @@ -1092,23 +1103,15 @@ class ERP5TypeCommandLineTestCase(ERP5TypeTestCaseMixin): if not quiet: ZopeTestCase._print('Adding %s ERP5 Site ... ' % portal_name) - extra_constructor_kw = _getConnectionStringDict() - # manage_addERP5Site does not accept the following 2 arguments - for k in ('erp5_sql_deferred_connection_string', - 'erp5_sql_transactionless_connection_string'): - extra_constructor_kw.pop(k, None) - email_from_address = os.environ.get('email_from_address') - if email_from_address is not None: - extra_constructor_kw['email_from_address'] = email_from_address - + kw = self._getSiteCreationParameterDict() factory = app.manage_addProduct['ERP5'] factory.manage_addERP5Site(portal_name, erp5_catalog_storage=erp5_catalog_storage, light_install=light_install, reindex=reindex, create_activities=create_activities, - **extra_constructor_kw ) - sql = extra_constructor_kw.get('erp5_sql_connection_string') + **kw) + sql = kw.get('erp5_sql_connection_string') if sql: app[portal_name]._setProperty('erp5_site_global_id', base64.standard_b64encode(sql)) -- 2.30.9