From ac0ae752273e3fd5baaf7e99058c4d19c6f65e24 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Mon, 19 May 2014 15:48:04 +0200 Subject: [PATCH 1/5] [ADD] Changes to lp:openupgrade-server/8.0 so far (revno. 5147) --- openerp/addons/base/ir/ir_model.py | 29 +- openerp/addons/base/res/res_currency.py | 14 +- .../addons/openupgrade_records/__init__.py | 2 + .../addons/openupgrade_records/__openerp__.py | 63 +++ .../addons/openupgrade_records/__terp__.py | 63 +++ .../openupgrade_records/lib/__init__.py | 2 + .../addons/openupgrade_records/lib/apriori.py | 9 + .../addons/openupgrade_records/lib/compare.py | 258 +++++++++ .../model/#generate_records_wizard.py# | 95 ++++ .../openupgrade_records/model/__init__.py | 5 + .../model/analysis_wizard.py | 187 +++++++ .../model/comparison_config.py | 106 ++++ .../model/generate_records_wizard.py | 94 ++++ .../model/install_all_wizard.py | 124 +++++ .../model/openupgrade_record.py | 120 +++++ .../security/ir.model.access.csv | 3 + .../view/analysis_wizard.xml | 30 ++ .../view/comparison_config.xml | 62 +++ .../view/generate_records_wizard.xml | 52 ++ .../view/install_all_wizard.xml | 54 ++ .../view/openupgrade_record.xml | 65 +++ openerp/modules/graph.py | 11 + openerp/modules/loading.py | 51 +- openerp/modules/migration.py | 10 +- openerp/openupgrade/#openupgrade_loading.py# | 187 +++++++ openerp/openupgrade/deferred_80.py | 32 ++ openerp/openupgrade/doc/Makefile | 70 +++ openerp/openupgrade/doc/source/API.rst | 32 ++ openerp/openupgrade/doc/source/analyse.rst | 29 + openerp/openupgrade/doc/source/analysis.rst | 25 + openerp/openupgrade/doc/source/community.rst | 17 + openerp/openupgrade/doc/source/conf.py | 209 ++++++++ .../openupgrade/doc/source/development.rst | 7 + openerp/openupgrade/doc/source/devfaq.rst | 19 + openerp/openupgrade/doc/source/format.rst | 88 +++ openerp/openupgrade/doc/source/index.rst | 32 ++ openerp/openupgrade/doc/source/intro.rst | 29 + openerp/openupgrade/doc/source/migrate.py.rst | 39 ++ .../doc/source/migration_details.rst | 55 ++ .../openupgrade/doc/source/modules50-60.rst | 342 ++++++++++++ .../openupgrade/doc/source/modules60-61.rst | 345 ++++++++++++ .../openupgrade/doc/source/modules61-70.rst | 408 ++++++++++++++ openerp/openupgrade/doc/source/status.rst | 31 ++ openerp/openupgrade/doc/source/strategies.rst | 20 + openerp/openupgrade/doc/source/xmlids.rst | 64 +++ openerp/openupgrade/openupgrade.py | 504 ++++++++++++++++++ openerp/openupgrade/openupgrade_70.py | 64 +++ openerp/openupgrade/openupgrade_loading.py | 187 +++++++ openerp/openupgrade/openupgrade_log.py | 76 +++ openerp/openupgrade/openupgrade_tools.py | 31 ++ openerp/osv/orm.py | 19 +- openerp/tools/convert.py | 3 + scripts/compare_noupdate_xml_records.py | 201 +++++++ scripts/migrate.py | 261 +++++++++ 54 files changed, 4913 insertions(+), 22 deletions(-) create mode 100644 openerp/addons/openupgrade_records/__init__.py create mode 100644 openerp/addons/openupgrade_records/__openerp__.py create mode 100644 openerp/addons/openupgrade_records/__terp__.py create mode 100644 openerp/addons/openupgrade_records/lib/__init__.py create mode 100644 openerp/addons/openupgrade_records/lib/apriori.py create mode 100644 openerp/addons/openupgrade_records/lib/compare.py create mode 100644 openerp/addons/openupgrade_records/model/#generate_records_wizard.py# create mode 100644 openerp/addons/openupgrade_records/model/__init__.py create mode 100644 openerp/addons/openupgrade_records/model/analysis_wizard.py create mode 100644 openerp/addons/openupgrade_records/model/comparison_config.py create mode 100644 openerp/addons/openupgrade_records/model/generate_records_wizard.py create mode 100644 openerp/addons/openupgrade_records/model/install_all_wizard.py create mode 100644 openerp/addons/openupgrade_records/model/openupgrade_record.py create mode 100644 openerp/addons/openupgrade_records/security/ir.model.access.csv create mode 100644 openerp/addons/openupgrade_records/view/analysis_wizard.xml create mode 100644 openerp/addons/openupgrade_records/view/comparison_config.xml create mode 100644 openerp/addons/openupgrade_records/view/generate_records_wizard.xml create mode 100644 openerp/addons/openupgrade_records/view/install_all_wizard.xml create mode 100644 openerp/addons/openupgrade_records/view/openupgrade_record.xml create mode 100644 openerp/openupgrade/#openupgrade_loading.py# create mode 100644 openerp/openupgrade/deferred_80.py create mode 100644 openerp/openupgrade/doc/Makefile create mode 100644 openerp/openupgrade/doc/source/API.rst create mode 100644 openerp/openupgrade/doc/source/analyse.rst create mode 100644 openerp/openupgrade/doc/source/analysis.rst create mode 100644 openerp/openupgrade/doc/source/community.rst create mode 100644 openerp/openupgrade/doc/source/conf.py create mode 100644 openerp/openupgrade/doc/source/development.rst create mode 100644 openerp/openupgrade/doc/source/devfaq.rst create mode 100644 openerp/openupgrade/doc/source/format.rst create mode 100644 openerp/openupgrade/doc/source/index.rst create mode 100644 openerp/openupgrade/doc/source/intro.rst create mode 100644 openerp/openupgrade/doc/source/migrate.py.rst create mode 100644 openerp/openupgrade/doc/source/migration_details.rst create mode 100644 openerp/openupgrade/doc/source/modules50-60.rst create mode 100644 openerp/openupgrade/doc/source/modules60-61.rst create mode 100644 openerp/openupgrade/doc/source/modules61-70.rst create mode 100644 openerp/openupgrade/doc/source/status.rst create mode 100644 openerp/openupgrade/doc/source/strategies.rst create mode 100644 openerp/openupgrade/doc/source/xmlids.rst create mode 100644 openerp/openupgrade/openupgrade.py create mode 100644 openerp/openupgrade/openupgrade_70.py create mode 100644 openerp/openupgrade/openupgrade_loading.py create mode 100644 openerp/openupgrade/openupgrade_log.py create mode 100644 openerp/openupgrade/openupgrade_tools.py create mode 100644 scripts/compare_noupdate_xml_records.py create mode 100644 scripts/migrate.py diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index aca8ac466c89..dde5c479244c 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -35,6 +35,8 @@ from openerp.tools.translate import _ from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS +from openerp.openupgrade import openupgrade_log, openupgrade + _logger = logging.getLogger(__name__) MODULE_UNINSTALL_FLAG = '_force_unlink' @@ -145,6 +147,11 @@ def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=No def _drop_table(self, cr, uid, ids, context=None): for model in self.browse(cr, uid, ids, context): + # OpenUpgrade: do not run the new table cleanup + openupgrade.message( + cr, 'Unknown', False, False, + "Not dropping the table or view of model %s", model.model) + continue model_pool = self.pool[model.model] cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,)) result = cr.fetchone() @@ -304,6 +311,12 @@ def _drop_column(self, cr, uid, ids, context=None): for field in self.browse(cr, uid, ids, context): if field.name in MAGIC_COLUMNS: continue + # OpenUpgrade: do not run the new column cleanup + openupgrade.message( + cr, 'Unknown', False, False, + "Not dropping the column of field %s of model %s", field.name, field.model) + continue + model = self.pool[field.model] cr.execute('select relkind from pg_class where relname=%s', (model._table,)) result = cr.fetchone() @@ -951,6 +964,10 @@ def unlink(self, cr, uid, ids, context=None): return super(ir_model_data,self).unlink(cr, uid, ids, context=context) def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None): + #OpenUpgrade: log entry (used in csv import) + if xml_id: + openupgrade_log.log_xml_id(cr, module, xml_id) + model_obj = self.pool[model] if not context: context = {} @@ -1177,7 +1194,17 @@ def _process_end(self, cr, uid, modules): for (model, res_id) in to_unlink: if model in self.pool: _logger.info('Deleting %s@%s', res_id, model) - self.pool[model].unlink(cr, uid, [res_id]) + try: + cr.execute('SAVEPOINT ir_model_data_delete'); + self.pool[model].unlink(cr, uid, [res_id]) + cr.execute('RELEASE SAVEPOINT ir_model_data_delete') + except Exception: + cr.execute('ROLLBACK TO SAVEPOINT ir_model_data_delete'); + _logger.warning( + 'Could not delete obsolete record with id: %d of model %s\n' + 'Please refer to the log message right above', + res_id, model) + class wizard_model_menu(osv.osv_memory): _name = 'wizard.ir.model.menu.create' diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index d92969b564f0..b88c15858fcf 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -107,9 +107,17 @@ def init(self, cr): # we would allow duplicate "global" currencies (all having company_id == NULL) cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'res_currency_unique_name_company_id_idx'""") if not cr.fetchone(): - cr.execute("""CREATE UNIQUE INDEX res_currency_unique_name_company_id_idx - ON res_currency - (name, (COALESCE(company_id,-1)))""") + try: + cr.execute('SAVEPOINT index_currency'); + cr.execute("""CREATE UNIQUE INDEX res_currency_unique_name_company_id_idx + ON res_currency + (name, (COALESCE(company_id,-1)))""") + cr.execute('RELEASE SAVEPOINT index_currency'); + except Exception, e: + cr.execute('ROLLBACK TO SAVEPOINT index_currency'); + import logging + logging.getLogger('OpenUpgrade').debug( + 'Could not create currency unique index: %s', e) def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): res = super(res_currency, self).read(cr, user, ids, fields, context, load) diff --git a/openerp/addons/openupgrade_records/__init__.py b/openerp/addons/openupgrade_records/__init__.py new file mode 100644 index 000000000000..cf27d0bc6cee --- /dev/null +++ b/openerp/addons/openupgrade_records/__init__.py @@ -0,0 +1,2 @@ +import model +import lib diff --git a/openerp/addons/openupgrade_records/__openerp__.py b/openerp/addons/openupgrade_records/__openerp__.py new file mode 100644 index 000000000000..181093306ea4 --- /dev/null +++ b/openerp/addons/openupgrade_records/__openerp__.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'OpenUpgrade Records', + 'version': '0.2', + 'category': 'Normal', + 'description': """Allow OpenUpgrade records to be +stored in the database and compare with other servers. + +This module depends on OpenERP client lib: + + easy_install openerp-client-lib + +""", + 'author': 'OpenUpgrade Community', + 'maintainer': 'OpenUpgrade Community', + 'contributors': ['Therp BV'], + 'website': 'https://launchpad.net/~openupgrade-committers', + 'depends': [], + 'init_xml': [], + 'update_xml': [ + 'view/openupgrade_record.xml', + 'view/comparison_config.xml', + 'view/analysis_wizard.xml', + 'view/generate_records_wizard.xml', + 'view/install_all_wizard.xml', + 'security/ir.model.access.csv', + ], + 'demo_xml': [ + ], + 'test': [ + ], + 'installable': True, + 'auto_install': False, + 'external_dependencies': { + 'python': ['openerplib'], + }, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/openupgrade_records/__terp__.py b/openerp/addons/openupgrade_records/__terp__.py new file mode 100644 index 000000000000..181093306ea4 --- /dev/null +++ b/openerp/addons/openupgrade_records/__terp__.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'OpenUpgrade Records', + 'version': '0.2', + 'category': 'Normal', + 'description': """Allow OpenUpgrade records to be +stored in the database and compare with other servers. + +This module depends on OpenERP client lib: + + easy_install openerp-client-lib + +""", + 'author': 'OpenUpgrade Community', + 'maintainer': 'OpenUpgrade Community', + 'contributors': ['Therp BV'], + 'website': 'https://launchpad.net/~openupgrade-committers', + 'depends': [], + 'init_xml': [], + 'update_xml': [ + 'view/openupgrade_record.xml', + 'view/comparison_config.xml', + 'view/analysis_wizard.xml', + 'view/generate_records_wizard.xml', + 'view/install_all_wizard.xml', + 'security/ir.model.access.csv', + ], + 'demo_xml': [ + ], + 'test': [ + ], + 'installable': True, + 'auto_install': False, + 'external_dependencies': { + 'python': ['openerplib'], + }, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/openupgrade_records/lib/__init__.py b/openerp/addons/openupgrade_records/lib/__init__.py new file mode 100644 index 000000000000..3177e8b3876b --- /dev/null +++ b/openerp/addons/openupgrade_records/lib/__init__.py @@ -0,0 +1,2 @@ +import apriori +import compare diff --git a/openerp/addons/openupgrade_records/lib/apriori.py b/openerp/addons/openupgrade_records/lib/apriori.py new file mode 100644 index 000000000000..2d2dca091269 --- /dev/null +++ b/openerp/addons/openupgrade_records/lib/apriori.py @@ -0,0 +1,9 @@ +""" Encode any known changes to the database here +to help the matching process +""" + +renamed_modules = { + } + +renamed_models = { + } diff --git a/openerp/addons/openupgrade_records/lib/compare.py b/openerp/addons/openupgrade_records/lib/compare.py new file mode 100644 index 000000000000..f43a4445c40d --- /dev/null +++ b/openerp/addons/openupgrade_records/lib/compare.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2011 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +##################################################################### +# library providing a function to analyse two progressive database +# layouts from the OpenUpgrade server. +##################################################################### + +import copy + +try: + from openerp.addons.openupgrade_records.lib import apriori +except ImportError: + from openupgrade_records.lib import apriori + +keys = [ + 'module', + 'mode', + 'model', + 'field', + 'type', + 'isfunction', + 'relation', + 'required', + 'selection_keys', + 'req_default', + 'inherits', + ] + + +def module_map(module): + return apriori.renamed_modules.get( + module, module) + + +def compare_records(dict_old, dict_new, fields): + """ + Check equivalence of two OpenUpgrade field representations + with respect to the keys in the 'fields' arguments. + Take apriori knowledge into account for mapped modules or + model names. + Return True of False. + """ + for field in fields: + if field == 'module': + if (module_map(dict_old[field]) != dict_new[field]): + return False + elif field == 'model': + if (apriori.renamed_models.get( + dict_old[field], dict_old[field]) != dict_new[field]): + return False + else: + if dict_old[field] != dict_new[field]: + return False + return True + + +def search(item, item_list, fields): + """ + Find a match of a dictionary in a list of similar dictionaries + with respect to the keys in the 'fields' arguments. + Return the item if found or None. + """ + for i in item_list: + if not compare_records(item, i, fields): + continue + return i + return None + + +def fieldprint(old, new, field, text, reprs): + fieldrepr = "%s (%s)" % (old['field'], old['type']) + repr = '%s / %s / %s' % ( + old['module'].ljust(12), old['model'].ljust(24), fieldrepr.ljust(30)) + if text: + reprs.setdefault(module_map(old['module']), []).append( + "%s: %s" % (repr, text)) + else: + reprs.setdefault(module_map(old['module']), []).append( + "%s: %s is now \'%s\' ('%s')" % ( + repr, field, new[field], old[field])) + + +def report_generic(new, old, attrs, reprs): + for attr in attrs: + if attr == 'required': + if old[attr] != new['required'] and new['required']: + text = "now required" + if new['req_default']: + text += ', default = %s' % new['req_default'] + fieldprint(old, new, None, text, reprs) + elif attr == 'isfunction': + if old['isfunction'] != new['isfunction']: + if new['isfunction']: + text = "now a function" + else: + text = "not a function anymore" + fieldprint(old, new, None, text, reprs) + else: + if old[attr] != new[attr]: + fieldprint(old, new, attr, None, reprs) + + +def compare_sets(old_records, new_records): + """ + Compare a set of OpenUpgrade field representations. + Try to match the equivalent fields in both sets. + Return a textual representation of changes in a dictionary with + module names as keys. Special case is the 'general' key + which contains overall remarks and matching statistics. + """ + reprs = {'general': []} + + for record in old_records + new_records: + record['matched'] = False + origlen = len(old_records) + new_models = set([column['model'] for column in new_records]) + old_models = set([column['model'] for column in old_records]) + + matched_direct = 0 + matched_other_module = 0 + matched_other_type = 0 + matched_other_name = 0 + in_obsolete_models = 0 + + obsolete_models = [] + for model in old_models: + if model not in new_models: + obsolete_models.append(model) + reprs['general'].append('obsolete model %s' % model) + + for column in copy.copy(old_records): + if column['model'] in obsolete_models: + old_records.remove(column) + in_obsolete_models += 1 + + for model in new_models: + if model not in old_models: + reprs['general'].append('new model %s' % model) + + def match(match_fields, report_fields, warn=False): + count = 0 + for column in copy.copy(old_records): + found = search(column, new_records, match_fields) + if found: + if warn: + pass + #print "Tentatively" + report_generic(found, column, report_fields, reprs) + old_records.remove(column) + new_records.remove(found) + count += 1 + return count + + matched_direct = match( + ['module', 'mode', 'model', 'field'], + ['relation', 'type', 'selection_keys', 'inherits', + 'isfunction', 'required']) + + # other module, same type and operation + matched_other_module = match( + ['mode', 'model', 'field', 'type'], + ['module', 'relation', 'selection_keys', 'inherits', + 'isfunction', 'required']) + + # other module, same operation, other type + matched_other_type = match( + ['mode', 'model', 'field'], + ['relation', 'type', 'selection_keys', 'inherits', + 'isfunction', 'required']) + + # fields with other names + #matched_other_name = match( + # ['module', 'type', 'relation'], + # ['field', 'relation', 'type', 'selection_keys', + # 'inherits', 'isfunction', 'required'], warn=True) + + printkeys = [ + 'relation', 'required', 'selection_keys', + 'req_default', 'inherits', 'mode' + ] + for column in old_records: + # we do not care about removed function fields + if not column['isfunction']: + if column['mode'] == 'create': + column['mode'] = '' + fieldprint( + column, None, None, "DEL " + ", ".join( + [k + ': ' + str(column[k]) for k in printkeys if column[k]] + ), reprs) + + for column in new_records: + # we do not care about newly added function fields + if not column['isfunction']: + if column['mode'] == 'create': + column['mode'] = '' + fieldprint( + column, None, None, "NEW " + ", ".join( + [k + ': ' + str(column[k]) for k in printkeys if column[k]] + ), reprs) + + for line in [ + "# %d fields matched," % (origlen - len(old_records)), + "# Direct match: %d" % matched_direct, + "# Found in other module: %d" % matched_other_module, + "# Found with different type: %d" % matched_other_type, + "# Found with different name: %d" % matched_other_name, + "# In obsolete models: %d" % in_obsolete_models, + "# Not matched: %d" % len(old_records), + "# New columns: %d" % len(new_records) + ]: + reprs['general'].append(line) + return reprs + + +def compare_xml_sets(old_records, new_records): + reprs = {} + match_fields = ['module', 'model', 'name'] + for column in copy.copy(old_records): + found = search(column, new_records, match_fields) + if found: + old_records.remove(column) + new_records.remove(found) + + for record in old_records: + record['old'] = True + for record in new_records: + record['new'] = True + + sorted_records = sorted( + old_records + new_records, + key=lambda k: '%s%s%s' % (k['model'].ljust(128), 'old' in k, k['name']) + ) + for entry in sorted_records: + if 'old' in entry: + content = 'DEL %s: %s' % (entry['model'], entry['name']) + elif 'new' in entry: + content = 'NEW %s: %s' % (entry['model'], entry['name']) + reprs.setdefault(module_map(entry['module']), []).append(content) + return reprs diff --git a/openerp/addons/openupgrade_records/model/#generate_records_wizard.py# b/openerp/addons/openupgrade_records/model/#generate_records_wizard.py# new file mode 100644 index 000000000000..c2f862e15fdd --- /dev/null +++ b/openerp/addons/openupgrade_records/model/#generate_records_wizard.py# @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import os + +try: + from openerp.osv.orm import TransientModel, except_orm + from openerp.osv import fields + from openerp.openupgrade import openupgrade_tools + from openerp import pooler +except ImportError: + from osv.osv import osv_memory as TransientModel, except_osv as except_orm + from osv import fields + from openupgrade import openupgrade_tools + import pooler + +class generate_records_wizard(TransientModel): + _name = 'openupgrade.generate.records.wizard' + _description = 'OpenUpgrade Generate Records Wizard' + _columns = { + 'state': fields.selection([('init', 'init'), ('ready', 'ready')], 'State'), + } + _defaults = { + 'state': lambda *a: 'init', + } + + def generate(self, cr, uid, ids, context=None): + """ + Main wizard step. Make sure that all modules are up-to-date, + then reinitialize all installed modules. + Equivalent of running the server with '-d --init all' + + The goal of this is to fill the records table. + + TODO: update module list and versions, then update all modules? + """ + # Truncate the records table + if (openupgrade_tools.table_exists(cr, 'openupgrade_attribute') and + openupgrade_tools.table_exists(cr, 'openupgrade_record')): + cr.execute( + 'TRUNCATE openupgrade_attribute, openupgrade_record;' + ) + + # Need to get all modules in state 'installed' + module_obj = self.pool.get('ir.module.module') + module_ids = module_obj.search( + cr, uid, [('state', 'in', ['to install', 'to upgrade'])]) + if module_ids: + cr.commit() + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) + # Did we succeed above? + module_ids = module_obj.search( + cr, uid, [('state', 'in', ['to install', 'to upgrade'])]) + if module_ids: + modules = module_obj.read( + cr, uid, module_ids, ['name'], context=context) + raise except_orm( + "Cannot reliably generate records", + ("Cannot seem to install or upgrade modules " + + ', '.join([x['name'] for x in modules]))) + # Now reinitialize all installed modules + module_ids = module_obj.search( + cr, uid, [('state', '=', 'installed')]) + module_obj.write( + cr, uid, module_ids, {'state': 'to install'}) + cr.commit() + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) + self.write(cr, uid, ids, {'state': 'ready'}) + # and we are done + return True + +generate_records_wizard() + diff --git a/openerp/addons/openupgrade_records/model/__init__.py b/openerp/addons/openupgrade_records/model/__init__.py new file mode 100644 index 000000000000..e2134973f724 --- /dev/null +++ b/openerp/addons/openupgrade_records/model/__init__.py @@ -0,0 +1,5 @@ +import openupgrade_record +import comparison_config +import analysis_wizard +import generate_records_wizard +import install_all_wizard diff --git a/openerp/addons/openupgrade_records/model/analysis_wizard.py b/openerp/addons/openupgrade_records/model/analysis_wizard.py new file mode 100644 index 000000000000..73735a321013 --- /dev/null +++ b/openerp/addons/openupgrade_records/model/analysis_wizard.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import os + +try: + from openerp.osv.orm import TransientModel + from openerp.osv import fields + from openerp.addons.openupgrade_records.lib import compare + from openerp.addons import get_module_path +except ImportError: + from osv.osv import osv_memory as TransientModel + from osv import fields + from openupgrade_records.lib import compare + from addons import get_module_path + + +class openupgrade_analysis_wizard(TransientModel): + _name = 'openupgrade.analysis.wizard' + _description = 'OpenUpgrade Analysis Wizard' + _columns = { + 'server_config': fields.many2one( + 'openupgrade.comparison.config', + 'Configuration', required=True), + 'state': fields.selection( + [('init', 'Init'), ('ready', 'Ready')], 'State', + readonly=True), + 'log': fields.text('Log'), + 'write': fields.boolean( + 'Write files', + help='Write analysis files to the module directories' + ), + } + _defaults = { + 'state': lambda *a: 'init', + 'write': lambda *a: True, + } + + def get_communication(self, cr, uid, ids, context=None): + """ + Retrieve both sets of database representations, + perform the comparison and register the resulting + change set + """ + def write_file(module, version, contents, + filename='openupgrade_analysis.txt'): + module_path = get_module_path(module) + if not module_path: + return "ERROR: could not find module path:\n" + full_path = os.path.join( + module_path, 'migrations', version) + if not os.path.exists(full_path): + try: + os.makedirs(full_path) + except os.error: + return "ERROR: could not create migrations directory:\n" + logfile = os.path.join(full_path, filename) + try: + f = open(logfile, 'w') + except Exception: + return "ERROR: could not open file %s for writing:\n" % logfile + f.write(contents) + f.close() + return None + + wizard = self.browse(cr, uid, ids[0], context=context) + # Retrieve connection and access methods + conf_obj = self.pool.get('openupgrade.comparison.config') + connection = conf_obj.get_connection( + cr, uid, [wizard.server_config.id], context=context) + remote_record_obj = connection.get_model('openupgrade.record') + local_record_obj = self.pool.get('openupgrade.record') + + # Retrieve field representations and compare + remote_records = remote_record_obj.field_dump(context) + local_records = local_record_obj.field_dump(cr, uid, context) + modules_record = set([record['module'] + for record in remote_records + local_records]) + res = compare.compare_sets(remote_records, local_records) + + # Retrieve xml id representations and compare + fields = ['module', 'model', 'name'] + local_xml_record_ids = local_record_obj.search( + cr, uid, [('type', '=', 'xmlid')]) + remote_xml_record_ids = remote_record_obj.search( + [('type', '=', 'xmlid')]) + local_xml_records = [ + dict([(field, x[field]) for field in fields]) + for x in local_record_obj.read( + cr, uid, local_xml_record_ids, fields) + ] + remote_xml_records = [ + dict([(field, x[field]) for field in fields]) + for x in remote_record_obj.read( + remote_xml_record_ids, fields) + ] + modules_xml_records = set( + [record['module'] + for record in remote_xml_records + local_xml_records]) + res_xml = compare.compare_xml_sets( + remote_xml_records, local_xml_records) + + # reorder and output the result + keys = ['general'] + list(modules_record & modules_xml_records) + module_obj = self.pool.get('ir.module.module') + module_ids = module_obj.search( + cr, uid, [('state', '=', 'installed')]) + modules = dict( + [(x['name'], x) for x in module_obj.read(cr, uid, module_ids)]) + general = '' + for key in keys: + contents = "---Fields in module '%s'---\n" % key + if key in res: + contents += '\n'.join( + [unicode(line) for line in sorted(res[key])]) + if res[key]: + contents += '\n' + contents += "---XML records in module '%s'---\n" % key + if key in res_xml: + contents += '\n'.join([unicode(line) for line in res_xml[key]]) + if res_xml[key]: + contents += '\n' + if key not in res and key not in res_xml: + contents += '-- nothing has changed in this module' + if key == 'general': + general += contents + continue + if key not in modules: + general += ( + "ERROR: module not in list of installed modules:\n" + + contents) + continue + if wizard.write: + error = write_file( + key, modules[key]['installed_version'], contents) + if error: + general += error + general += contents + else: + general += contents + + # Store the general log in as many places as possible ;-) + if wizard.write and 'base' in modules: + write_file( + 'base', modules['base']['installed_version'], general, + 'openupgrade_general_log.txt') + self.pool.get('openupgrade.comparison.config').write( + cr, uid, wizard.server_config.id, + {'last_log': general}) + self.write(cr, uid, ids, {'state': 'ready', 'log': general}) + + result = { + 'name': self._description, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'openupgrade.analysis.wizard', + 'domain': [], + 'context': context, + 'type': 'ir.actions.act_window', + #'target': 'new', + 'res_id': ids[0], + } + return result + +openupgrade_analysis_wizard() diff --git a/openerp/addons/openupgrade_records/model/comparison_config.py b/openerp/addons/openupgrade_records/model/comparison_config.py new file mode 100644 index 000000000000..21a1c62dd22d --- /dev/null +++ b/openerp/addons/openupgrade_records/model/comparison_config.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import openerplib +try: + from openerp.osv.orm import Model, except_orm + from openerp.osv import fields + from openerp.tools.translate import _ +except ImportError: + from osv.osv import osv as Model, except_osv as except_orm + from osv import fields + from tools.translate import _ + + +class openupgrade_comparison_config(Model): + _name = 'openupgrade.comparison.config' + _columns = { + 'name': fields.char('Name', size=64), + 'server': fields.char('Server', size=64, required=True), + 'port': fields.integer('Port', required=True), + 'protocol': fields.selection( + [('http://', 'XML-RPC')], + # ('https://', 'XML-RPC Secure')], not supported by libopenerp + 'Protocol', required=True), + 'database': fields.char('Database', size=64, required=True), + 'username': fields.char('Username', size=24, required=True), + 'password': fields.char('Password', size=24, required=True, + password=True), + 'last_log': fields.text('Last log'), + } + _defaults = { + 'port': lambda *a: 8069, + 'protocol': lambda *a: 'http://', + } + + def get_connection(self, cr, uid, ids, context=None): + if not ids: + raise except_orm( + _("Cannot connect"), _("Invalid id passed.")) + conf = self.read(cr, uid, ids[0], context=None) + return openerplib.get_connection( + hostname=conf['server'], + database=conf['database'], + login=conf['username'], + password=conf['password'], + port=conf['port'], + ) + + def test_connection(self, cr, uid, ids, context=None): + try: + connection = self.get_connection(cr, uid, [ids[0]], context) + user_model = connection.get_model("res.users") + ids = user_model.search([("login", "=", "admin")]) + user_info = user_model.read(ids[0], ["name"]) + except Exception, e: + raise except_orm( + _("Connection failed."), unicode(e)) + raise except_orm( + _("Connection succesful."), + _("%s is connected.") % user_info["name"] + ) + + def analyze(self, cr, uid, ids, context=None): + """ + Run the analysis wizard + """ + wizard_obj = self.pool.get('openupgrade.analysis.wizard') + wizard_id = wizard_obj.create( + cr, uid, {'server_config': ids[0]}, context) + result = { + 'name': wizard_obj._description, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'openupgrade.analysis.wizard', + 'domain': [], + 'context': context, + 'type': 'ir.actions.act_window', + 'target': 'new', + 'res_id': wizard_id, + 'nodestroy': True, + } + return result + +openupgrade_comparison_config() diff --git a/openerp/addons/openupgrade_records/model/generate_records_wizard.py b/openerp/addons/openupgrade_records/model/generate_records_wizard.py new file mode 100644 index 000000000000..56739ca47d89 --- /dev/null +++ b/openerp/addons/openupgrade_records/model/generate_records_wizard.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +try: + from openerp.osv.orm import TransientModel, except_orm + from openerp.osv import fields + from openerp.openupgrade import openupgrade_tools + from openerp import pooler +except ImportError: + from osv.osv import osv_memory as TransientModel, except_osv as except_orm + from osv import fields + from openupgrade import openupgrade_tools + import pooler + + +class generate_records_wizard(TransientModel): + _name = 'openupgrade.generate.records.wizard' + _description = 'OpenUpgrade Generate Records Wizard' + _columns = { + 'state': fields.selection( + [('init', 'init'), ('ready', 'ready')], 'State'), + } + _defaults = { + 'state': lambda *a: 'init', + } + + def generate(self, cr, uid, ids, context=None): + """ + Main wizard step. Make sure that all modules are up-to-date, + then reinitialize all installed modules. + Equivalent of running the server with '-d --init all' + + The goal of this is to fill the records table. + + TODO: update module list and versions, then update all modules? + """ + # Truncate the records table + if (openupgrade_tools.table_exists(cr, 'openupgrade_attribute') and + openupgrade_tools.table_exists(cr, 'openupgrade_record')): + cr.execute( + 'TRUNCATE openupgrade_attribute, openupgrade_record;' + ) + + # Need to get all modules in state 'installed' + module_obj = self.pool.get('ir.module.module') + module_ids = module_obj.search( + cr, uid, [('state', 'in', ['to install', 'to upgrade'])]) + if module_ids: + cr.commit() + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) + # Did we succeed above? + module_ids = module_obj.search( + cr, uid, [('state', 'in', ['to install', 'to upgrade'])]) + if module_ids: + modules = module_obj.read( + cr, uid, module_ids, ['name'], context=context) + raise except_orm( + "Cannot reliably generate records", + ("Cannot seem to install or upgrade modules " + + ', '.join([x['name'] for x in modules]))) + # Now reinitialize all installed modules + module_ids = module_obj.search( + cr, uid, [('state', '=', 'installed')]) + module_obj.write( + cr, uid, module_ids, {'state': 'to install'}) + cr.commit() + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) + self.write(cr, uid, ids, {'state': 'ready'}) + # and we are done + return True + +generate_records_wizard() diff --git a/openerp/addons/openupgrade_records/model/install_all_wizard.py b/openerp/addons/openupgrade_records/model/install_all_wizard.py new file mode 100644 index 000000000000..8c710b4a8700 --- /dev/null +++ b/openerp/addons/openupgrade_records/model/install_all_wizard.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import time +try: + from openerp.osv.orm import TransientModel + from openerp.osv import fields + from openerp import pooler +except ImportError: + from osv.osv import osv_memory as TransientModel + from osv import fields + import pooler + + +class install_all_wizard(TransientModel): + _name = 'openupgrade.install.all.wizard' + _description = 'OpenUpgrade Install All Wizard' + _columns = { + 'state': fields.selection( + [('init', 'init'), ('ready', 'ready')], 'State', readonly=True), + 'to_install': fields.integer( + 'Number of modules to install', readonly=True), + } + _defaults = { + 'state': lambda *a: 'init', + } + + def default_get(self, cr, uid, fields, context=None): + """ + Update module list and retrieve the number + of installable modules + """ + res = super(install_all_wizard, self).default_get( + cr, uid, fields, context=None) + module_obj = self.pool.get('ir.module.module') + update, add = module_obj.update_list(cr, uid,) + print "%s modules added" % add + module_ids = module_obj.search( + cr, uid, [ + ('state', 'not in', + ['installed', 'uninstallable', 'unknown']) + ]) + res.update( + {'to_install': module_ids and len(module_ids) or False} + ) + return res + + def quirk_fiscalyear(self, cr, uid, ids, context=None): + """ + Install account module first and create a fiscal year, + in order to prevent "No fiscal year defined" exception + during an upgrade or reinstallation of the account module. + + Refer to account_fiscalyear.find(), which is called as + a default function by the orm upon module upgrade. + """ + module_obj = self.pool.get('ir.module.module') + pool = self.pool + # Retrieve status of the account module + account_module_id = module_obj.search( + cr, uid, [('name', '=', 'account')], context=context)[0] + state = module_obj.read( + cr, uid, account_module_id, ['state'], context=context)['state'] + if state != 'installed': + # Cancel installation of other modules + module_ids = module_obj.search( + cr, uid, [('state', '=', 'to install')]) + module_obj.write(cr, uid, module_ids, {'state': 'uninstalled'}) + # Mark the module and its dependencies + module_obj.button_install(cr, uid, [account_module_id]) + # Install account module + cr.commit() + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) + # get or create today's fiscal year + fy_obj = pool.get('account.fiscalyear') + if not fy_obj.find(cr, uid, False, exception=False, context=context): + fy_obj.create(cr, uid, { + 'name': time.strftime('%Y'), + 'code': time.strftime('%Y'), + 'date_start': "%s-01-01" % time.strftime('%Y'), + 'date_stop': "%s-12-31" % time.strftime('%Y'), + }) + + def install_all(self, cr, uid, ids, context=None): + """ + Main wizard step. Set all installable modules to install + and actually install them. + """ + module_obj = self.pool.get('ir.module.module') + module_ids = module_obj.search( + cr, uid, [ + ('state', 'not in', + ['installed', 'uninstallable', 'unknown'])]) + if module_ids: + module_obj.write( + cr, uid, module_ids, {'state': 'to install'}) + cr.commit() + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) + self.write(cr, uid, ids, {'state': 'ready'}) + return True + +install_all_wizard() diff --git a/openerp/addons/openupgrade_records/model/openupgrade_record.py b/openerp/addons/openupgrade_records/model/openupgrade_record.py new file mode 100644 index 000000000000..cd902e6e9b71 --- /dev/null +++ b/openerp/addons/openupgrade_records/model/openupgrade_record.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module Copyright (C) 2012-2014 OpenUpgrade community +# https://launchpad.net/~openupgrade-committers +# +# Contributors: +# Therp BV +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +try: + from openerp.osv.orm import Model + from openerp.osv import fields +except ImportError: + from osv.osv import osv as Model + from osv import fields + + +# Cannot use forward references in 6.0 +class openupgrade_record(Model): + _name = 'openupgrade.record' +openupgrade_record() + + +class openupgrade_attribute(Model): + _name = 'openupgrade.attribute' + _rec_name = 'name' + _columns = { + 'name': fields.char( + 'Name', size=24, + readonly=True, + ), + 'value': fields.char( + 'Value', + size=4096, + readonly=True, + ), + 'record_id': fields.many2one( + 'openupgrade.record', ondelete='CASCADE', + readonly=True, + ), + } +openupgrade_attribute() + + +class openupgrade_record(Model): + _inherit = 'openupgrade.record' + + _columns = { + 'name': fields.char('Name', size=256, readonly=True), + 'module': fields.char('Module', size=128, readonly=True), + 'model': fields.char('Model', size=128, readonly=True), + 'field': fields.char('Field', size=128, readonly=True), + 'mode': fields.selection( + [('create', 'Create'), ('modify', 'Modify')], + 'Mode', + help='Set to Create if a field is newly created ' + 'in this module. If this module modifies an attribute of an ' + 'exting field, set to Modify.', + readonly=True, + ), + 'type': fields.selection( + [('field', 'Field'), ('xmlid', 'XML ID')], + 'Type', + readonly=True, + ), + 'attribute_ids': fields.one2many( + 'openupgrade.attribute', 'record_id', 'Attributes', + readonly=True, + ), + } + + def field_dump(self, cr, uid, context=None): + keys = [ + 'module', + 'mode', + 'model', + 'field', + 'type', + 'isfunction', + 'relation', + 'required', + 'selection_keys', + 'req_default', + 'inherits', + ] + + template = dict([(x, False) for x in keys]) + ids = self.search(cr, uid, [('type', '=', 'field')], context=context) + records = self.browse(cr, uid, ids, context=context) + data = [] + for record in records: + repr = template.copy() + repr.update({ + 'module': record.module, + 'model': record.model, + 'field': record.field, + 'mode': record.mode, + }) + repr.update( + dict([(x.name, x.value) for x in record.attribute_ids])) + data.append(repr) + return data + +openupgrade_record() diff --git a/openerp/addons/openupgrade_records/security/ir.model.access.csv b/openerp/addons/openupgrade_records/security/ir.model.access.csv new file mode 100644 index 000000000000..c836d78b434f --- /dev/null +++ b/openerp/addons/openupgrade_records/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_openupgrade_record","openupgrade.record all","model_openupgrade_record",,1,0,0,0 +"access_openupgrade_attribute","openupgrade.attribute all","model_openupgrade_attribute",,1,0,0,0 diff --git a/openerp/addons/openupgrade_records/view/analysis_wizard.xml b/openerp/addons/openupgrade_records/view/analysis_wizard.xml new file mode 100644 index 000000000000..3d7b4990afd8 --- /dev/null +++ b/openerp/addons/openupgrade_records/view/analysis_wizard.xml @@ -0,0 +1,30 @@ + + + + + view.openupgrade.analysis_wizard.form + openupgrade.analysis.wizard + form + +
+ + + + +