diff --git a/requirements.txt b/requirements.txt index 6ac769a65ef..09418114542 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,7 @@ # generated from manifests external_dependencies astor +dataclasses +mako +odoorpc +openupgradelib sentry_sdk<=1.9.0 diff --git a/upgrade_analysis/README.rst b/upgrade_analysis/README.rst new file mode 100644 index 00000000000..6beb3bf68c5 --- /dev/null +++ b/upgrade_analysis/README.rst @@ -0,0 +1,120 @@ +================ +Upgrade Analysis +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:242860bef7e19733f963e0ac5d76d8cf6fab512d5868fdea809ea2bc848d5718 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/17.0/upgrade_analysis + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-upgrade_analysis + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +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 + +This module is just a tool, a continuation of the old +openupgrade_records in OpenUpgrade in previous versions. It's not +recommended to have this module in a production database. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +`Usage instructions `__ + +Known issues / Roadmap +====================== + +- Log removed modules in the module that owned them (#468) +- Detect renamed many2many tables (#213) +- Make sure that the ``migration_analysis.txt`` file is always + generated in all cases. (See: + https://github.com/OCA/OpenUpgrade/pull/3209#issuecomment-1157449981) + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Therp BV +* Opener B.V. +* GRAP + +Contributors +------------ + +- Stefan Rijnhart +- Holger Brunn +- Pedro M. Baeza +- Ferdinand Gassauer +- Florent Xicluna +- Miquel Raïch +- Sylvain LE GAL + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +.. |maintainer-StefanRijnhart| image:: https://github.com/StefanRijnhart.png?size=40px + :target: https://github.com/StefanRijnhart + :alt: StefanRijnhart +.. |maintainer-legalsylvain| image:: https://github.com/legalsylvain.png?size=40px + :target: https://github.com/legalsylvain + :alt: legalsylvain + +Current `maintainers `__: + +|maintainer-StefanRijnhart| |maintainer-legalsylvain| + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/upgrade_analysis/__init__.py b/upgrade_analysis/__init__.py new file mode 100644 index 00000000000..172ae9a2543 --- /dev/null +++ b/upgrade_analysis/__init__.py @@ -0,0 +1,6 @@ +from . import odoo_patch +from . import models +from . import wizards +from . import blacklist +from . import compare +from . import upgrade_log diff --git a/upgrade_analysis/__manifest__.py b/upgrade_analysis/__manifest__.py new file mode 100644 index 00000000000..1bcdeb13caf --- /dev/null +++ b/upgrade_analysis/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Upgrade Analysis", + "summary": "Performs a difference analysis between modules" + " installed on two different Odoo instances", + "version": "17.0.1.0.0", + "category": "Migration", + "author": "Therp BV, Opener B.V., GRAP, Odoo Community Association (OCA)", + "maintainers": ["StefanRijnhart", "legalsylvain"], + "website": "https://github.com/OCA/server-tools", + "data": [ + "security/ir.model.access.csv", + "views/menu.xml", + "views/view_upgrade_comparison_config.xml", + "views/view_upgrade_analysis.xml", + "views/view_upgrade_record.xml", + "wizards/view_upgrade_generate_record_wizard.xml", + "wizards/view_upgrade_install_wizard.xml", + ], + "installable": True, + "depends": ["base"], + "external_dependencies": { + "python": ["mako", "dataclasses", "odoorpc", "openupgradelib"], + }, + "license": "AGPL-3", +} diff --git a/upgrade_analysis/blacklist.py b/upgrade_analysis/blacklist.py new file mode 100644 index 00000000000..b280aac2405 --- /dev/null +++ b/upgrade_analysis/blacklist.py @@ -0,0 +1,15 @@ +BLACKLIST_MODULES = [ + "payment_alipay", + "payment_ogone", + "payment_payulatam", + "payment_payumoney", +] + +# the hw_* modules are not affected by a migration as they don't +# contain any ORM functionality, but they do start up threads that +# delay the process and spit out annoying log messages continuously. + +# We also don't want to analyze tests modules +BLACKLIST_MODULES_STARTS_WITH = ["hw_", "test_"] + +BLACKLIST_MODULES_ENDS_WITH = ["_test"] diff --git a/upgrade_analysis/compare.py b/upgrade_analysis/compare.py new file mode 100644 index 00000000000..74c95d1adfd --- /dev/null +++ b/upgrade_analysis/compare.py @@ -0,0 +1,545 @@ +# 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 +# layouts from the OpenUpgrade server. +##################################################################### + +import collections +import copy + +try: + from odoo.addons.openupgrade_scripts import apriori +except ImportError: + from dataclasses import dataclass + from dataclasses import field as dc_field + + @dataclass + class NullApriori: + renamed_modules: dict = dc_field(default_factory=dict) + merged_modules: dict = dc_field(default_factory=dict) + renamed_models: dict = dc_field(default_factory=dict) + merged_models: dict = dc_field(default_factory=dict) + + apriori = NullApriori() + + +def module_map(module): + return apriori.renamed_modules.get( + module, apriori.merged_modules.get(module, module) + ) + + +def model_rename_map(model): + return apriori.renamed_models.get(model, model) + + +def model_map(model): + return apriori.renamed_models.get(model, apriori.merged_models.get(model, model)) + + +def inv_model_map(model): + inv_model_map_dict = {v: k for k, v in apriori.renamed_models.items()} + return inv_model_map_dict.get(model, model) + + +IGNORE_FIELDS = [ + "create_date", + "create_uid", + "id", + "write_date", + "write_uid", +] + + +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["module"]) != dict_new["module"]: + return False + 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"] + ): + return False + if dict_old["model"] == "ir.ui.view": + # basically, to avoid the assets_backend case + return False + elif dict_old[field] != dict_new[field]: + return False + return True + + +def search(item, item_list, fields, get_all=None): + """ + 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. + """ + all_found = [] + for other in item_list: + if not compare_records(item, other, fields): + continue + if not get_all: + return other + if other["module"] != other["prefix"]: + all_found.append(other) + if get_all: + return all_found + # search for renamed fields + if "field" in fields: + for other in item_list: + if not item["field"] or item["field"] is not None or item["isproperty"]: + continue + if compare_records(dict(item, field=other["field"]), other, fields): + return other + return None + + +def fieldprint(old, new, field, text, reprs): + fieldrepr = "{}".format(old["field"]) + if old["field"] not in ("_inherits", "_order"): + fieldrepr += " ({})".format(old["type"]) + fullrepr = "{:<12} / {:<24} / {:<30}".format(old["module"], old["model"], fieldrepr) + if not text: + text = f"{field} is now '{new[field]}' ('{old[field]}')" + if field in ("column1", "column2"): + text += f" [{old['table']}]" + if field == "relation": + text += " [nothing to do]" + reprs[module_map(old["module"])].append(f"{fullrepr}: {text}") + if field == "module": + text = f"previously in module {old[field]}" + fullrepr = "{:<12} / {:<24} / {:<30}".format( + new["module"], old["model"], fieldrepr + ) + reprs[module_map(new["module"])].append(f"{fullrepr}: {text}") + + +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" + fieldprint(old, new, "", text, reprs) + elif attr == "stored": + if old[attr] != new[attr]: + if new["stored"]: + text = "is now stored" + else: + text = "not stored anymore" + fieldprint(old, new, "", text, reprs) + elif attr == "isfunction": + if old[attr] != new[attr]: + if new["isfunction"]: + text = "now a function" + else: + text = "not a function anymore" + 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": + if old[attr] != new[attr]: + if new[attr]: + text = "now related" + else: + text = "not related anymore" + fieldprint(old, new, "", text, reprs) + elif attr == "table": + if old[attr] != new[attr]: + fieldprint(old, new, attr, "", reprs) + if old[attr] and new[attr]: + if old["column1"] != new["column1"]: + fieldprint(old, new, "column1", "", reprs) + if old["column2"] != new["column2"]: + fieldprint(old, new, "column2", "", reprs) + elif old[attr] != new[attr]: + fieldprint(old, new, attr, "", 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 = collections.defaultdict(list) + + def clean_records(records): + result = [] + for record in records: + if record["field"] not in IGNORE_FIELDS: + result.append(record) + return result + + old_records = clean_records(old_records) + new_records = clean_records(new_records) + + origlen = len(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 + matched_other_type = 0 + in_obsolete_models = 0 + + obsolete_models = [] + for model in old_models: + if model not in new_models: + if model_map(model) not in new_models: + obsolete_models.append(model) + + non_obsolete_old_records = [] + for column in copy.copy(old_records): + if column["model"] in obsolete_models: + in_obsolete_models += 1 + else: + non_obsolete_old_records.append(column) + + def match(match_fields, report_fields, warn=False): + count = 0 + for column in copy.copy(non_obsolete_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) + non_obsolete_old_records.remove(column) + new_records.remove(found) + count += 1 + return count + + matched_direct = match( + ["module", "mode", "model", "field"], + [ + "relation", + "type", + "selection_keys", + "_inherits", + "stored", + "isfunction", + "isrelated", + "required", + "table", + "_order", + ], + ) + + # other module, same type and operation + matched_other_module = match( + ["mode", "model", "field", "type"], + [ + "module", + "relation", + "selection_keys", + "_inherits", + "stored", + "isfunction", + "isrelated", + "required", + "table", + "_order", + ], + ) + + # other module, same operation, other type + matched_other_type = match( + ["module", "mode", "model", "field"], + [ + "relation", + "type", + "selection_keys", + "_inherits", + "stored", + "isfunction", + "isrelated", + "required", + "table", + "_order", + ], + ) + + # Info that is displayed for deleted fields + printkeys_old = [ + "relation", + "required", + "selection_keys", + "_inherits", + "mode", + "attachment", + ] + # Info that is displayed for new fields + printkeys_new = printkeys_old + [ + "hasdefault", + ] + for column in old_records: + if column["field"] == "_order": + continue + # we do not care about removed non stored function fields + if not column["stored"] and (column["isfunction"] or column["isrelated"]): + continue + if column["mode"] == "create": + column["mode"] = "" + extra_message = ", ".join( + [ + k + ": " + str(column[k]) if k != str(column[k]) else k + for k in printkeys_old + if column[k] + ] + ) + if extra_message: + extra_message = " " + extra_message + fieldprint(column, "", "", "DEL" + extra_message, reprs) + + for column in new_records: + if column["field"] == "_order": + continue + # we do not care about newly added non stored function fields + if not column["stored"] and (column["isfunction"] or column["isrelated"]): + continue + if column["mode"] == "create": + column["mode"] = "" + printkeys = printkeys_new.copy() + if column["isfunction"] or column["isrelated"]: + printkeys.extend(["isfunction", "isrelated", "stored"]) + extra_message = ", ".join( + [ + 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, "", "", "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) + return reprs + + +def compare_xml_sets(old_records, new_records): + reprs = collections.defaultdict(list) + + def match_updates(match_fields): + old_updated, new_updated = {}, {} + for column in copy.copy(old_records): + found_all = search(column, old_records, match_fields, True) + for found in found_all: + old_records.remove(found) + for column in copy.copy(new_records): + found_all = search(column, new_records, match_fields, True) + for found in found_all: + new_records.remove(found) + matched_records = list(old_updated.values()) + list(new_updated.values()) + matched_records = [y for x in matched_records for y in x] + return matched_records + + 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 + found["definition"] = ( + column["definition"] + and column["definition"] != found["definition"] + and "is now '{}' ('{}')".format( + found["definition"], column["definition"] + ) + ) + column["definition"] = 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["definition"]) + ) or found["noupdate_switched"]: + matched_records.append(found) + return matched_records + + # direct match + modified_records = match(["module", "model", "name"]) + + # updated records (will be excluded) + match_updates(["model", "name"]) + + # other module, same full xmlid + moved_records = match(["model", "name"], "moved") + + # other module, same suffix, other prefix + renamed_records = match(["model", "suffix", "other_prefix"], "renamed") + + for record in old_records: + record["old"] = True + record["domain"] = False + record["definition"] = False + record["noupdate_switched"] = False + for record in new_records: + record["new"] = True + record["domain"] = False + record["definition"] = 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"]), + ) + for entry in sorted_records: + content = "" + if "old" in entry: + content = f"DEL {entry['model']}: {entry['name']}" + if "moved" in entry: + content += f" [moved to {entry['moved']} module]" + elif "renamed" in entry: + content += f" [renamed to {entry['renamed']} module]" + elif "new" in entry: + content = f"NEW {entry['model']}: {entry['name']}" + if "moved" in entry: + content += f" [moved from {entry['moved']} module]" + elif "renamed" in entry: + content += f" [renamed from {entry['renamed']} module]" + if "old" not in entry and "new" not in entry: + content = f"{entry['model']}: {entry['name']}" + if entry["domain"]: + content += " (deleted domain)" + if entry["definition"]: + content += f" (changed definition: {entry['definition']})" + if entry["noupdate"]: + content += " (noupdate)" + if entry["noupdate_switched"]: + content += " (noupdate switched)" + reprs[module_map(entry["module"])].append(content) + return reprs + + +def compare_model_sets(old_records, new_records): + """ + Compare a set of OpenUpgrade model representations. + """ + 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} + + obsolete_models = [] + for column in copy.copy(old_records): + 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 = f"obsolete model {model}" + if column["model_type"]: + text += f" [{column['model_type']}]" + reprs[module_map(column["module"])].append(text) + reprs["general"].append( + f"obsolete model {model} [module module_map(column['module'])]" + ) + else: + moved_module = "" + if module_map(column["module"]) != new_models[model_map(model)]: + moved_module = f" in module {new_models[model_map(model)]}" + text = "obsolete model {} (renamed to {}{})".format( + model, + model_map(model), + moved_module, + ) + if column["model_type"]: + text += " [column['model_type']]" + reprs[module_map(column["module"])].append(text) + reprs["general"].append( + f"obsolete model {model} (renamed to {model_map(model)}) " + f"[module {module_map(column['module'])}]" + ) + else: + if module_map(column["module"]) != new_models[model]: + text = f"model {model} (moved to {new_models[model]})" + if column["model_type"]: + text += " [column['model_type']]" + reprs[module_map(column["module"])].append(text) + text = f"model {model} (moved from {old_models[model]})" + if column["model_type"]: + text += " [column['model_type']]" + + for column in copy.copy(new_records): + model = column["model"] + if model in new_models: + if model not in old_models: + if inv_model_map(model) not in old_models: + text = f"new model {model}" + if column["model_type"]: + text += f" [{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 = f" in module {old_models[inv_model_map(model)]}" + text = "new model {} (renamed from {}{})".format( + model, + inv_model_map(model), + moved_module, + ) + if column["model_type"]: + text += f" [{column['model_type']}]" + reprs[column["module"]].append(text) + reprs["general"].append( + f"new model {model} (renamed from {inv_model_map(model)}) " + f"[module {column['module']}]" + ) + else: + if column["module"] != module_map(old_models[model]): + text = f"model {model} (moved from {old_models[model]})" + if column["model_type"]: + text += f" [{column['model_type']}]" + reprs[column["module"]].append(text) + return reprs diff --git a/upgrade_analysis/i18n/es_AR.po b/upgrade_analysis/i18n/es_AR.po new file mode 100644 index 00000000000..f9a7acb05fa --- /dev/null +++ b/upgrade_analysis/i18n/es_AR.po @@ -0,0 +1,552 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * upgrade_analysis +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-09-04 06:07+0000\n" +"Last-Translator: Ignacio Buioli \n" +"Language-Team: none\n" +"Language: es_AR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Modules" +msgstr "Todos los Módulos" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All OCA Modules" +msgstr "Todos los Módulos de OCA" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Odoo SA Modules" +msgstr "Todos los Módulos de Odoo SA" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Other Modules" +msgstr "Todos los Otros Módulos" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__analysis_ids +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "Analyses" +msgstr "Análisis" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__analysis_date +msgid "Analysis Date" +msgstr "Fecha de Análisis" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__analysis_qty +msgid "Analysis Qty" +msgstr "Análisis de Cantidad" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__attribute_ids +msgid "Attribute" +msgstr "Atributo" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_form +msgid "Attributes" +msgstr "Atributos" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/wizards/upgrade_generate_record_wizard.py:0 +#, python-format +msgid "Cannot seem to install or upgrade modules %s" +msgstr "Parece que no se pueden instalar o actualizar los módulos %s" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Clear the list" +msgstr "Limpiar la lista" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Close" +msgstr "Cerrar" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__config_id +msgid "Comparison Config" +msgstr "Configuración de Comparación" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_comparison_config +msgid "Comparison Configurations" +msgstr "Configuraciones de Comparación" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "" +"Connection failed.\n" +"\n" +"DETAIL: %s" +msgstr "" +"Conexión fallida.\n" +"\n" +"DETALLES: %s" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "Continue" +msgstr "Continuar" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "Could not connect the Odoo server at %(server)s:%(port)s" +msgstr "No es posible conectar al servidor de Odoo en %(server)s:%(port)s" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__mode__create +msgid "Create" +msgstr "Crear" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_search +msgid "Create Mode" +msgstr "Modo de Creación" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__database +msgid "Database" +msgstr "Base de Datos" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__definition +msgid "Definition" +msgstr "Definición" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__domain +msgid "Domain" +msgstr "Dominio" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_analysis__state__done +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_generate_record_wizard__state__done +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_install_wizard__state__done +msgid "Done" +msgstr "Hecho" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_generate_record_wizard__state__draft +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_install_wizard__state__draft +msgid "Draft" +msgstr "Borrador" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__field +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__field +msgid "Field" +msgstr "Campo" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_generate_record_wizard +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_generate_record +msgid "Generate Records Wizard" +msgstr "Asistente de Generación de Registros" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__id +msgid "ID" +msgstr "ID" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Install Modules" +msgstr "Instalar Módulos" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_install_wizard +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_install +msgid "Install Modules Wizard" +msgstr "Asistente de Instalación de Módulos" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__is_oca_module +msgid "Is Oca Module" +msgstr "Es un Módulo de OCA" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__is_odoo_module +msgid "Is Odoo Module" +msgstr "Es un Módulo de Odoo" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__write_uid +msgid "Last Updated by" +msgstr "Última actualización realizada por" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__log +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_analysis_form +msgid "Log" +msgstr "Registro" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__mode +msgid "Mode" +msgstr "Modo" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__model +msgid "Model" +msgstr "Modelo" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model_original_module +msgid "Model Original Module" +msgstr "Modelo de Módulo Original" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model_type +msgid "Model Type" +msgstr "Tipo de Modelo" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__mode__modify +msgid "Modify" +msgstr "Modificar" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_search +msgid "Modify Mode" +msgstr "Modo de Modificación" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_ir_module_module +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__module_ids +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__module +msgid "Module" +msgstr "Módulo" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__module_qty +msgid "Modules Quantity" +msgstr "Cantidad de Módulos" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "Modules initialized and record created" +msgstr "Módulos inicializados y registros creados" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__name +msgid "Name" +msgstr "Nombre" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "New Analysis" +msgstr "Nuevo Análisis" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_record.py:0 +#, python-format +msgid "No manifest found in %(addon_dir)s" +msgstr "No se encontró manifiesta en %(addon_dir)s" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__noupdate +msgid "Noupdate" +msgstr "Noupdate" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__password +msgid "Password" +msgstr "Contraseña" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_analysis_form +msgid "Perform Analysis" +msgstr "Realizar Análisis" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__port +msgid "Port" +msgstr "Puerto" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__prefix +msgid "Prefix" +msgstr "Prefijo" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__record_id +msgid "Record" +msgstr "Registro" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_records +msgid "Records" +msgstr "Registros" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__server +msgid "Server" +msgstr "Servidor" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_record__mode +msgid "" +"Set to Create if a field is newly created in this module. If this module " +"modifies an attribute of an existing field, set to Modify." +msgstr "" +"Establézcalo en Crear si se crea un campo recientemente en este módulo. Si " +"este módulo modifica un atributo de un campo existente, configúrelo en " +"Modificar." + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__smart_search +msgid "Smart Search" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__state +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__state +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__state +msgid "State" +msgstr "Estado" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__suffix +msgid "Suffix" +msgstr "Sufijo" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "Test Connection" +msgstr "Prueba de Conexión" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_analysis__upgrade_path +msgid "" +"The base file path to save the analyse files of Odoo modules. Taken from " +"Odoo's --upgrade-path command line option or the 'scripts' subdirectory in " +"the openupgrade_scripts addon." +msgstr "" +"La ruta del archivo base para guardar los archivos de análisis de los " +"módulos de Odoo. Tomado de la opción de línea de comando --upgrade-path de " +"Odoo o del subdirectorio 'scripts' en el complemento openupgrade_scripts." + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "The modules have been installed successfuly" +msgstr "Los módulos han sido instalados correctamente" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "" +"This will install the selected modules on the database. Do not continue if " +"you use this database in production." +msgstr "" +"Esto instalará los módulos seleccionados en la base de datos. No continúe si " +"utiliza esta base de datos en producción." + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "" +"This will reinitialize all the modules installed on this database. Do not " +"continue if you use this database in production." +msgstr "" +"Esto reiniciará todos los módulos instalados en esta base de datos. No " +"continúe si utiliza esta base de datos en producción." + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__type +msgid "Type" +msgstr "Tipo" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_analysis.py:0 +#, python-format +msgid "Unexpected root Element: %(root)s in file: %(file)s" +msgstr "Elemento root Inesperado: %(root)s en el archivo: %(file)s" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_analysis_tree +#: model:ir.model,name:upgrade_analysis.model_upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_analysis +msgid "Upgrade Analyses" +msgstr "Análisis de Actualización" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade +msgid "Upgrade Analysis" +msgstr "Análisis de Actualización" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_attribute +msgid "Upgrade Attribute" +msgstr "Atributo de Actualización" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_comparison_config +msgid "Upgrade Comparison Configuration" +msgstr "Configuración de Comparación de Actualizaciones" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_generate_record_wizard +msgid "Upgrade Generate Record Wizard" +msgstr "Actualización del Asistente de Generación de Registros" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_install_wizard +msgid "Upgrade Install Wizard" +msgstr "Asistente de Instalación de Actualización" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__upgrade_path +msgid "Upgrade Path" +msgstr "Ruta de Actualización" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_record +msgid "Upgrade Record" +msgstr "Registro de Actualización" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__username +msgid "Username" +msgstr "Nombre de usuario" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__value +msgid "Value" +msgstr "Valor" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__version +msgid "Version" +msgstr "Versión" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_files +msgid "Write Files" +msgstr "Escribir Archivos" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_analysis__write_files +msgid "Write analysis files to the module directories" +msgstr "Escribir archivos de análisis en los directorios del módulo" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__xmlid +msgid "XML ID" +msgstr "XML ID" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "" +"You are correctly connected to the server %(server)s (version %(version)s) " +"with the user %(user_name)s" +msgstr "" +"Está correctamente conectado al servidor %(server)s (versión %(version)s) " +"con el usuario %(user_name)s" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_analysis__state__draft +msgid "draft" +msgstr "borrador" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_comparison_config_tree +msgid "upgrade Comparison Configs" +msgstr "actualizar configuraciones de comparación" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_record_tree +msgid "upgrade Records" +msgstr "actualizar Registros" diff --git a/upgrade_analysis/i18n/fr.po b/upgrade_analysis/i18n/fr.po new file mode 100644 index 00000000000..2be62b11326 --- /dev/null +++ b/upgrade_analysis/i18n/fr.po @@ -0,0 +1,537 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * upgrade_analysis +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-05-14 19:47+0000\n" +"Last-Translator: Yves Le Doeuff \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Modules" +msgstr "Tous les modules" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All OCA Modules" +msgstr "Tous les modules OCA" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Odoo SA Modules" +msgstr "Tous les modules Odoo SA" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Other Modules" +msgstr "Tous les autres modules" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__analysis_ids +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "Analyses" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__analysis_date +msgid "Analysis Date" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__analysis_qty +msgid "Analysis Qty" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__attribute_ids +msgid "Attribute" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_form +msgid "Attributes" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/wizards/upgrade_generate_record_wizard.py:0 +#, python-format +msgid "Cannot seem to install or upgrade modules %s" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Clear the list" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Close" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__config_id +msgid "Comparison Config" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_comparison_config +msgid "Comparison Configurations" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "" +"Connection failed.\n" +"\n" +"DETAIL: %s" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "Continue" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "Could not connect the Odoo server at %(server)s:%(port)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__mode__create +msgid "Create" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_search +msgid "Create Mode" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__create_uid +msgid "Created by" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__create_date +msgid "Created on" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__database +msgid "Database" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__definition +msgid "Definition" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__display_name +msgid "Display Name" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__domain +msgid "Domain" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_analysis__state__done +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_generate_record_wizard__state__done +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_install_wizard__state__done +msgid "Done" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_generate_record_wizard__state__draft +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_install_wizard__state__draft +msgid "Draft" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__field +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__field +msgid "Field" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_generate_record_wizard +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_generate_record +msgid "Generate Records Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__id +msgid "ID" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Install Modules" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_install_wizard +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_install +msgid "Install Modules Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__is_oca_module +msgid "Is Oca Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__is_odoo_module +msgid "Is Odoo Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record____last_update +msgid "Last Modified on" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__write_date +msgid "Last Updated on" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__log +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_analysis_form +msgid "Log" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__mode +msgid "Mode" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__model +msgid "Model" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model_original_module +msgid "Model Original Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model_type +msgid "Model Type" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__mode__modify +msgid "Modify" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_search +msgid "Modify Mode" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_ir_module_module +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__module_ids +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__module +msgid "Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__module_qty +msgid "Modules Quantity" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "Modules initialized and record created" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__name +msgid "Name" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "New Analysis" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_record.py:0 +#, python-format +msgid "No manifest found in %(addon_dir)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__noupdate +msgid "Noupdate" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__password +msgid "Password" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_analysis_form +msgid "Perform Analysis" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__port +msgid "Port" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__prefix +msgid "Prefix" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__record_id +msgid "Record" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_records +msgid "Records" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__server +msgid "Server" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_record__mode +msgid "" +"Set to Create if a field is newly created in this module. If this module " +"modifies an attribute of an existing field, set to Modify." +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__smart_search +msgid "Smart Search" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__state +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__state +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__state +msgid "State" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__suffix +msgid "Suffix" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "Test Connection" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_analysis__upgrade_path +msgid "" +"The base file path to save the analyse files of Odoo modules. Taken from " +"Odoo's --upgrade-path command line option or the 'scripts' subdirectory in " +"the openupgrade_scripts addon." +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "The modules have been installed successfuly" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "" +"This will install the selected modules on the database. Do not continue if " +"you use this database in production." +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "" +"This will reinitialize all the modules installed on this database. Do not " +"continue if you use this database in production." +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__type +msgid "Type" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_analysis.py:0 +#, python-format +msgid "Unexpected root Element: %(root)s in file: %(file)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_analysis_tree +#: model:ir.model,name:upgrade_analysis.model_upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_analysis +msgid "Upgrade Analyses" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade +msgid "Upgrade Analysis" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_attribute +msgid "Upgrade Attribute" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_comparison_config +msgid "Upgrade Comparison Configuration" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_generate_record_wizard +msgid "Upgrade Generate Record Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_install_wizard +msgid "Upgrade Install Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__upgrade_path +msgid "Upgrade Path" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_record +msgid "Upgrade Record" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__username +msgid "Username" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__value +msgid "Value" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__version +msgid "Version" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_files +msgid "Write Files" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_analysis__write_files +msgid "Write analysis files to the module directories" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__xmlid +msgid "XML ID" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "" +"You are correctly connected to the server %(server)s (version %(version)s) " +"with the user %(user_name)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_analysis__state__draft +msgid "draft" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_comparison_config_tree +msgid "upgrade Comparison Configs" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_record_tree +msgid "upgrade Records" +msgstr "" diff --git a/upgrade_analysis/i18n/upgrade_analysis.pot b/upgrade_analysis/i18n/upgrade_analysis.pot new file mode 100644 index 00000000000..65e03a4a302 --- /dev/null +++ b/upgrade_analysis/i18n/upgrade_analysis.pot @@ -0,0 +1,534 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * upgrade_analysis +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Modules" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All OCA Modules" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Odoo SA Modules" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "All Other Modules" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__analysis_ids +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "Analyses" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__analysis_date +msgid "Analysis Date" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__analysis_qty +msgid "Analysis Qty" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__attribute_ids +msgid "Attribute" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_form +msgid "Attributes" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/wizards/upgrade_generate_record_wizard.py:0 +#, python-format +msgid "Cannot seem to install or upgrade modules %s" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Clear the list" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Close" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__config_id +msgid "Comparison Config" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_comparison_config +msgid "Comparison Configurations" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "" +"Connection failed.\n" +"\n" +"DETAIL: %s" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "Continue" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "Could not connect the Odoo server at %(server)s:%(port)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__mode__create +msgid "Create" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_search +msgid "Create Mode" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__create_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__create_uid +msgid "Created by" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__create_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__create_date +msgid "Created on" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__database +msgid "Database" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__definition +msgid "Definition" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__display_name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__display_name +msgid "Display Name" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__domain +msgid "Domain" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_analysis__state__done +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_generate_record_wizard__state__done +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_install_wizard__state__done +msgid "Done" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_generate_record_wizard__state__draft +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_install_wizard__state__draft +msgid "Draft" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__field +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__field +msgid "Field" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_generate_record_wizard +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_generate_record +msgid "Generate Records Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__id +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__id +msgid "ID" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "Install Modules" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_install_wizard +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_install +msgid "Install Modules Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__is_oca_module +msgid "Is Oca Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__is_odoo_module +msgid "Is Odoo Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard____last_update +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record____last_update +msgid "Last Modified on" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__write_uid +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__write_date +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__write_date +msgid "Last Updated on" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__log +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_analysis_form +msgid "Log" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__mode +msgid "Mode" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__model +msgid "Model" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model_original_module +msgid "Model Original Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__model_type +msgid "Model Type" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__mode__modify +msgid "Modify" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_record_search +msgid "Modify Mode" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_ir_module_module +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__module_ids +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__module +msgid "Module" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__module_qty +msgid "Modules Quantity" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "Modules initialized and record created" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__name +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__name +msgid "Name" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "New Analysis" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_record.py:0 +#, python-format +msgid "No manifest found in %(addon_dir)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__noupdate +msgid "Noupdate" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__password +msgid "Password" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_analysis_form +msgid "Perform Analysis" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__port +msgid "Port" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__prefix +msgid "Prefix" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__record_id +msgid "Record" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_records +msgid "Records" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__server +msgid "Server" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_record__mode +msgid "" +"Set to Create if a field is newly created in this module. If this module " +"modifies an attribute of an existing field, set to Modify." +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_ir_module_module__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__smart_search +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__smart_search +msgid "Smart Search" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__state +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_generate_record_wizard__state +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_install_wizard__state +msgid "State" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__suffix +msgid "Suffix" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_comparison_config_form +msgid "Test Connection" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_analysis__upgrade_path +msgid "" +"The base file path to save the analyse files of Odoo modules. Taken from " +"Odoo's --upgrade-path command line option or the 'scripts' subdirectory in " +"the openupgrade_scripts addon." +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "The modules have been installed successfuly" +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_install_wizard_form +msgid "" +"This will install the selected modules on the database. Do not continue if " +"you use this database in production." +msgstr "" + +#. module: upgrade_analysis +#: model_terms:ir.ui.view,arch_db:upgrade_analysis.view_upgrade_generate_record_wizard_form +msgid "" +"This will reinitialize all the modules installed on this database. Do not " +"continue if you use this database in production." +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_record__type +msgid "Type" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_analysis.py:0 +#, python-format +msgid "Unexpected root Element: %(root)s in file: %(file)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_analysis_tree +#: model:ir.model,name:upgrade_analysis.model_upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade_analysis +msgid "Upgrade Analyses" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.ui.menu,name:upgrade_analysis.menu_upgrade +msgid "Upgrade Analysis" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_attribute +msgid "Upgrade Attribute" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_comparison_config +msgid "Upgrade Comparison Configuration" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_generate_record_wizard +msgid "Upgrade Generate Record Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_install_wizard +msgid "Upgrade Install Wizard" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__upgrade_path +msgid "Upgrade Path" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model,name:upgrade_analysis.model_upgrade_record +msgid "Upgrade Record" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__username +msgid "Username" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_attribute__value +msgid "Value" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_comparison_config__version +msgid "Version" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,field_description:upgrade_analysis.field_upgrade_analysis__write_files +msgid "Write Files" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields,help:upgrade_analysis.field_upgrade_analysis__write_files +msgid "Write analysis files to the module directories" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_record__type__xmlid +msgid "XML ID" +msgstr "" + +#. module: upgrade_analysis +#. odoo-python +#: code:addons/upgrade_analysis/models/upgrade_comparison_config.py:0 +#, python-format +msgid "" +"You are correctly connected to the server %(server)s (version %(version)s) " +"with the user %(user_name)s" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.model.fields.selection,name:upgrade_analysis.selection__upgrade_analysis__state__draft +msgid "draft" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_comparison_config_tree +msgid "upgrade Comparison Configs" +msgstr "" + +#. module: upgrade_analysis +#: model:ir.actions.act_window,name:upgrade_analysis.action_upgrade_record_tree +msgid "upgrade Records" +msgstr "" diff --git a/upgrade_analysis/models/__init__.py b/upgrade_analysis/models/__init__.py new file mode 100644 index 00000000000..7a9c9233d7e --- /dev/null +++ b/upgrade_analysis/models/__init__.py @@ -0,0 +1,5 @@ +from . import ir_module_module +from . import upgrade_comparison_config +from . import upgrade_analysis +from . import upgrade_attribute +from . import upgrade_record diff --git a/upgrade_analysis/models/ir_module_module.py b/upgrade_analysis/models/ir_module_module.py new file mode 100644 index 00000000000..38edbbac890 --- /dev/null +++ b/upgrade_analysis/models/ir_module_module.py @@ -0,0 +1,32 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os + +from odoo import fields, models +from odoo.modules import get_module_path + + +class IrModuleModule(models.Model): + _inherit = "ir.module.module" + + is_odoo_module = fields.Boolean( + compute="_compute_is_odoo_module", + ) + + is_oca_module = fields.Boolean(compute="_compute_is_oca_module") + + def _compute_is_oca_module(self): + for module in self: + module.is_oca_module = "/OCA/" in module.website + + def _compute_is_odoo_module(self): + for module in self: + module_path = get_module_path(module.name) + if not module_path: + module.is_odoo_module = False + continue + absolute_repo_path = os.path.split(module_path)[0] + x, relative_repo_path = os.path.split(absolute_repo_path) + module.is_odoo_module = relative_repo_path == "addons" diff --git a/upgrade_analysis/models/upgrade_analysis.py b/upgrade_analysis/models/upgrade_analysis.py new file mode 100644 index 00000000000..b1e0789a88a --- /dev/null +++ b/upgrade_analysis/models/upgrade_analysis.py @@ -0,0 +1,596 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016-2020 Opener B.V. +# Copyright 2019 ForgeFlow +# Copyright 2020 GRAP +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# flake8: noqa: C901 + +import logging +import os +from copy import deepcopy + +from lxml import etree +from mako.template import Template + +from odoo import fields, models, release +from odoo.exceptions import ValidationError +from odoo.modules import get_module_path +from odoo.tools import config +from odoo.tools.convert import nodeattr2bool +from odoo.tools.translate import _ + +try: + from odoo.addons.openupgrade_scripts.apriori import merged_modules, renamed_modules +except ImportError: + renamed_modules = {} + merged_modules = {} + +from .. import compare + +_logger = logging.getLogger(__name__) +_IGNORE_MODULES = ["openupgrade_records", "upgrade_analysis"] + + +class UpgradeAnalysis(models.Model): + _name = "upgrade.analysis" + _description = "Upgrade Analyses" + + analysis_date = fields.Datetime(readonly=True) + + state = fields.Selection( + [("draft", "draft"), ("done", "Done")], readonly=True, default="draft" + ) + config_id = fields.Many2one( + string="Comparison Config", + comodel_name="upgrade.comparison.config", + readonly=True, + required=True, + ) + + log = fields.Text(readonly=True) + upgrade_path = fields.Char( + compute="_compute_upgrade_path", + readonly=False, + store=True, + help=( + "The base file path to save the analyse files of Odoo modules. " + "Taken from Odoo's --upgrade-path command line option or the " + "'scripts' subdirectory in the openupgrade_scripts addon." + ), + ) + write_files = fields.Boolean( + help="Write analysis files to the module directories", default=True + ) + + def _compute_upgrade_path(self): + """Return the --upgrade-path configuration option or the `scripts` + directory in `openupgrade_scripts` if available + """ + res = config.get("upgrade_path", False) + if not res: + module_path = get_module_path("openupgrade_scripts", display_warning=False) + if module_path: + res = os.path.join(module_path, "scripts") + self.upgrade_path = res + + def _get_remote_model(self, connection, model): + self.ensure_one() + if model == "record": + if float(self.config_id.version) < 14.0: + return connection.env["openupgrade.record"] + else: + return connection.env["upgrade.record"] + return False + + def _write_file( + self, module_name, version, content, filename="upgrade_analysis.txt" + ): + module = self.env["ir.module.module"].search([("name", "=", module_name)])[0] + if module.is_odoo_module: + if not self.upgrade_path: + return ( + "ERROR: no upgrade_path set when writing analysis of %s\n" + % module_name + ) + full_path = os.path.join(self.upgrade_path, module_name, version) + else: + full_path = os.path.join( + get_module_path(module_name), "migrations", version + ) + if not os.path.exists(full_path): + try: + os.makedirs(full_path) + except OSError: + return "ERROR: could not create migrations directory %s:\n" % ( + full_path + ) + 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 + _logger.debug("Writing analysis to %s", logfile) + f.write(content) + f.close() + return None + + def analyze(self): + """ + Retrieve both sets of database representations, + perform the comparison and register the resulting + change set + """ + self.ensure_one() + self.write( + { + "analysis_date": fields.Datetime.now(), + } + ) + + connection = self.config_id.get_connection() + RemoteRecord = self._get_remote_model(connection, "record") + LocalRecord = self.env["upgrade.record"] + + # Retrieve field representations and compare + remote_records = RemoteRecord.field_dump() + local_records = LocalRecord.field_dump() + res = compare.compare_sets(remote_records, local_records) + + # Retrieve xml id representations and compare + flds = [ + "module", + "model", + "name", + "noupdate", + "prefix", + "suffix", + "domain", + "definition", + ] + local_xml_records = [ + {field: record[field] for field in flds} + for record in LocalRecord.search([("type", "=", "xmlid")]) + ] + remote_xml_record_ids = RemoteRecord.search([("type", "=", "xmlid")]) + remote_xml_records = [ + {field: record[field] for field in flds} + for record in RemoteRecord.read(remote_xml_record_ids, flds) + ] + res_xml = compare.compare_xml_sets(remote_xml_records, local_xml_records) + + # Retrieve model representations and compare + flds = [ + "module", + "model", + "name", + "model_original_module", + "model_type", + ] + local_model_records = [ + {field: record[field] for field in flds} + for record in LocalRecord.search([("type", "=", "model")]) + ] + remote_model_record_ids = RemoteRecord.search([("type", "=", "model")]) + remote_model_records = [ + {field: record[field] for field in flds} + for record in RemoteRecord.read(remote_model_record_ids, flds) + ] + res_model = compare.compare_model_sets( + remote_model_records, local_model_records + ) + + affected_modules = sorted( + { + record["module"] + for record in remote_records + + local_records + + remote_xml_records + + local_xml_records + + remote_model_records + + local_model_records + } + ) + if "base" in affected_modules: + try: + pass + except ImportError: + _logger.error( + "You are using upgrade_analysis on core modules without " + " having openupgrade_scripts module available." + " The analysis process will not work properly," + " if you are generating analysis for the odoo modules" + " in an openupgrade context." + ) + + # reorder and output the result + keys = ["general"] + affected_modules + modules = { + module["name"]: module + for module in self.env["ir.module.module"].search( + [("state", "=", "installed")] + ) + } + general_log = "" + + no_changes_modules = [] + + for ignore_module in _IGNORE_MODULES: + if ignore_module in keys: + keys.remove(ignore_module) + + for key in keys: + contents = "---Models in module '%s'---\n" % key + if key in res_model: + contents += "\n".join([str(line) for line in res_model[key]]) + if res_model[key]: + contents += "\n" + contents += "---Fields in module '%s'---\n" % key + if key in res: + contents += "\n".join([str(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([str(line) for line in res_xml[key]]) + if res_xml[key]: + contents += "\n" + if key not in res and key not in res_xml and key not in res_model: + contents += "---nothing has changed in this module--\n" + no_changes_modules.append(key) + if key == "general": + general_log += contents + continue + if compare.module_map(key) not in modules: + general_log += ( + "ERROR: module not in list of installed modules:\n" + contents + ) + continue + if key not in modules: + # no need to log in full log the merged/renamed modules + continue + if self.write_files: + error = self._write_file(key, modules[key].installed_version, contents) + if error: + general_log += error + general_log += contents + else: + general_log += contents + + # Store the full log + if self.write_files and "base" in modules: + self._write_file( + "base", + modules["base"].installed_version, + general_log, + "upgrade_general_log.txt", + ) + + try: + self.generate_noupdate_changes() + except Exception as e: + _logger.exception("Error generating noupdate changes: %s" % e) + general_log += "ERROR: error when generating noupdate changes: %s\n" % e + try: + self.generate_module_coverage_file(no_changes_modules) + except Exception as e: + _logger.exception("Error generating module coverage file: %s" % e) + general_log += "ERROR: error when generating module coverage file: %s\n" % e + + self.write( + { + "state": "done", + "log": general_log, + } + ) + return True + + @staticmethod + def _get_node_dict(element): + res = {} + if element is None: + return res + for child in element: + if "name" in child.attrib: + key = "./{}[@name='{}']".format(child.tag, child.attrib["name"]) + res[key] = child + return res + + @staticmethod + def _get_node_value(element): + if "eval" in element.attrib.keys(): + return element.attrib["eval"] + if "ref" in element.attrib.keys(): + return element.attrib["ref"] + if not len(element): + return element.text + return etree.tostring(element) + + def _get_xml_diff( + self, remote_update, remote_noupdate, local_update, local_noupdate + ): + odoo = etree.Element("odoo") + for xml_id in sorted(local_noupdate.keys()): + local_record = local_noupdate[xml_id] + remote_record = None + if xml_id in remote_update and xml_id not in remote_noupdate: + remote_record = remote_update[xml_id] + elif xml_id in remote_noupdate: + remote_record = remote_noupdate[xml_id] + + if "." in xml_id: + module_xmlid = xml_id.split(".", 1)[0] + else: + module_xmlid = "" + + if remote_record is None and not module_xmlid: + continue + + if local_record.tag == "template": + old_tmpl = etree.tostring(remote_record, encoding="utf-8") + new_tmpl = etree.tostring(local_record, encoding="utf-8") + if old_tmpl != new_tmpl: + odoo.append(local_record) + continue + + element = etree.Element( + "record", id=xml_id, model=local_record.attrib["model"] + ) + # Add forcecreate attribute if exists + if local_record.attrib.get("forcecreate"): + element.attrib["forcecreate"] = local_record.attrib["forcecreate"] + record_remote_dict = self._get_node_dict(remote_record) + record_local_dict = self._get_node_dict(local_record) + for key in sorted(record_remote_dict.keys()): + if not local_record.xpath(key): + # The element is no longer present. + # Does the field still exist? + if record_remote_dict[key].tag == "field": + field_name = remote_record.xpath(key)[0].attrib.get("name") + if ( + field_name + not in self.env[local_record.attrib["model"]]._fields.keys() + ): + continue + # Overwrite an existing value with an empty one. + attribs = deepcopy(record_remote_dict[key]).attrib + for attr in ["eval", "ref"]: + if attr in attribs: + del attribs[attr] + element.append(etree.Element(record_remote_dict[key].tag, attribs)) + else: + oldrepr = self._get_node_value(record_remote_dict[key]) + newrepr = self._get_node_value(record_local_dict[key]) + + if oldrepr != newrepr: + element.append(deepcopy(record_local_dict[key])) + + for key in sorted(record_local_dict.keys()): + if remote_record is None or not remote_record.xpath(key): + element.append(deepcopy(record_local_dict[key])) + + if len(element): + odoo.append(element) + + if not len(odoo): + return "" + return etree.tostring( + etree.ElementTree(odoo), + pretty_print=True, + xml_declaration=True, + encoding="utf-8", + ).decode("utf-8") + + @staticmethod + def _update_node(target, source): + for element in source: + if "name" in element.attrib: + query = "./{}[@name='{}']".format(element.tag, element.attrib["name"]) + else: + # query = "./{}".format(element.tag) + continue + for existing in target.xpath(query): + target.remove(existing) + target.append(element) + + @classmethod + def _process_data_node( + self, data_node, records_update, records_noupdate, module_name + ): + noupdate = nodeattr2bool(data_node, "noupdate", False) + for record in data_node.xpath("./record") + data_node.xpath("./template"): + self._process_record_node( + record, noupdate, records_update, records_noupdate, module_name + ) + + @classmethod + def _process_record_node( + self, record, noupdate, records_update, records_noupdate, module_name + ): + xml_id = record.get("id") + if not xml_id: + return + if "." in xml_id and xml_id.startswith(module_name + "."): + xml_id = xml_id[len(module_name) + 1 :] + for records in records_noupdate, records_update: + # records can occur multiple times in the same module + # with different noupdate settings + if xml_id in records: + # merge records (overwriting an existing element + # with the same tag). The order processing the + # various directives from the manifest is + # important here + self._update_node(records[xml_id], record) + break + else: + target_dict = records_noupdate if noupdate else records_update + target_dict[xml_id] = record + + @classmethod + def _parse_files(self, xml_files, module_name): + records_update = {} + records_noupdate = {} + parser = etree.XMLParser( + remove_blank_text=True, + strip_cdata=False, + ) + for xml_file in xml_files: + try: + # This is for a final correct pretty print + # Ref.: https://stackoverflow.com/a/7904066 + # Also don't strip CDATA tags as needed for HTML content + root_node = etree.fromstring(xml_file.encode("utf-8"), parser=parser) + except etree.XMLSyntaxError: + continue + # Support xml files with root Element either odoo or openerp + # Condition: each xml file should have only one root element + # {, or —rarely— }; + root_node_noupdate = nodeattr2bool(root_node, "noupdate", False) + if root_node.tag not in ("openerp", "odoo", "data"): + raise ValidationError( + _("Unexpected root Element: %(root)s in file: %(file)s") + % {"root": root_node.getroot(), "file": xml_file} + ) + for node in root_node: + if node.tag == "data": + self._process_data_node( + node, records_update, records_noupdate, module_name + ) + elif node.tag == "record": + self._process_record_node( + node, + root_node_noupdate, + records_update, + records_noupdate, + module_name, + ) + + return records_update, records_noupdate + + def generate_noupdate_changes(self): + """Communicate with the remote server to fetch all xml data records + per module, and generate a diff in XML format that can be imported + from the module's migration script using openupgrade.load_data() + """ + self.ensure_one() + connection = self.config_id.get_connection() + remote_record_obj = self._get_remote_model(connection, "record") + local_record_obj = self.env["upgrade.record"] + local_modules = local_record_obj.list_modules() + all_remote_modules = remote_record_obj.list_modules() + for local_module in local_modules: + remote_files = [] + remote_modules = [] + remote_update, remote_noupdate = {}, {} + for remote_module in all_remote_modules: + if local_module == renamed_modules.get( + remote_module, merged_modules.get(remote_module, remote_module) + ): + remote_files.extend( + remote_record_obj.get_xml_records(remote_module) + ) + remote_modules.append(remote_module) + add_remote_update, add_remote_noupdate = self._parse_files( + remote_files, remote_module + ) + remote_update.update(add_remote_update) + remote_noupdate.update(add_remote_noupdate) + if not remote_modules: + continue + local_files = local_record_obj.get_xml_records(local_module) + local_update, local_noupdate = self._parse_files(local_files, local_module) + diff = self._get_xml_diff( + remote_update, remote_noupdate, local_update, local_noupdate + ) + if diff: + module = self.env["ir.module.module"].search( + [("name", "=", local_module)] + ) + self._write_file( + local_module, + module.installed_version, + diff, + filename="noupdate_changes.xml", + ) + return True + + def generate_module_coverage_file(self, no_changes_modules): + self.ensure_one() + + module_coverage_file_folder = config.get("module_coverage_file_folder", False) + + if not module_coverage_file_folder: + return + + file_template = Template( + filename=os.path.join( + get_module_path("upgrade_analysis"), + "static", + "src", + "module_coverage_template.rst.mako", + ) + ) + + module_domain = [ + ("state", "=", "installed"), + ("name", "not in", ["upgrade_analysis", "openupgrade_records"]), + ] + + connection = self.config_id.get_connection() + all_local_modules = ( + self.env["ir.module.module"].search(module_domain).mapped("name") + ) + all_remote_modules = ( + connection.env["ir.module.module"] + .browse(connection.env["ir.module.module"].search(module_domain)) + .mapped("name") + ) + + start_version = connection.version + end_version = release.major_version + + all_modules = sorted(list(set(all_remote_modules + all_local_modules))) + module_descriptions = {} + for module in all_modules: + status = "" + if module in all_local_modules and module in all_remote_modules: + module_description = " %s" % module + elif module in all_local_modules: + module_description = " |new| %s" % module + else: + module_description = " |del| %s" % module + + if module in compare.apriori.merged_modules: + status = "Merged into %s. " % compare.apriori.merged_modules[module] + elif module in compare.apriori.renamed_modules: + status = "Renamed to %s. " % compare.apriori.renamed_modules[module] + elif module in compare.apriori.renamed_modules.values(): + status = ( + "Renamed from %s. " + % [ + x + for x in compare.apriori.renamed_modules + if compare.apriori.renamed_modules[x] == module + ][0] + ) + elif module in no_changes_modules: + status += "No DB layout changes. " + module_descriptions[module_description.ljust(51, " ")] = status.ljust( + 49, " " + ) + + rendered_text = file_template.render( + start_version=start_version, + end_version=end_version, + module_descriptions=module_descriptions, + ) + + file_name = "modules{}-{}.rst".format( + start_version.replace(".", ""), + end_version.replace(".", ""), + ) + + file_path = os.path.join(module_coverage_file_folder, file_name) + f = open(file_path, "w+") + f.write(rendered_text) + f.close() + return True diff --git a/upgrade_analysis/models/upgrade_attribute.py b/upgrade_analysis/models/upgrade_attribute.py new file mode 100644 index 00000000000..15c93446657 --- /dev/null +++ b/upgrade_analysis/models/upgrade_attribute.py @@ -0,0 +1,21 @@ +# 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", + index=True, + ondelete="CASCADE", + readonly=True, + ) diff --git a/upgrade_analysis/models/upgrade_comparison_config.py b/upgrade_analysis/models/upgrade_comparison_config.py new file mode 100644 index 00000000000..649866379af --- /dev/null +++ b/upgrade_analysis/models/upgrade_comparison_config.py @@ -0,0 +1,96 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from urllib.error import URLError + +import odoorpc + +from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.tools.translate import _ + + +class UpgradeComparisonConfig(models.Model): + _name = "upgrade.comparison.config" + _description = "Upgrade Comparison Configuration" + + name = fields.Char() + + server = fields.Char(required=True, default="localhost") + + port = fields.Integer(required=True, default=8069) + + database = fields.Char(required=True) + + username = fields.Char(required=True, default="admin") + + password = fields.Char(required=True, default="admin") + + version = fields.Char() + + analysis_ids = fields.One2many( + string="Analyses", comodel_name="upgrade.analysis", inverse_name="config_id" + ) + analysis_qty = fields.Integer(compute="_compute_analysis_qty") + + @api.depends("analysis_ids") + def _compute_analysis_qty(self): + for config in self: + config.analysis_qty = len(config.analysis_ids) + + def get_connection(self): + self.ensure_one() + try: + remote = odoorpc.ODOO(self.server, port=self.port) + except URLError as exc: + raise UserError( + _("Could not connect the Odoo server at %(server)s:%(port)s") + % {"server": self.server, "port": self.port} + ) from exc + remote.login(self.database, self.username, self.password) + self.version = remote.version + return remote + + def test_connection(self): + self.ensure_one() + try: + connection = self.get_connection() + user_model = connection.env["res.users"] + ids = user_model.search([("login", "=", "admin")]) + user_info = user_model.read([ids[0]], ["name"])[0] + except Exception as e: + raise UserError(_("Connection failed.\n\nDETAIL: %s") % e) from e + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "type": "info", + "message": _( + "You are correctly connected to the server %(server)s" + " (version %(version)s) with the user %(user_name)s" + ) + % dict( + server=self.server, + version=self.version, + user_name=user_info["name"], + ), + }, + } + + def new_analysis(self): + self.ensure_one() + analysis = self.env["upgrade.analysis"].create({"config_id": self.id}) + return { + "name": analysis._description, + "view_mode": "form", + "res_model": analysis._name, + "type": "ir.actions.act_window", + # "target": "new", + "res_id": analysis.id, + # "nodestroy": True, + } + + def action_show_analysis(self): + self.ensure_one() + return {} diff --git a/upgrade_analysis/models/upgrade_record.py b/upgrade_analysis/models/upgrade_record.py new file mode 100644 index 00000000000..36e0936d23c --- /dev/null +++ b/upgrade_analysis/models/upgrade_record.py @@ -0,0 +1,185 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016-2020 Opener B.V. +# Copyright 2019 ForgeFlow +# Copyright 2020 GRAP +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import ast +import logging +import os + +from odoo import api, fields, models +from odoo.exceptions import ValidationError +from odoo.modules.module import MANIFEST_NAMES, get_module_path +from odoo.tools.translate import _ + +_logger = logging.getLogger(__name__) + + +class UpgradeRecord(models.Model): + _name = "upgrade.record" + _description = "Upgrade 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 " + "in this module. If this module modifies an attribute of an " + "existing field, set to Modify.", + readonly=True, + ) + + type = fields.Selection( + [("field", "Field"), ("xmlid", "XML ID"), ("model", "Model")], + 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) + + definition = 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") + def _compute_prefix_and_suffix(self): + for rec in self: + rec.prefix, rec.suffix = rec.name.split(".", 1) + + @api.depends("model", "type") + def _compute_model_original_module(self): + for rec in self: + if rec.type == "model": + rec.model_original_module = self.env[rec.model]._original_module + else: + rec.model_original_module = "" + + @api.depends("model", "type") + def _compute_model_type(self): + for rec in self: + if rec.type == "model": + model = self.env[rec.model] + if model._auto and model._transient: + rec.model_type = "transient" + elif model._auto: + rec.model_type = "" + elif not model._auto and model._abstract: + rec.model_type = "abstract" + else: + rec.model_type = "sql_view" + else: + rec.model_type = "" + + @api.model + def field_dump(self): + keys = [ + "attachment", + "module", + "mode", + "model", + "field", + "type", + "isfunction", + "isproperty", + "isrelated", + "relation", + "required", + "stored", + "selection_keys", + "hasdefault", + "table", + "_inherits", + "_order", + ] + + template = {x: False for x in keys} + data = [] + for record in self.search([("type", "=", "field")]): + repre = template.copy() + repre.update( + { + "module": record.module, + "model": record.model, + "field": record.field, + "mode": record.mode, + } + ) + repre.update({x.name: x.value for x in record.attribute_ids}) + if repre["table"]: + repre.update( + { + "column1": self.env[repre["model"]] + ._fields[repre["field"]] + .column1, + "column2": self.env[repre["model"]] + ._fields[repre["field"]] + .column2, + } + ) + data.append(repre) + return data + + @api.model + def list_modules(self): + """Return the set of covered modules""" + self.env.cr.execute( + """SELECT DISTINCT(module) FROM upgrade_record + ORDER BY module""" + ) + return [module for (module,) in self.env.cr.fetchall()] + + @staticmethod + def _read_manifest(addon_dir): + for manifest_name in MANIFEST_NAMES: + if os.access(os.path.join(addon_dir, manifest_name), os.R_OK): + with open(os.path.join(addon_dir, manifest_name)) as f: + manifest_string = f.read() + return ast.literal_eval(manifest_string) + raise ValidationError( + _("No manifest found in %(addon_dir)s") % {"addon_dir": addon_dir} + ) + + @api.model + def get_xml_records(self, module): + """Return all XML records from the given module""" + addon_dir = get_module_path(module) + manifest = self._read_manifest(addon_dir) + # The order of the keys are important. + # Load files in the same order as in + # module/loading.py:load_module_graph + files = [] + for key in ["init_xml", "update_xml", "data"]: + if not manifest.get(key): + continue + for xml_file in manifest[key]: + if not xml_file.lower().endswith(".xml"): + continue + parts = xml_file.split("/") + try: + with open(os.path.join(addon_dir, *parts)) as xml_handle: + files.append(xml_handle.read()) + except UnicodeDecodeError: + _logger.warning( + "Encoding error: Unable to read %s", + os.path.join(addon_dir, *parts), + ) + continue + return files diff --git a/upgrade_analysis/odoo_patch/__init__.py b/upgrade_analysis/odoo_patch/__init__.py new file mode 100644 index 00000000000..4a183795658 --- /dev/null +++ b/upgrade_analysis/odoo_patch/__init__.py @@ -0,0 +1,3 @@ +from . import addons +from . import odoo +from . import odoo_patch diff --git a/upgrade_analysis/odoo_patch/addons/__init__.py b/upgrade_analysis/odoo_patch/addons/__init__.py new file mode 100644 index 00000000000..3247dc7b6b5 --- /dev/null +++ b/upgrade_analysis/odoo_patch/addons/__init__.py @@ -0,0 +1,3 @@ +from . import mrp +from . import point_of_sale +from . import stock diff --git a/upgrade_analysis/odoo_patch/addons/mrp/__init__.py b/upgrade_analysis/odoo_patch/addons/mrp/__init__.py new file mode 100644 index 00000000000..e795cbc3313 --- /dev/null +++ b/upgrade_analysis/odoo_patch/addons/mrp/__init__.py @@ -0,0 +1,11 @@ +# flake8: noqa: B902 +from odoo.addons import mrp +from ...odoo_patch import OdooPatch + + +class PreInitHookPatch(OdooPatch): + target = mrp + method_names = ["_pre_init_mrp"] + + def _pre_init_mrp(cr): + """Don't try to create an existing column on reinstall""" diff --git a/upgrade_analysis/odoo_patch/addons/point_of_sale/__init__.py b/upgrade_analysis/odoo_patch/addons/point_of_sale/__init__.py new file mode 100644 index 00000000000..f363b810609 --- /dev/null +++ b/upgrade_analysis/odoo_patch/addons/point_of_sale/__init__.py @@ -0,0 +1,13 @@ +# flake8: noqa: B902 +from odoo import api +from odoo.addons.point_of_sale.models import pos_config +from ...odoo_patch import OdooPatch + + +class PreInitHookPatch(OdooPatch): + target = pos_config.PosConfig + method_names = ["post_install_pos_localisation"] + + @api.model + def post_install_pos_localisation(cr): + """Do not configure twice pos_localisation""" diff --git a/upgrade_analysis/odoo_patch/addons/stock/__init__.py b/upgrade_analysis/odoo_patch/addons/stock/__init__.py new file mode 100644 index 00000000000..3ff1cb0a26e --- /dev/null +++ b/upgrade_analysis/odoo_patch/addons/stock/__init__.py @@ -0,0 +1,11 @@ +# flake8: noqa: B902 +from odoo.addons import stock +from ...odoo_patch import OdooPatch + + +class PreInitHookPatch(OdooPatch): + target = stock + method_names = ["pre_init_hook"] + + def pre_init_hook(cr): + """Don't unlink stock data on reinstall""" diff --git a/upgrade_analysis/odoo_patch/odoo/__init__.py b/upgrade_analysis/odoo_patch/odoo/__init__.py new file mode 100644 index 00000000000..42c35ff23d9 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/__init__.py @@ -0,0 +1,4 @@ +from . import addons +from . import models +from . import modules +from . import tools diff --git a/upgrade_analysis/odoo_patch/odoo/addons/__init__.py b/upgrade_analysis/odoo_patch/odoo/addons/__init__.py new file mode 100644 index 00000000000..0e44449338c --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/addons/__init__.py @@ -0,0 +1 @@ +from . import base diff --git a/upgrade_analysis/odoo_patch/odoo/addons/base/__init__.py b/upgrade_analysis/odoo_patch/odoo/addons/base/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/addons/base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/upgrade_analysis/odoo_patch/odoo/addons/base/models/__init__.py b/upgrade_analysis/odoo_patch/odoo/addons/base/models/__init__.py new file mode 100644 index 00000000000..413bb238014 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/addons/base/models/__init__.py @@ -0,0 +1 @@ +from . import ir_model diff --git a/upgrade_analysis/odoo_patch/odoo/addons/base/models/ir_model.py b/upgrade_analysis/odoo_patch/odoo/addons/base/models/ir_model.py new file mode 100644 index 00000000000..d7e9b9034e2 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/addons/base/models/ir_model.py @@ -0,0 +1,42 @@ +from odoo.addons.base.models import ir_model + +from ...... import upgrade_log +from .....odoo_patch import OdooPatch + + +class IrModelConstraintPatch(OdooPatch): + target = ir_model.IrModelConstraint + method_names = ["_reflect_model"] + + def _reflect_model(self, model): + """Reflect the _sql_constraints of the given model.""" + + def cons_text(txt): + return txt.lower().replace(", ", ",").replace(" (", "(") + + # map each constraint on the name of the module where it is defined + constraint_module = { + constraint[0]: cls._module + for cls in reversed(type(model).mro()) + if not getattr(cls, "pool", None) + for constraint in getattr(cls, "_local_sql_constraints", ()) + } + + data_list = [] + for key, definition, message in model._sql_constraints: + conname = f"{model._table}_{key}" + module = constraint_module.get(key) + record = self._reflect_constraint( + model, conname, "u", cons_text(definition), module, message + ) + if record: + xml_id = f"{module}.constraint_{conname}" + data_list.append(dict(xml_id=xml_id, record=record)) + if data_list: + self.env["ir.model.data"]._update_xmlids(data_list) + # Begin OpenUpgrade addition + for data in data_list: + xml_id = data.get("xml_id") + module = xml_id.split(".")[0] + upgrade_log.log_xml_id(self.env.cr, module, xml_id) + # End OpenUpgrade addition diff --git a/upgrade_analysis/odoo_patch/odoo/models.py b/upgrade_analysis/odoo_patch/odoo/models.py new file mode 100644 index 00000000000..ed5c662b5db --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/models.py @@ -0,0 +1,23 @@ +from odoo import api, models + +from ... import upgrade_log +from ..odoo_patch import OdooPatch + + +class BaseModelPatch(OdooPatch): + target = models.BaseModel + method_names = ["_convert_records"] + + @api.model + def _convert_records(self, records, log=lambda a: None): + """Log data ids that are imported with `load`""" + current_module = self.env.context["module"] + for res in BaseModelPatch._convert_records._original_method( + self, records, log=log + ): + _id, xid, _record, _info = res + if xid: + xid = xid if "." in xid else f"{current_module}.{xid}" + upgrade_log.log_xml_id(self.env.cr, current_module, xid) + + yield res diff --git a/upgrade_analysis/odoo_patch/odoo/modules/__init__.py b/upgrade_analysis/odoo_patch/odoo/modules/__init__.py new file mode 100644 index 00000000000..7246323f930 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/modules/__init__.py @@ -0,0 +1 @@ +from . import registry diff --git a/upgrade_analysis/odoo_patch/odoo/modules/registry.py b/upgrade_analysis/odoo_patch/odoo/modules/registry.py new file mode 100644 index 00000000000..c71f2ce2055 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/modules/registry.py @@ -0,0 +1,34 @@ +import logging +from threading import current_thread + +from odoo import SUPERUSER_ID, api +from odoo.modules.registry import Registry + +from .... import upgrade_log +from ...odoo_patch import OdooPatch + +_logger = logging.getLogger(__name__) + + +class RegistryPatch(OdooPatch): + target = Registry + method_names = ["init_models"] + + def init_models(self, cr, model_names, context, install=True): + if "module" in context: + module_name = context["module"] + _logger.debug("Logging models of module %s", module_name) + upg_registry = current_thread()._upgrade_registry + local_registry = {} + env = api.Environment(cr, SUPERUSER_ID, {}) + for model in env.values(): + if not model._auto: + continue + upgrade_log.log_model(model, local_registry) + upgrade_log.compare_registries( + cr, context["module"], upg_registry, local_registry + ) + + return RegistryPatch.init_models._original_method( + self, cr, model_names, context, install=install + ) diff --git a/upgrade_analysis/odoo_patch/odoo/tools/__init__.py b/upgrade_analysis/odoo_patch/odoo/tools/__init__.py new file mode 100644 index 00000000000..99a9527ec5a --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/tools/__init__.py @@ -0,0 +1 @@ +from . import convert diff --git a/upgrade_analysis/odoo_patch/odoo/tools/convert.py b/upgrade_analysis/odoo_patch/odoo/tools/convert.py new file mode 100644 index 00000000000..0912c6d2674 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/tools/convert.py @@ -0,0 +1,14 @@ +from odoo.tools.convert import xml_import + +from .... import upgrade_log +from ...odoo_patch import OdooPatch + + +class XMLImportPatch(OdooPatch): + target = xml_import + method_names = ["_test_xml_id"] + + def _test_xml_id(self, xml_id): + res = XMLImportPatch._test_xml_id._original_method(self, xml_id) + upgrade_log.log_xml_id(self.env.cr, self.module, xml_id) + return res diff --git a/upgrade_analysis/odoo_patch/odoo_patch.py b/upgrade_analysis/odoo_patch/odoo_patch.py new file mode 100644 index 00000000000..82d43b96666 --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo_patch.py @@ -0,0 +1,61 @@ +import logging + +_logger = logging.getLogger(__name__) + + +class OdooPatch: + """Simple mechanism to apply a collection of monkeypatches using a + context manager. + + Classes can register their monkeypatches by inheriting from this class. + They need to define a `target` member, referring to the object or module + that needs to be patched, and a list `method_names`. They also need to + redefine those methods under the same name. + + The original method is made available on the new method as + `_original_method`. + + Example: + + ``` + from odoo import api + from odoo.addons.some_module.models.my_model import MyModel + + class MyModelPatch(OdooPatch): + target = MyModel + method_names = ['do_something'] + + @api.model + def do_something(self): + res = MyModelPatch.do_something._original_method() + ... + return res + ``` + + Usage: + + ``` + with OdooPatch(): + do_something() + ``` + """ + + def __enter__(self): + for cls in OdooPatch.__subclasses__(): + for method_name in cls.method_names: + method = getattr(cls, method_name) + method._original_method = getattr(cls.target, method_name) + setattr(cls.target, method_name, method) + + def __exit__(self, exc_type, exc_value, tb): + for cls in OdooPatch.__subclasses__(): + for method_name in cls.method_names: + method = getattr(cls.target, method_name) + if hasattr(method, "_original_method"): + setattr(cls.target, method_name, method._original_method) + else: + _logger.warning( + "_original_method not found on method %s of class %s", + method_name, + cls.target, + ) diff --git a/upgrade_analysis/pyproject.toml b/upgrade_analysis/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/upgrade_analysis/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/upgrade_analysis/readme/CONTRIBUTORS.md b/upgrade_analysis/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..fea05c677e8 --- /dev/null +++ b/upgrade_analysis/readme/CONTRIBUTORS.md @@ -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.md b/upgrade_analysis/readme/DESCRIPTION.md new file mode 100644 index 00000000000..ace8cc31b4f --- /dev/null +++ b/upgrade_analysis/readme/DESCRIPTION.md @@ -0,0 +1,12 @@ +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: + + +This module is just a tool, a continuation of the old +openupgrade_records in OpenUpgrade in previous versions. It's not +recommended to have this module in a production database. diff --git a/upgrade_analysis/readme/ROADMAP.md b/upgrade_analysis/readme/ROADMAP.md new file mode 100644 index 00000000000..8b6ba6ece2c --- /dev/null +++ b/upgrade_analysis/readme/ROADMAP.md @@ -0,0 +1,5 @@ +- Log removed modules in the module that owned them (#468) +- Detect renamed many2many tables (#213) +- Make sure that the `migration_analysis.txt` file is always generated + in all cases. (See: + ) diff --git a/upgrade_analysis/readme/USAGE.md b/upgrade_analysis/readme/USAGE.md new file mode 100644 index 00000000000..34452556f22 --- /dev/null +++ b/upgrade_analysis/readme/USAGE.md @@ -0,0 +1 @@ +[Usage instructions](https://oca.github.io/OpenUpgrade/analyse.html) diff --git a/upgrade_analysis/security/ir.model.access.csv b/upgrade_analysis/security/ir.model.access.csv new file mode 100644 index 00000000000..1025e9e721a --- /dev/null +++ b/upgrade_analysis/security/ir.model.access.csv @@ -0,0 +1,7 @@ +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,access_upgrade_analysis,model_upgrade_analysis,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/static/description/icon.png b/upgrade_analysis/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/upgrade_analysis/static/description/icon.png differ diff --git a/upgrade_analysis/static/description/index.html b/upgrade_analysis/static/description/index.html new file mode 100644 index 00000000000..3f17e8830cc --- /dev/null +++ b/upgrade_analysis/static/description/index.html @@ -0,0 +1,456 @@ + + + + + +Upgrade Analysis + + + +
+

Upgrade Analysis

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

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

+

This module is just a tool, a continuation of the old +openupgrade_records in OpenUpgrade in previous versions. It’s not +recommended to have this module in a production database.

+

Table of contents

+ + +
+

Known issues / Roadmap

+ +
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Therp BV
  • +
  • Opener B.V.
  • +
  • GRAP
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

Current maintainers:

+

StefanRijnhart legalsylvain

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/upgrade_analysis/static/src/module_coverage_template.rst.mako b/upgrade_analysis/static/src/module_coverage_template.rst.mako new file mode 100644 index 00000000000..1f7efefc5f6 --- /dev/null +++ b/upgrade_analysis/static/src/module_coverage_template.rst.mako @@ -0,0 +1,12 @@ +Module coverage ${start_version} -> ${end_version} +============================ + +.. include:: coverage_legend.rst + ++---------------------------------------------------+----------------------+-------------------------------------------------+ +| Module | Status + Extra Information | ++===================================================+======================+=================================================+ +% for module, extra_information in module_descriptions.items(): +|${module}| |${extra_information}| ++---------------------------------------------------+----------------------+-------------------------------------------------+ +% endfor diff --git a/upgrade_analysis/tests/__init__.py b/upgrade_analysis/tests/__init__.py new file mode 100644 index 00000000000..d9b96c4fa5a --- /dev/null +++ b/upgrade_analysis/tests/__init__.py @@ -0,0 +1 @@ +from . import test_module diff --git a/upgrade_analysis/tests/test_module.py b/upgrade_analysis/tests/test_module.py new file mode 100644 index 00000000000..fbb54e50592 --- /dev/null +++ b/upgrade_analysis/tests/test_module.py @@ -0,0 +1,46 @@ +from odoo.tests import common, tagged + + +@tagged("post_install", "-at_install") +class TestUpgradeAnalysis(common.TransactionCase): + def setUp(self): + super().setUp() + self.IrModuleModule = self.env["ir.module.module"] + self.product_module = self.IrModuleModule.search([("name", "=", "product")]) + self.sale_module = self.IrModuleModule.search([("name", "=", "sale")]) + self.upgrade_analysis = self.IrModuleModule.search( + [("name", "=", "upgrade_analysis")] + ) + + def test_upgrade_install_wizard(self): + InstallWizard = self.env["upgrade.install.wizard"] + wizard = InstallWizard.create({}) + + wizard.select_odoo_modules() + self.assertTrue( + self.product_module.id in wizard.module_ids.ids, + "Select Odoo module should select 'product' module", + ) + + wizard.select_oca_modules() + self.assertTrue( + self.upgrade_analysis.id in wizard.module_ids.ids, + "Select OCA module should select 'upgrade_analysis' module", + ) + + wizard.select_other_modules() + self.assertFalse( + self.product_module.id in wizard.module_ids.ids, + "Select Other module should not select 'product' module", + ) + + wizard.unselect_modules() + self.assertEqual( + wizard.module_ids.ids, [], "Unselect module should clear the selection" + ) + # For the time being, tests doens't call install_modules() function + # because installing module in a test context will execute the test + # of the installed modules, raising finally an error: + + # TypeError: Many2many fields ir.actions.server.partner_ids and + # ir.actions.server.partner_ids use the same table and columns diff --git a/upgrade_analysis/upgrade_log.py b/upgrade_analysis/upgrade_log.py new file mode 100644 index 00000000000..a0df0dc62d4 --- /dev/null +++ b/upgrade_analysis/upgrade_log.py @@ -0,0 +1,235 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from openupgradelib.openupgrade_tools import table_exists + +from odoo import models + +_logger = logging.getLogger(__name__) + + +def get_record_id(cr, module, model, field, mode): + """ + OpenUpgrade: get or create the id from the record table matching + the key parameter values + """ + cr.execute( + "SELECT id FROM upgrade_record " + "WHERE module = %s AND model = %s AND " + "field = %s AND mode = %s AND type = %s", + (module, model, field, mode, "field"), + ) + record = cr.fetchone() + if record: + return record[0] + cr.execute( + "INSERT INTO upgrade_record " + "(create_date, module, model, field, mode, type) " + "VALUES (NOW() AT TIME ZONE 'UTC', %s, %s, %s, %s, %s)", + (module, model, field, mode, "field"), + ) + cr.execute( + "SELECT id FROM upgrade_record " + "WHERE module = %s AND model = %s AND " + "field = %s AND mode = %s AND type = %s", + (module, model, field, mode, "field"), + ) + return cr.fetchone()[0] + + +def compare_registries(cr, module, registry, local_registry): + """ + OpenUpgrade: Compare the local registry with the global registry, + log any differences and merge the local registry with + the global one. + """ + if not table_exists(cr, "upgrade_record"): + return + for model, flds in local_registry.items(): + registry.setdefault(model, {}) + for field, attributes in flds.items(): + old_field = registry[model].setdefault(field, {}) + mode = old_field and "modify" or "create" + record_id = False + for key, value in attributes.items(): + if key not in old_field or old_field[key] != value: + if not record_id: + record_id = get_record_id(cr, module, model, field, mode) + cr.execute( + "SELECT id FROM upgrade_attribute " + "WHERE name = %s AND value = %s AND " + "record_id = %s", + (key, value, record_id), + ) + if not cr.fetchone(): + cr.execute( + "INSERT INTO upgrade_attribute " + "(create_date, name, value, record_id) " + "VALUES (NOW() AT TIME ZONE 'UTC', %s, %s, %s)", + (key, value, record_id), + ) + old_field[key] = value + + +def hasdefault(field): + """Return a representation of the field's default method. + + The default method is only meaningful if the field is a regular read/write + field with a `default` method or a `compute` method. + + Note that Odoo fields accept a literal value as a `default` attribute + this value is wrapped in a lambda expression in odoo/fields.py: + https://github.com/odoo/odoo/blob/7eeba9d/odoo/fields.py#L484-L487 + """ + if ( + not field.readonly # It's not a proper computed field + and not field.inverse # It's not a field that delegates their data + and not isrelated(field) # It's not an (unstored) related field. + ): + if field.default: + return "default" + if field.compute: + return "compute" + return "" + + +def isfunction(field): + if ( + field.compute + and (field.readonly or field.inverse) + and not field.related + and not field.company_dependent + ): + return "function" + return "" + + +def isproperty(field): + if field.company_dependent: + return "property" + return "" + + +def isrelated(field): + if field.related: + return "related" + return "" + + +def _get_relation(field): + if field.type in ("many2many", "many2one", "one2many"): + return field.comodel_name + elif field.type == "many2one_reference": + return field.model_field + else: + return "" + + +def log_model(model, local_registry): + """ + OpenUpgrade: Store the characteristics of the BaseModel and its fields + in the local registry, so that we can compare changes with the + main registry + """ + + if not model._name: + return + + typemap = {"monetary": "float"} + + # persistent models only + if isinstance(model, models.TransientModel): + return + + model_registry = local_registry.setdefault(model._name, {}) + if model._inherits: + model_registry["_inherits"] = {"_inherits": str(model._inherits)} + model_registry["_order"] = {"_order": model._order} + for fieldname, field in model._fields.items(): + properties = { + "type": typemap.get(field.type, field.type), + "isfunction": isfunction(field), + "isproperty": isproperty(field), + "isrelated": isrelated(field), + "relation": _get_relation(field), + "table": field.relation if field.type == "many2many" else "", + "required": field.required and "required" or "", + "stored": field.store and "stored" or "", + "selection_keys": "", + "hasdefault": hasdefault(field), + } + if field.type == "selection": + if isinstance(field.selection, tuple | list): + properties["selection_keys"] = str( + sorted(x[0] for x in field.selection) + ) + else: + properties["selection_keys"] = "function" + elif field.type == "binary": + properties["attachment"] = str(getattr(field, "attachment", False)) + for key, value in properties.items(): + if value: + model_registry.setdefault(fieldname, {})[key] = value + + +def log_xml_id(cr, module, xml_id): + """ + Log xml_ids at load time in the records table. + Called from: + - tools/convert.py:xml_import._test_xml_id() + - odoo/models.py:BaseModel._convert_records() + - odoo/addons/base/models/ir_model.py:IrModelConstraint._reflect_model() + + # Catcha's + - The module needs to be loaded with 'init', or the calling method + won't be called. This can be brought about by installing the + module or updating the 'state' field of the module to 'to install' + or call the server with '--init ' and the database argument. + + - Do you get the right results immediately when installing the module? + No, sorry. This method retrieves the model from the ir_model_table, but + when the xml id is encountered for the first time, this method is called + before the item is present in this table. Therefore, you will not + get any meaningful results until the *second* time that you 'init' + the module. + + - The good news is that the upgrade_analysis module that comes + with this distribution allows you to deal with all of this with + one click on the menu item Settings -> Customizations -> + Database Structure -> OpenUpgrade -> Generate Records + + - You cannot reinitialize the modules in your production database + and expect to keep working on it happily ever after. Do not perform + this routine on your production database. + + :param module: The module that contains the xml_id + :param xml_id: the xml_id, with or without 'module.' prefix + """ + if not table_exists(cr, "upgrade_record"): + return + if "." not in xml_id: + xml_id = f"{module}.{xml_id}" + cr.execute( + "SELECT model FROM ir_model_data " "WHERE module = %s AND name = %s", + xml_id.split("."), + ) + record = cr.fetchone() + if not record: + _logger.warning("Cannot find xml_id %s", xml_id) + return + else: + cr.execute( + "SELECT id FROM upgrade_record " + "WHERE module=%s AND model=%s AND name=%s AND type=%s", + (module, record[0], xml_id, "xmlid"), + ) + if not cr.fetchone(): + cr.execute( + "INSERT INTO upgrade_record " + "(create_date, module, model, name, type) " + "values(NOW() AT TIME ZONE 'UTC', %s, %s, %s, %s)", + (module, record[0], xml_id, "xmlid"), + ) diff --git a/upgrade_analysis/views/menu.xml b/upgrade_analysis/views/menu.xml new file mode 100644 index 00000000000..e12c752369b --- /dev/null +++ b/upgrade_analysis/views/menu.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/upgrade_analysis/views/view_upgrade_analysis.xml b/upgrade_analysis/views/view_upgrade_analysis.xml new file mode 100644 index 00000000000..8ec3967878f --- /dev/null +++ b/upgrade_analysis/views/view_upgrade_analysis.xml @@ -0,0 +1,64 @@ + + + + + upgrade.analysis + + + + + + + + + + + upgrade.analysis + +
+
+ +
+ + + + + + + + + + + + +
+
+
+ + + Upgrade Analyses + ir.actions.act_window + upgrade.analysis + + + + +
diff --git a/upgrade_analysis/views/view_upgrade_comparison_config.xml b/upgrade_analysis/views/view_upgrade_comparison_config.xml new file mode 100644 index 00000000000..475d6e1863c --- /dev/null +++ b/upgrade_analysis/views/view_upgrade_comparison_config.xml @@ -0,0 +1,79 @@ + + + + + upgrade.comparison.config + + + + + + + + + + + + upgrade.comparison.config + +
+
+
+ +
+ +
+ + + + + + + + + +