diff --git a/upgrade_analysis/README.rst b/upgrade_analysis/README.rst index f3d1c48de..2b99745a8 100644 --- a/upgrade_analysis/README.rst +++ b/upgrade_analysis/README.rst @@ -1,59 +1,8 @@ -.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png - :target: https://www.gnu.org/licenses/agpl - :alt: License: AGPL-3 +================ +Upgrade Analysis +================ -=============================== -OpenUpgrade Database Comparison -=============================== - -This module provides the tool to generate the database analysis files that indicate how the Odoo data model and module data have changed between two versions of Odoo. Database analysis files for the core modules are included in the OpenUpgrade distribution so as a migration script developer you will not usually need to use this tool yourself. If you do need to run your analysis of a custom set of modules, please refer to the documentation here: https://doc.therp.nl/openupgrade/analysis.html - -Installation -============ - -This module has a python dependency on openerp-client-lib. You need to make this module available in your Python environment, for instance by installing it with the pip tool. - -Known issues / Roadmap -====================== - -* scripts/compare_noupdate_xml_records.py should be integrated in the analysis process (#590) -* Log removed modules in the module that owned them (#468) -* Detect renamed many2many tables (#213) - -Bug Tracker -=========== - -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smash it by providing detailed and welcomed feedback. - -Images ------- - -* Odoo Community Association: `Icon `_. - -Contributors ------------- - -* Stefan Rijnhart -* Holger Brunn -* Pedro M. Baeza -* Ferdinand Gassauer -* Florent Xicluna -* Miquel Raïch - -Maintainer ----------- - -.. image:: https://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: https://odoo-community.org - -This module is maintained by the OCA. - -OCA, or the Odoo Community Association, is a nonprofit organization whose -mission is to support the collaborative development of Odoo features and -promote its widespread use. - -To contribute to this module, please visit https://odoo-community.org. +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/upgrade_analysis/__init__.py b/upgrade_analysis/__init__.py index c102a8ca6..abe0f8c3c 100644 --- a/upgrade_analysis/__init__.py +++ b/upgrade_analysis/__init__.py @@ -1,2 +1,5 @@ from . import models +from . import wizards from . import blacklist +from . import apriori +from . import compare diff --git a/upgrade_analysis/__manifest__.py b/upgrade_analysis/__manifest__.py index 79ebfc939..1a83113a1 100644 --- a/upgrade_analysis/__manifest__.py +++ b/upgrade_analysis/__manifest__.py @@ -2,18 +2,21 @@ # Copyright 2016 Opener B.V. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - "name": "OpenUpgrade Records", + "name": "Upgrade Analysis", + "summary": "performs a difference analysis between modules" + " installed on two different Odoo instances", "version": "14.0.1.0.0", "category": "Migration", - "author": "Therp BV, Opener B.V., Odoo Community Association (OCA)", + "author": "Therp BV, Opener B.V., GRAP, Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-tools", "data": [ - "views/openupgrade_record.xml", - "views/comparison_config.xml", - "views/analysis_wizard.xml", - "views/generate_records_wizard.xml", - "views/install_all_wizard.xml", "security/ir.model.access.csv", + "views/menu.xml", + "views/view_upgrade_comparison_config.xml", + "views/view_upgrade_record.xml", + "wizards/view_upgrade_analysis_wizard.xml", + "wizards/view_upgrade_generate_record_wizard.xml", + "wizards/view_upgrade_install_wizard.xml", ], "installable": True, "external_dependencies": { diff --git a/upgrade_analysis/apriori.py b/upgrade_analysis/apriori.py index 973300fa0..2e5883191 100644 --- a/upgrade_analysis/apriori.py +++ b/upgrade_analysis/apriori.py @@ -2,97 +2,12 @@ to help the matching process """ -renamed_modules = { - # Odoo - 'crm_reveal': 'crm_iap_lead', - 'document': 'attachment_indexation', - 'payment_ogone': 'payment_ingenico', - # OCA/hr - # TODO: Transform possible data - 'hr_skill': 'hr_skills' -} +renamed_modules = {} -merged_modules = { - # Odoo - 'account_cancel': 'account', - 'account_voucher': 'account', - 'crm_phone_validation': 'crm', - 'decimal_precision': 'base', - 'delivery_hs_code': 'delivery', - 'hw_scale': 'hw_drivers', - 'hw_scanner': 'hw_drivers', - 'hw_screen': 'hw_drivers', - 'l10n_fr_certification': 'account', - 'l10n_fr_sale_closing': 'l10n_fr', - 'mrp_bom_cost': 'mrp_account', - 'mrp_byproduct': 'mrp', - 'payment_stripe_sca': 'payment_stripe', - 'stock_zebra': 'stock', - 'survey_crm': 'survey', - 'test_pylint': 'test_lint', - 'web_settings_dashboard': 'base_setup', - 'website_crm_phone_validation': 'website_crm', - 'website_sale_link_tracker': 'website_sale', - 'website_survey': 'survey', - # OCA/account-financial-tools - 'account_move_chatter': 'account', - # OCA/account-reconcile - 'account_set_reconcilable': 'account', - # OCA/l10n-spain - 'l10n_es_aeat_sii': 'l10n_es_aeat_sii_oca', - # OCA/server-backend - 'base_suspend_security': 'base', - # OCA/social - 'mass_mailing_unique': 'mass_mailing', - # OCA/timesheet - 'sale_timesheet_existing_project': 'sale_timesheet', - # OCA/web - 'web_favicon': 'base', - 'web_widget_color': 'web', - 'web_widget_many2many_tags_multi_selection': 'web', - # OCA/website - 'website_canonical_url': 'website', - 'website_logo': 'website', -} +merged_modules = {} -# only used here for openupgrade_records analysis: -renamed_models = { - # Odoo - 'account.register.payments': 'account.payment.register', - 'crm.reveal.industry': 'crm.iap.lead.industry', - 'crm.reveal.role': 'crm.iap.lead.role', - 'crm.reveal.seniority': 'crm.iap.lead.seniority', - 'mail.blacklist.mixin': 'mail.thread.blacklist', - 'mail.mail.statistics': 'mailing.trace', - 'mail.statistics.report': 'mailing.trace.report', - 'mail.mass_mailing': 'mailing.mailing', - 'mail.mass_mailing.contact': 'mailing.contact', - 'mail.mass_mailing.list': 'mailing.list', - 'mail.mass_mailing.list_contact_rel': 'mailing.contact.subscription', - 'mail.mass_mailing.stage': 'utm.stage', - 'mail.mass_mailing.tag': 'utm.tag', - 'mail.mass_mailing.test': 'mailing.mailing.test', - 'mass.mailing.list.merge': 'mailing.list.merge', - 'mass.mailing.schedule.date': 'mailing.mailing.schedule.date', - 'mrp.subproduct': 'mrp.bom.byproduct', - 'sms.send_sms': 'sms.composer', - 'stock.fixed.putaway.strat': 'stock.putaway.rule', - 'survey.mail.compose.message': 'survey.invite', - 'website.redirect': 'website.rewrite', - # OCA/... -} +# only used here for upgrade_analysis +renamed_models = {} -# only used here for openupgrade_records analysis: -merged_models = { - # Odoo - 'account.invoice': 'account.move', - 'account.invoice.line': 'account.move.line', - 'account.invoice.tax': 'account.move.line', - 'account.voucher': 'account.move', - 'account.voucher.line': 'account.move.line', - 'lunch.order.line': 'lunch.order', - 'mail.mass_mailing.campaign': 'utm.campaign', - 'slide.category': 'slide.slide', - 'survey.page': 'survey.question', - # OCA/... -} +# only used here for upgrade_analysis +merged_models = {} diff --git a/upgrade_analysis/compare.py b/upgrade_analysis/compare.py index 52ac7bbe8..6af14263d 100644 --- a/upgrade_analysis/compare.py +++ b/upgrade_analysis/compare.py @@ -1,7 +1,7 @@ -# coding: utf-8 # Copyright 2011-2015 Therp BV # Copyright 2015-2016 Opener B.V. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# flake8: noqa: C901 ##################################################################### # library providing a function to analyse two progressive database @@ -11,12 +11,13 @@ import collections import copy -from odoo.addons.openupgrade_records.lib import apriori +from . import apriori def module_map(module): return apriori.renamed_modules.get( - module, apriori.merged_modules.get(module, module)) + module, apriori.merged_modules.get(module, module) + ) def model_rename_map(model): @@ -24,8 +25,7 @@ def model_rename_map(model): def model_map(model): - return apriori.renamed_models.get( - model, apriori.merged_models.get(model, model)) + return apriori.renamed_models.get(model, apriori.merged_models.get(model, model)) def inv_model_map(model): @@ -34,12 +34,12 @@ def inv_model_map(model): IGNORE_FIELDS = [ - 'create_date', - 'create_uid', - 'id', - 'write_date', - 'write_uid', - ] + "create_date", + "create_uid", + "id", + "write_date", + "write_uid", +] def compare_records(dict_old, dict_new, fields): @@ -51,17 +51,19 @@ def compare_records(dict_old, dict_new, fields): Return True of False. """ for field in fields: - if field == 'module': - if module_map(dict_old['module']) != dict_new['module']: + if field == "module": + if module_map(dict_old["module"]) != dict_new["module"]: return False - elif field == 'model': - if model_rename_map(dict_old['model']) != dict_new['model']: + elif field == "model": + if model_rename_map(dict_old["model"]) != dict_new["model"]: return False - elif field == 'other_prefix': - if dict_old['module'] != dict_old['prefix'] or \ - dict_new['module'] != dict_new['prefix']: + elif field == "other_prefix": + if ( + dict_old["module"] != dict_old["prefix"] + or dict_new["module"] != dict_new["prefix"] + ): return False - if dict_old['model'] == 'ir.ui.view': + if dict_old["model"] == "ir.ui.view": # basically, to avoid the assets_backend case return False elif dict_old[field] != dict_new[field]: @@ -80,71 +82,69 @@ def search(item, item_list, fields): continue return other # search for renamed fields - if 'field' in fields: + if "field" in fields: for other in item_list: - if not item['field'] or item['field'] is not None or \ - item['isproperty']: + if not item["field"] or item["field"] is not None or item["isproperty"]: continue - if compare_records( - dict(item, field=other['field']), other, fields): + if compare_records(dict(item, field=other["field"]), other, fields): return other return None def fieldprint(old, new, field, text, reprs): - fieldrepr = "%s (%s)" % (old['field'], old['type']) - fullrepr = '%-12s / %-24s / %-30s' % ( - old['module'], old['model'], fieldrepr) + fieldrepr = "{} ({})".format(old["field"], old["type"]) + fullrepr = "{:<12} / {:<24} / {:<30}".format(old["module"], old["model"], fieldrepr) if not text: - text = "%s is now '%s' ('%s')" % (field, new[field], old[field]) - if field == 'relation': - text += ' [nothing to do]' - reprs[module_map(old['module'])].append("%s: %s" % (fullrepr, text)) - if field == 'module': + text = "{} is now '{}' ('{}')".format(field, new[field], old[field]) + if field == "relation": + text += " [nothing to do]" + reprs[module_map(old["module"])].append("{}: {}".format(fullrepr, text)) + if field == "module": text = "previously in module %s" % old[field] - fullrepr = '%-12s / %-24s / %-30s' % ( - new['module'], old['model'], fieldrepr) - reprs[module_map(new['module'])].append("%s: %s" % (fullrepr, text)) + fullrepr = "{:<12} / {:<24} / {:<30}".format( + new["module"], old["model"], fieldrepr + ) + reprs[module_map(new["module"])].append("{}: {}".format(fullrepr, text)) def report_generic(new, old, attrs, reprs): for attr in attrs: - if attr == 'required': - if old[attr] != new['required'] and new['required']: + if attr == "required": + if old[attr] != new["required"] and new["required"]: text = "now required" - if new['req_default']: - text += ', req_default: %s' % new['req_default'] - fieldprint(old, new, '', text, reprs) - elif attr == 'stored': + if new["req_default"]: + text += ", req_default: %s" % new["req_default"] + fieldprint(old, new, "", text, reprs) + elif attr == "stored": if old[attr] != new[attr]: - if new['stored']: + if new["stored"]: text = "is now stored" else: text = "not stored anymore" - fieldprint(old, new, '', text, reprs) - elif attr == 'isfunction': + fieldprint(old, new, "", text, reprs) + elif attr == "isfunction": if old[attr] != new[attr]: - if new['isfunction']: + if new["isfunction"]: text = "now a function" else: text = "not a function anymore" - fieldprint(old, new, '', text, reprs) - elif attr == 'isproperty': + fieldprint(old, new, "", text, reprs) + elif attr == "isproperty": if old[attr] != new[attr]: if new[attr]: text = "now a property" else: text = "not a property anymore" - fieldprint(old, new, '', text, reprs) - elif attr == 'isrelated': + fieldprint(old, new, "", text, reprs) + elif attr == "isrelated": if old[attr] != new[attr]: if new[attr]: text = "now related" else: text = "not related anymore" - fieldprint(old, new, '', text, reprs) + fieldprint(old, new, "", text, reprs) elif old[attr] != new[attr]: - fieldprint(old, new, attr, '', reprs) + fieldprint(old, new, attr, "", reprs) def compare_sets(old_records, new_records): @@ -160,7 +160,7 @@ def compare_sets(old_records, new_records): def clean_records(records): result = [] for record in records: - if record['field'] not in IGNORE_FIELDS: + if record["field"] not in IGNORE_FIELDS: result.append(record) return result @@ -168,8 +168,8 @@ def compare_sets(old_records, new_records): new_records = clean_records(new_records) origlen = len(old_records) - new_models = set([column['model'] for column in new_records]) - old_models = set([column['model'] for column in old_records]) + new_models = {column["model"] for column in new_records} + old_models = {column["model"] for column in old_records} matched_direct = 0 matched_other_module = 0 @@ -184,7 +184,7 @@ def compare_sets(old_records, new_records): non_obsolete_old_records = [] for column in copy.copy(old_records): - if column['model'] in obsolete_models: + if column["model"] in obsolete_models: in_obsolete_models += 1 else: non_obsolete_old_records.append(column) @@ -205,152 +205,193 @@ def compare_sets(old_records, new_records): return count matched_direct = match( - ['module', 'mode', 'model', 'field'], - ['relation', 'type', 'selection_keys', 'inherits', 'stored', - 'isfunction', 'isrelated', 'required', 'table']) + ["module", "mode", "model", "field"], + [ + "relation", + "type", + "selection_keys", + "inherits", + "stored", + "isfunction", + "isrelated", + "required", + "table", + ], + ) # other module, same type and operation matched_other_module = match( - ['mode', 'model', 'field', 'type'], - ['module', 'relation', 'selection_keys', 'inherits', 'stored', - 'isfunction', 'isrelated', 'required', 'table']) + ["mode", "model", "field", "type"], + [ + "module", + "relation", + "selection_keys", + "inherits", + "stored", + "isfunction", + "isrelated", + "required", + "table", + ], + ) # other module, same operation, other type matched_other_type = match( - ['mode', 'model', 'field'], - ['relation', 'type', 'selection_keys', 'inherits', 'stored', - 'isfunction', 'isrelated', 'required', 'table']) + ["mode", "model", "field"], + [ + "relation", + "type", + "selection_keys", + "inherits", + "stored", + "isfunction", + "isrelated", + "required", + "table", + ], + ) printkeys = [ - 'relation', 'required', 'selection_keys', - 'req_default', 'inherits', 'mode', 'attachment', - ] + "relation", + "required", + "selection_keys", + "req_default", + "inherits", + "mode", + "attachment", + ] for column in old_records: # we do not care about removed non stored function fields - if not column['stored'] and ( - column['isfunction'] or column['isrelated']): + if not column["stored"] and (column["isfunction"] or column["isrelated"]): continue - if column['mode'] == 'create': - column['mode'] = '' + if column["mode"] == "create": + column["mode"] = "" extra_message = ", ".join( - [k + ': ' + str(column[k]) if k != str(column[k]) else k - for k in printkeys if column[k]] + [ + k + ": " + str(column[k]) if k != str(column[k]) else k + for k in printkeys + if column[k] + ] ) if extra_message: extra_message = " " + extra_message - fieldprint( - column, '', '', "DEL" + extra_message, reprs) + fieldprint(column, "", "", "DEL" + extra_message, reprs) - printkeys.extend([ - 'hasdefault', - ]) + printkeys.extend( + [ + "hasdefault", + ] + ) for column in new_records: # we do not care about newly added non stored function fields - if not column['stored'] and ( - column['isfunction'] or column['isrelated']): + if not column["stored"] and (column["isfunction"] or column["isrelated"]): continue - if column['mode'] == 'create': - column['mode'] = '' + if column["mode"] == "create": + column["mode"] = "" printkeys_plus = printkeys.copy() - if column['isfunction'] or column['isrelated']: - printkeys_plus.extend(['isfunction', 'isrelated', 'stored']) + if column["isfunction"] or column["isrelated"]: + printkeys_plus.extend(["isfunction", "isrelated", "stored"]) extra_message = ", ".join( - [k + ': ' + str(column[k]) if k != str(column[k]) else k - for k in printkeys_plus if column[k]] + [ + k + ": " + str(column[k]) if k != str(column[k]) else k + for k in printkeys_plus + if column[k] + ] ) if extra_message: extra_message = " " + extra_message - fieldprint( - column, '', '', "NEW" + extra_message, reprs) + fieldprint(column, "", "", "NEW" + extra_message, 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, - "# In obsolete models: %d" % in_obsolete_models, - "# Not matched: %d" % len(old_records), - "# New columns: %d" % len(new_records) - ]: - reprs['general'].append(line) + "# %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, + "# 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 = collections.defaultdict(list) - def match(match_fields, match_type='direct'): + def match(match_fields, match_type="direct"): matched_records = [] for column in copy.copy(old_records): found = search(column, new_records, match_fields) if found: old_records.remove(column) new_records.remove(found) - if match_type != 'direct': - column['old'] = True - found['new'] = True - column[match_type] = found['module'] - found[match_type] = column['module'] - found['domain'] = column['domain'] != found['domain'] and \ - column['domain'] != '[]' and found['domain'] is False - column['domain'] = False - column['noupdate_switched'] = False - found['noupdate_switched'] = \ - column['noupdate'] != found['noupdate'] - if match_type != 'direct': + if match_type != "direct": + column["old"] = True + found["new"] = True + column[match_type] = found["module"] + found[match_type] = column["module"] + found["domain"] = ( + column["domain"] != found["domain"] + and column["domain"] != "[]" + and found["domain"] is False + ) + column["domain"] = False + column["noupdate_switched"] = False + found["noupdate_switched"] = column["noupdate"] != found["noupdate"] + if match_type != "direct": matched_records.append(column) matched_records.append(found) - elif (match_type == 'direct' and found['domain']) or \ - found['noupdate_switched']: + elif (match_type == "direct" and found["domain"]) or found[ + "noupdate_switched" + ]: matched_records.append(found) return matched_records # direct match - modified_records = match(['module', 'model', 'name']) + modified_records = match(["module", "model", "name"]) # other module, same full xmlid - moved_records = match(['model', 'name'], 'moved') + moved_records = match(["model", "name"], "moved") # other module, same suffix, other prefix - renamed_records = match(['model', 'suffix', 'other_prefix'], 'renamed') + renamed_records = match(["model", "suffix", "other_prefix"], "renamed") for record in old_records: - record['old'] = True - record['domain'] = False - record['noupdate_switched'] = False + record["old"] = True + record["domain"] = False + record["noupdate_switched"] = False for record in new_records: - record['new'] = True - record['domain'] = False - record['noupdate_switched'] = False + record["new"] = True + record["domain"] = False + record["noupdate_switched"] = False sorted_records = sorted( - old_records + new_records + moved_records + renamed_records + - modified_records, - key=lambda k: (k['model'], 'old' in k, k['name']) + old_records + new_records + moved_records + renamed_records + modified_records, + key=lambda k: (k["model"], "old" in k, k["name"]), ) for entry in sorted_records: - content = '' - if 'old' in entry: - content = 'DEL %(model)s: %(name)s' % entry - if 'moved' in entry: - content += ' [potentially moved to %(moved)s module]' % entry - elif 'renamed' in entry: - content += ' [renamed to %(renamed)s module]' % entry - elif 'new' in entry: - content = 'NEW %(model)s: %(name)s' % entry - if 'moved' in entry: - content += ' [potentially moved from %(moved)s module]' % entry - elif 'renamed' in entry: - content += ' [renamed from %(renamed)s module]' % entry - if 'old' not in entry and 'new' not in entry: - content = '%(model)s: %(name)s' % entry - if entry['domain']: - content += ' (deleted domain)' - if entry['noupdate']: - content += ' (noupdate)' - if entry['noupdate_switched']: - content += ' (noupdate switched)' - reprs[module_map(entry['module'])].append(content) + content = "" + if "old" in entry: + content = "DEL %(model)s: %(name)s" % entry + if "moved" in entry: + content += " [potentially moved to %(moved)s module]" % entry + elif "renamed" in entry: + content += " [renamed to %(renamed)s module]" % entry + elif "new" in entry: + content = "NEW %(model)s: %(name)s" % entry + if "moved" in entry: + content += " [potentially moved from %(moved)s module]" % entry + elif "renamed" in entry: + content += " [renamed from %(renamed)s module]" % entry + if "old" not in entry and "new" not in entry: + content = "%(model)s: %(name)s" % entry + if entry["domain"]: + content += " (deleted domain)" + if entry["noupdate"]: + content += " (noupdate)" + if entry["noupdate_switched"]: + content += " (noupdate switched)" + reprs[module_map(entry["module"])].append(content) return reprs @@ -360,79 +401,84 @@ def compare_model_sets(old_records, new_records): """ reprs = collections.defaultdict(list) - new_models = {column['model']: column['module'] for column in new_records} - old_models = {column['model']: column['module'] for column in old_records} + new_models = {column["model"]: column["module"] for column in new_records} + old_models = {column["model"]: column["module"] for column in old_records} obsolete_models = [] for column in copy.copy(old_records): - model = column['model'] + model = column["model"] if model in old_models: if model not in new_models: if model_map(model) not in new_models: obsolete_models.append(model) - text = 'obsolete model %s' % model - if column['model_type']: - text += " [%s]" % column['model_type'] - reprs[module_map(column['module'])].append(text) - reprs['general'].append('obsolete model %s [module %s]' % ( - model, module_map(column['module']))) + text = "obsolete model %s" % model + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[module_map(column["module"])].append(text) + reprs["general"].append( + "obsolete model %s [module %s]" + % (model, module_map(column["module"])) + ) else: - moved_module = '' - if module_map(column['module']) != new_models[model_map( - model)]: - moved_module = ' in module %s' % new_models[model_map( - model)] - text = 'obsolete model %s (renamed to %s%s)' % ( - model, model_map(model), moved_module) - if column['model_type']: - text += " [%s]" % column['model_type'] - reprs[module_map(column['module'])].append(text) - reprs['general'].append( - 'obsolete model %s (renamed to %s) [module %s]' % ( - model, model_map(model), - module_map(column['module']))) + moved_module = "" + if module_map(column["module"]) != new_models[model_map(model)]: + moved_module = " in module %s" % new_models[model_map(model)] + text = "obsolete model {} (renamed to {}{})".format( + model, + model_map(model), + moved_module, + ) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[module_map(column["module"])].append(text) + reprs["general"].append( + "obsolete model %s (renamed to %s) [module %s]" + % (model, model_map(model), module_map(column["module"])) + ) else: - if module_map(column['module']) != new_models[model]: - text = 'model %s (moved to %s)' % ( - model, new_models[model]) - if column['model_type']: - text += " [%s]" % column['model_type'] - reprs[module_map(column['module'])].append(text) - text = 'model %s (moved from %s)' % ( - model, old_models[model]) - if column['model_type']: - text += " [%s]" % column['model_type'] + if module_map(column["module"]) != new_models[model]: + text = "model {} (moved to {})".format(model, new_models[model]) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[module_map(column["module"])].append(text) + text = "model {} (moved from {})".format(model, old_models[model]) + if column["model_type"]: + text += " [%s]" % column["model_type"] for column in copy.copy(new_records): - model = column['model'] + model = column["model"] if model in new_models: if model not in old_models: if inv_model_map(model) not in old_models: - text = 'new model %s' % model - if column['model_type']: - text += " [%s]" % column['model_type'] - reprs[column['module']].append(text) - reprs['general'].append('new model %s [module %s]' % ( - model, column['module'])) + text = "new model %s" % model + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[column["module"]].append(text) + reprs["general"].append( + "new model {} [module {}]".format(model, column["module"]) + ) else: - moved_module = '' - if column['module'] != module_map(old_models[inv_model_map( - model)]): - moved_module = ' in module %s' % old_models[ - inv_model_map(model)] - text = 'new model %s (renamed from %s%s)' % ( - model, inv_model_map(model), moved_module) - if column['model_type']: - text += " [%s]" % column['model_type'] - reprs[column['module']].append(text) - reprs['general'].append( - 'new model %s (renamed from %s) [module %s]' % ( - model, inv_model_map(model), column['module'])) + moved_module = "" + if column["module"] != module_map(old_models[inv_model_map(model)]): + moved_module = ( + " in module %s" % old_models[inv_model_map(model)] + ) + text = "new model {} (renamed from {}{})".format( + model, + inv_model_map(model), + moved_module, + ) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[column["module"]].append(text) + reprs["general"].append( + "new model %s (renamed from %s) [module %s]" + % (model, inv_model_map(model), column["module"]) + ) else: - if column['module'] != module_map(old_models[model]): - text = 'model %s (moved from %s)' % ( - model, old_models[model]) - if column['model_type']: - text += " [%s]" % column['model_type'] - reprs[column['module']].append(text) + if column["module"] != module_map(old_models[model]): + text = "model {} (moved from {})".format(model, old_models[model]) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[column["module"]].append(text) return reprs diff --git a/upgrade_analysis/models/__init__.py b/upgrade_analysis/models/__init__.py index 6e4458424..748d5aee0 100644 --- a/upgrade_analysis/models/__init__.py +++ b/upgrade_analysis/models/__init__.py @@ -1,5 +1,3 @@ -from . import openupgrade_record -from . import comparison_config -from . import analysis_wizard -from . import generate_records_wizard -from . import install_all_wizard +from . import upgrade_comparison_config +from . import upgrade_attribute +from . import upgrade_record diff --git a/upgrade_analysis/models/upgrade_attribute.py b/upgrade_analysis/models/upgrade_attribute.py new file mode 100644 index 000000000..ce6ef46e3 --- /dev/null +++ b/upgrade_analysis/models/upgrade_attribute.py @@ -0,0 +1,20 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class UpgradeAttribute(models.Model): + _name = "upgrade.attribute" + _description = "Upgrade Attribute" + + name = fields.Char(readonly=True) + + value = fields.Char(readonly=True) + + record_id = fields.Many2one( + comodel_name="upgrade.record", + ondelete="CASCADE", + readonly=True, + ) diff --git a/upgrade_analysis/models/comparison_config.py b/upgrade_analysis/models/upgrade_comparison_config.py similarity index 80% rename from upgrade_analysis/models/comparison_config.py rename to upgrade_analysis/models/upgrade_comparison_config.py index 0e5274bdf..17d60e61f 100644 --- a/upgrade_analysis/models/comparison_config.py +++ b/upgrade_analysis/models/upgrade_comparison_config.py @@ -8,25 +8,25 @@ from odoo import fields, models from odoo.exceptions import UserError from odoo.tools.translate import _ -from ..lib import apriori +from .. import apriori -class OpenupgradeComparisonConfig(models.Model): - _name = "openupgrade.comparison.config" - _description = "OpenUpgrade Comparison Configuration" +class UpgradeComparisonConfig(models.Model): + _name = "upgrade.comparison.config" + _description = "Upgrade Comparison Configuration" name = fields.Char() - server = fields.Char(required=True) + + server = fields.Char(required=True, default="localhost") + port = fields.Integer(required=True, default=8069) - protocol = fields.Selection( - [("http://", "XML-RPC")], - # ('https://', 'XML-RPC Secure')], not supported by libopenerp - required=True, - default="http://", - ) + database = fields.Char(required=True) - username = fields.Char(required=True) - password = fields.Char(required=True) + + username = fields.Char(required=True, default="admin") + + password = fields.Char(required=True, default="admin") + last_log = fields.Text() def get_connection(self): @@ -51,8 +51,8 @@ class OpenupgradeComparisonConfig(models.Model): def analyze(self): """ Run the analysis wizard """ self.ensure_one() - wizard = self.env["openupgrade.analysis.wizard"].create( - {"server_config": self.id} + wizard = self.env["upgrade.analysis.wizard"].create( + {"server_config_id": self.id} ) return { "name": wizard._description, diff --git a/upgrade_analysis/models/openupgrade_record.py b/upgrade_analysis/models/upgrade_record.py similarity index 85% rename from upgrade_analysis/models/openupgrade_record.py rename to upgrade_analysis/models/upgrade_record.py index 80b5a8a3a..89426c308 100644 --- a/upgrade_analysis/models/openupgrade_record.py +++ b/upgrade_analysis/models/upgrade_record.py @@ -5,27 +5,18 @@ from odoo import api, fields, models -class Attribute(models.Model): - _name = "openupgrade.attribute" - _description = "OpenUpgrade Attribute" +class UpgradeRecord(models.Model): + _name = "upgrade.record" + _description = "Upgrade Record" name = fields.Char(readonly=True) - value = fields.Char(readonly=True) - record_id = fields.Many2one( - "openupgrade.record", - ondelete="CASCADE", - readonly=True, - ) - -class Record(models.Model): - _name = "openupgrade.record" - _description = "OpenUpgrade Record" - - name = fields.Char(readonly=True) module = fields.Char(readonly=True) + model = fields.Char(readonly=True) + field = fields.Char(readonly=True) + mode = fields.Selection( [("create", "Create"), ("modify", "Modify")], help="Set to Create if a field is newly created " @@ -33,16 +24,26 @@ class Record(models.Model): "existing field, set to Modify.", readonly=True, ) - type = fields.Selection( # Uh oh, reserved keyword + + type = fields.Selection( [("field", "Field"), ("xmlid", "XML ID"), ("model", "Model")], readonly=True, ) - attribute_ids = fields.One2many("openupgrade.attribute", "record_id", readonly=True) + + attribute_ids = fields.One2many( + comodel_name="upgrade.attribute", inverse_name="record_id", readonly=True + ) + noupdate = fields.Boolean(readonly=True) + domain = fields.Char(readonly=True) + prefix = fields.Char(compute="_compute_prefix_and_suffix") + suffix = fields.Char(compute="_compute_prefix_and_suffix") + model_original_module = fields.Char(compute="_compute_model_original_module") + model_type = fields.Char(compute="_compute_model_type") @api.depends("name") diff --git a/upgrade_analysis/readme/CONTRIBUTORS.rst b/upgrade_analysis/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..5de0e93e5 --- /dev/null +++ b/upgrade_analysis/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ +* Stefan Rijnhart +* Holger Brunn +* Pedro M. Baeza +* Ferdinand Gassauer +* Florent Xicluna +* Miquel Raïch +* Sylvain LE GAL diff --git a/upgrade_analysis/readme/DESCRIPTION.rst b/upgrade_analysis/readme/DESCRIPTION.rst new file mode 100644 index 000000000..ff1a57292 --- /dev/null +++ b/upgrade_analysis/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module provides the tool to generate the database analysis files that indicate how the Odoo data model and module data have changed between two versions of Odoo. Database analysis files for the core modules are included in the OpenUpgrade distribution so as a migration script developer you will not usually need to use this tool yourself. If you do need to run your analysis of a custom set of modules, please refer to the documentation here: https://doc.therp.nl/openupgrade/analysis.html diff --git a/upgrade_analysis/readme/ROADMAP.rst b/upgrade_analysis/readme/ROADMAP.rst new file mode 100644 index 000000000..734c22444 --- /dev/null +++ b/upgrade_analysis/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* scripts/compare_noupdate_xml_records.py should be integrated in the analysis process (#590) +* Log removed modules in the module that owned them (#468) +* Detect renamed many2many tables (#213) diff --git a/upgrade_analysis/readme/USAGE.rst b/upgrade_analysis/readme/USAGE.rst new file mode 100644 index 000000000..e69de29bb diff --git a/upgrade_analysis/security/ir.model.access.csv b/upgrade_analysis/security/ir.model.access.csv index 2ab5e67c1..5d835a8a9 100644 --- a/upgrade_analysis/security/ir.model.access.csv +++ b/upgrade_analysis/security/ir.model.access.csv @@ -1,4 +1,7 @@ -"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 -"access_openupgrade_comparison_config","openupgrade.comparison.config","model_openupgrade_comparison_config",base.group_system,1,1,1,1 +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_upgrade_record,upgrade.record all,model_upgrade_record,,1,0,0,0 +access_upgrade_attribute,upgrade.attribute all,model_upgrade_attribute,,1,0,0,0 +access_upgrade_comparison_config,upgrade.comparison.config,model_upgrade_comparison_config,base.group_system,1,1,1,1 +access_upgrade_analysis_wizard,access_upgrade_analysis_wizard,model_upgrade_analysis_wizard,base.group_system,1,1,1,1 +access_upgrade_generate_record_wizard,access_upgrade_generate_record_wizard,model_upgrade_generate_record_wizard,base.group_system,1,1,1,1 +access_upgrade_install_wizard,access_upgrade_install_wizard,model_upgrade_install_wizard,base.group_system,1,1,1,1 diff --git a/upgrade_analysis/views/menu.xml b/upgrade_analysis/views/menu.xml new file mode 100644 index 000000000..12d44779d --- /dev/null +++ b/upgrade_analysis/views/menu.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/upgrade_analysis/views/comparison_config.xml b/upgrade_analysis/views/view_upgrade_comparison_config.xml similarity index 63% rename from upgrade_analysis/views/comparison_config.xml rename to upgrade_analysis/views/view_upgrade_comparison_config.xml index db0ae7770..9d98c47a2 100644 --- a/upgrade_analysis/views/comparison_config.xml +++ b/upgrade_analysis/views/view_upgrade_comparison_config.xml @@ -1,13 +1,11 @@ - - view.openupgrade.comparison_config.tree - openupgrade.comparison.config + + upgrade.comparison.config - + - @@ -15,14 +13,12 @@ - - view.openupgrade.comparison_config.form - openupgrade.comparison.config + + upgrade.comparison.config -
+ - @@ -57,21 +53,18 @@
-) - - OpenUpgrade Comparison Configs + + + upgrade Comparison Configs ir.actions.act_window - openupgrade.comparison.config + upgrade.comparison.config
diff --git a/upgrade_analysis/views/openupgrade_record.xml b/upgrade_analysis/views/view_upgrade_record.xml similarity index 65% rename from upgrade_analysis/views/openupgrade_record.xml rename to upgrade_analysis/views/view_upgrade_record.xml index a8d683d95..8fbd3c5c4 100644 --- a/upgrade_analysis/views/openupgrade_record.xml +++ b/upgrade_analysis/views/view_upgrade_record.xml @@ -1,17 +1,8 @@ - - - - - Search view for openupgrade records - openupgrade.record + + upgrade.record @@ -39,11 +30,10 @@ - - view.openupgrade.record.tree - openupgrade.record + + upgrade.record - + @@ -54,11 +44,10 @@ - - view.openupgrade.record.form - openupgrade.record + + upgrade.record -
+ @@ -80,17 +69,17 @@ - - OpenUpgrade Records + + upgrade Records ir.actions.act_window - openupgrade.record + upgrade.record diff --git a/upgrade_analysis/wizards/__init__.py b/upgrade_analysis/wizards/__init__.py new file mode 100644 index 000000000..dffe83f41 --- /dev/null +++ b/upgrade_analysis/wizards/__init__.py @@ -0,0 +1,3 @@ +from . import upgrade_analysis_wizard +from . import upgrade_generate_record_wizard +from . import upgrade_install_wizard diff --git a/upgrade_analysis/models/analysis_wizard.py b/upgrade_analysis/wizards/upgrade_analysis_wizard.py similarity index 90% rename from upgrade_analysis/models/analysis_wizard.py rename to upgrade_analysis/wizards/upgrade_analysis_wizard.py index 61a5e2082..82cbaab77 100644 --- a/upgrade_analysis/models/analysis_wizard.py +++ b/upgrade_analysis/wizards/upgrade_analysis_wizard.py @@ -8,15 +8,15 @@ import os from odoo import fields, models from odoo.modules import get_module_path -from ..lib import compare +from .. import compare -class AnalysisWizard(models.TransientModel): - _name = "openupgrade.analysis.wizard" - _description = "OpenUpgrade Analysis Wizard" +class UpgradeAnalysisWizard(models.TransientModel): + _name = "upgrade.analysis.wizard" + _description = "upgrade Analysis Wizard" - server_config = fields.Many2one( - "openupgrade.comparison.config", "Configuration", required=True + server_config_id = fields.Many2one( + "upgrade.comparison.config", "Configuration", required=True ) state = fields.Selection( [("init", "Init"), ("ready", "Ready")], readonly=True, default="init" @@ -33,7 +33,7 @@ class AnalysisWizard(models.TransientModel): change set """ - def write_file(module, version, content, filename="openupgrade_analysis.txt"): + def write_file(module, version, content, filename="upgrade_analysis.txt"): module_path = get_module_path(module) if not module_path: return "ERROR: could not find module path:\n" @@ -53,9 +53,9 @@ class AnalysisWizard(models.TransientModel): return None self.ensure_one() - connection = self.server_config.get_connection() - remote_record_obj = connection.env["openupgrade.record"] - local_record_obj = self.env["openupgrade.record"] + connection = self.server_config_id.get_connection() + remote_record_obj = connection.env["upgrade.record"] + local_record_obj = self.env["upgrade.record"] # Retrieve field representations and compare remote_records = remote_record_obj.field_dump() @@ -168,9 +168,9 @@ class AnalysisWizard(models.TransientModel): "base", modules["base"].installed_version, general, - "openupgrade_general_log.txt", + "upgrade_general_log.txt", ) - self.server_config.write({"last_log": general}) + self.server_config_id.write({"last_log": general}) self.write({"state": "ready", "log": general}) return { diff --git a/upgrade_analysis/models/generate_records_wizard.py b/upgrade_analysis/wizards/upgrade_generate_record_wizard.py similarity index 88% rename from upgrade_analysis/models/generate_records_wizard.py rename to upgrade_analysis/wizards/upgrade_generate_record_wizard.py index b09f6839e..fc7f6c02f 100644 --- a/upgrade_analysis/models/generate_records_wizard.py +++ b/upgrade_analysis/wizards/upgrade_generate_record_wizard.py @@ -10,8 +10,8 @@ from odoo.modules.registry import Registry class GenerateWizard(models.TransientModel): - _name = "openupgrade.generate.records.wizard" - _description = "OpenUpgrade Generate Records Wizard" + _name = "upgrade.generate.record.wizard" + _description = "Upgrade Generate Record Wizard" _rec_name = "state" state = fields.Selection([("init", "init"), ("ready", "ready")], default="init") @@ -35,9 +35,9 @@ class GenerateWizard(models.TransientModel): TODO: update module list and versions, then update all modules?""" # Truncate the records table if openupgrade_tools.table_exists( - self.env.cr, "openupgrade_attribute" - ) and openupgrade_tools.table_exists(self.env.cr, "openupgrade_record"): - self.env.cr.execute("TRUNCATE openupgrade_attribute, openupgrade_record;") + self.env.cr, "upgrade_attribute" + ) and openupgrade_tools.table_exists(self.env.cr, "upgrade_record"): + self.env.cr.execute("TRUNCATE upgrade_attribute, upgrade_record;") # Run any quirks self.quirk_standard_calendar_attendances() @@ -67,7 +67,7 @@ class GenerateWizard(models.TransientModel): # Set domain property self.env.cr.execute( - """ UPDATE openupgrade_record our + """ UPDATE upgrade_record our SET domain = iaw.domain FROM ir_model_data imd JOIN ir_act_window iaw ON imd.res_id = iaw.id @@ -79,13 +79,13 @@ class GenerateWizard(models.TransientModel): ) self.env.cache.invalidate( [ - (self.env["openupgrade.record"]._fields["domain"], None), + (self.env["upgrade.record"]._fields["domain"], None), ] ) # Set noupdate property from ir_model_data self.env.cr.execute( - """ UPDATE openupgrade_record our + """ UPDATE upgrade_record our SET noupdate = imd.noupdate FROM ir_model_data imd WHERE our.type = 'xmlid' @@ -101,7 +101,7 @@ class GenerateWizard(models.TransientModel): # Log model records self.env.cr.execute( - """INSERT INTO openupgrade_record + """INSERT INTO upgrade_record (module, name, model, type) SELECT imd2.module, imd2.module || '.' || imd.name AS name, im.model, 'model' AS type diff --git a/upgrade_analysis/models/install_all_wizard.py b/upgrade_analysis/wizards/upgrade_install_wizard.py similarity index 89% rename from upgrade_analysis/models/install_all_wizard.py rename to upgrade_analysis/wizards/upgrade_install_wizard.py index c1085418e..4ea1d36b9 100644 --- a/upgrade_analysis/models/install_all_wizard.py +++ b/upgrade_analysis/wizards/upgrade_install_wizard.py @@ -9,9 +9,9 @@ from odoo.osv.expression import AND from ..blacklist import BLACKLIST_MODULES -class InstallAll(models.TransientModel): - _name = "openupgrade.install.all.wizard" - _description = "OpenUpgrade Install All Wizard" +class UpgradeInstallWizard(models.TransientModel): + _name = "upgrade.install.wizard" + _description = "Upgrade Install Wizard" state = fields.Selection( [("init", "init"), ("ready", "ready")], readonly=True, default="init" @@ -22,7 +22,7 @@ class InstallAll(models.TransientModel): def default_get(self, fields): """Update module list and retrieve the number of installable modules""" - res = super(InstallAll, self).default_get(fields) + res = super().default_get(fields) update, add = self.env["ir.module.module"].update_list() modules = self.env["ir.module.module"].search( [("state", "not in", ["uninstallable", "unknown"])] diff --git a/upgrade_analysis/views/analysis_wizard.xml b/upgrade_analysis/wizards/view_upgrade_analysis_wizard.xml similarity index 74% rename from upgrade_analysis/views/analysis_wizard.xml rename to upgrade_analysis/wizards/view_upgrade_analysis_wizard.xml index efaa5294f..d988c503d 100644 --- a/upgrade_analysis/views/analysis_wizard.xml +++ b/upgrade_analysis/wizards/view_upgrade_analysis_wizard.xml @@ -1,13 +1,12 @@ - - view.openupgrade.analysis_wizard.form - openupgrade.analysis.wizard + + upgrade.analysis.wizard - + - + - - view.openupgrade.generate_records_wizard.form - openupgrade.generate.records.wizard + + upgrade.generate.record.wizard - +

This will reinitialize all the modules installed on this database. Do not continue if you use this database in production.

-

Modules initialized and records created

+

Modules initialized and record created