diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..7a9599183 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# generated from manifests external_dependencies +dataclasses +mako +odoorpc +openupgradelib diff --git a/setup/upgrade_analysis/odoo/addons/upgrade_analysis b/setup/upgrade_analysis/odoo/addons/upgrade_analysis new file mode 120000 index 000000000..363ae3c1e --- /dev/null +++ b/setup/upgrade_analysis/odoo/addons/upgrade_analysis @@ -0,0 +1 @@ +../../../../upgrade_analysis \ No newline at end of file diff --git a/setup/upgrade_analysis/setup.py b/setup/upgrade_analysis/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/upgrade_analysis/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/upgrade_analysis/README.rst b/upgrade_analysis/README.rst new file mode 100644 index 000000000..62b7497d6 --- /dev/null +++ b/upgrade_analysis/README.rst @@ -0,0 +1,106 @@ +================ +Upgrade Analysis +================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/16.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-16-0/server-tools-16-0-upgrade_analysis + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/149/16.0 + :alt: Try me on Runbot + +|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 smashing 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 000000000..172ae9a25 --- /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 000000000..3aeecb5ef --- /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": "16.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 000000000..ea5007219 --- /dev/null +++ b/upgrade_analysis/blacklist.py @@ -0,0 +1,10 @@ +BLACKLIST_MODULES = [] + +# 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 000000000..e43150af6 --- /dev/null +++ b/upgrade_analysis/compare.py @@ -0,0 +1,547 @@ +# 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, 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 = "{} is now '{}' ('{}')".format(field, new[field], old[field]) + if field in ("column1", "column2"): + text += " [%s]" % old["table"] + if field == "relation": + text += " [nothing to do]" + reprs[module_map(old["module"])].append("{}: {}".format(fullrepr, text)) + if field == "module": + text = "previously in module %s" % old[field] + fullrepr = "{:<12} / {:<24} / {:<30}".format( + new["module"], old["model"], fieldrepr + ) + reprs[module_map(new["module"])].append("{}: {}".format(fullrepr, text)) + + +def report_generic(new, old, attrs, reprs): + for attr in attrs: + if attr == "required": + if old[attr] != new["required"] and new["required"]: + 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 = "DEL %(model)s: %(name)s" % entry + if "moved" in entry: + content += " [moved to %(moved)s module]" % entry + elif "renamed" in entry: + content += " [renamed to %(renamed)s module]" % entry + elif "new" in entry: + content = "NEW %(model)s: %(name)s" % entry + if "moved" in entry: + content += " [moved from %(moved)s module]" % entry + elif "renamed" in entry: + content += " [renamed from %(renamed)s module]" % entry + if "old" not in entry and "new" not in entry: + content = "%(model)s: %(name)s" % entry + if entry["domain"]: + content += " (deleted domain)" + if entry["definition"]: + content += " (changed definition: %(definition)s)" % entry + 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 = "obsolete model %s" % model + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[module_map(column["module"])].append(text) + reprs["general"].append( + "obsolete model %s [module %s]" + % (model, module_map(column["module"])) + ) + else: + moved_module = "" + if module_map(column["module"]) != new_models[model_map(model)]: + moved_module = " in module %s" % new_models[model_map(model)] + text = "obsolete model {} (renamed to {}{})".format( + model, + model_map(model), + moved_module, + ) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[module_map(column["module"])].append(text) + reprs["general"].append( + "obsolete model %s (renamed to %s) [module %s]" + % (model, model_map(model), module_map(column["module"])) + ) + else: + if module_map(column["module"]) != new_models[model]: + text = "model {} (moved to {})".format(model, new_models[model]) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[module_map(column["module"])].append(text) + text = "model {} (moved from {})".format(model, old_models[model]) + if column["model_type"]: + text += " [%s]" % column["model_type"] + + for column in copy.copy(new_records): + model = column["model"] + if model in new_models: + if model not in old_models: + if inv_model_map(model) not in old_models: + text = "new model %s" % model + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[column["module"]].append(text) + reprs["general"].append( + "new model {} [module {}]".format(model, column["module"]) + ) + else: + moved_module = "" + if column["module"] != module_map(old_models[inv_model_map(model)]): + moved_module = ( + " in module %s" % old_models[inv_model_map(model)] + ) + text = "new model {} (renamed from {}{})".format( + model, + inv_model_map(model), + moved_module, + ) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[column["module"]].append(text) + reprs["general"].append( + "new model %s (renamed from %s) [module %s]" + % (model, inv_model_map(model), column["module"]) + ) + else: + if column["module"] != module_map(old_models[model]): + text = "model {} (moved from {})".format(model, old_models[model]) + if column["model_type"]: + text += " [%s]" % column["model_type"] + reprs[column["module"]].append(text) + return reprs diff --git a/upgrade_analysis/i18n/es_AR.po b/upgrade_analysis/i18n/es_AR.po new file mode 100644 index 000000000..73cf7b78e --- /dev/null +++ b/upgrade_analysis/i18n/es_AR.po @@ -0,0 +1,535 @@ +# 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 +#: 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 +#: 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 +#: 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 +#: 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_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 +#: 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 +#: 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 000000000..4bc6821e0 --- /dev/null +++ b/upgrade_analysis/i18n/fr.po @@ -0,0 +1,520 @@ +# 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 +#: 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 +#: 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 +#: 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 +#: 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_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 +#: 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 +#: 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 000000000..0fdfaae71 --- /dev/null +++ b/upgrade_analysis/i18n/upgrade_analysis.pot @@ -0,0 +1,517 @@ +# 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" +"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 +#: 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 +#: 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 +#: 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 +#: 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_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 +#: 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 +#: 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 000000000..7a9c9233d --- /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 000000000..38edbbac8 --- /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 000000000..8d0e53aa5 --- /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=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 os.error: + 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(49, " ")] = 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 000000000..15c934466 --- /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 000000000..649866379 --- /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 000000000..f62083036 --- /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), "r") 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), "r") 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 000000000..4a1837956 --- /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 000000000..3247dc7b6 --- /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 000000000..e795cbc33 --- /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 000000000..f363b8106 --- /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 000000000..3ff1cb0a2 --- /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 000000000..42c35ff23 --- /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 000000000..0e4444933 --- /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 000000000..0650744f6 --- /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 000000000..413bb2380 --- /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 000000000..68cab246a --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/addons/base/models/ir_model.py @@ -0,0 +1,40 @@ +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 = "%s_%s" % (model._table, key) + module = constraint_module.get(key) + record = self._reflect_constraint( + model, conname, "u", cons_text(definition), module, message + ) + if record: + xml_id = "%s.constraint_%s" % (module, conname) + data_list.append(dict(xml_id=xml_id, record=record)) + + self.env["ir.model.data"]._update_xmlids(data_list) + 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) diff --git a/upgrade_analysis/odoo_patch/odoo/models.py b/upgrade_analysis/odoo_patch/odoo/models.py new file mode 100644 index 000000000..51de8037b --- /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 "{}.{}".format(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 000000000..7246323f9 --- /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 000000000..017da063f --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo/modules/registry.py @@ -0,0 +1,33 @@ +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): + 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 000000000..99a9527ec --- /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 000000000..0912c6d26 --- /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 000000000..f2ad6f73c --- /dev/null +++ b/upgrade_analysis/odoo_patch/odoo_patch.py @@ -0,0 +1,61 @@ +import logging + +_logger = logging.getLogger(__name__) + + +class OdooPatch(object): + """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/readme/CONTRIBUTORS.rst b/upgrade_analysis/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..b8c8505e9 --- /dev/null +++ b/upgrade_analysis/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ +* Stefan Rijnhart +* Holger Brunn +* Pedro M. Baeza +* Ferdinand Gassauer +* Florent Xicluna +* Miquel Raïch +* Sylvain LE GAL diff --git a/upgrade_analysis/readme/DESCRIPTION.rst b/upgrade_analysis/readme/DESCRIPTION.rst new file mode 100644 index 000000000..1b12e675f --- /dev/null +++ b/upgrade_analysis/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +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. diff --git a/upgrade_analysis/readme/ROADMAP.rst b/upgrade_analysis/readme/ROADMAP.rst new file mode 100644 index 000000000..48d1f8d94 --- /dev/null +++ b/upgrade_analysis/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* 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) diff --git a/upgrade_analysis/readme/USAGE.rst b/upgrade_analysis/readme/USAGE.rst new file mode 100644 index 000000000..bd341e59d --- /dev/null +++ b/upgrade_analysis/readme/USAGE.rst @@ -0,0 +1 @@ +`Usage instructions `_ diff --git a/upgrade_analysis/security/ir.model.access.csv b/upgrade_analysis/security/ir.model.access.csv new file mode 100644 index 000000000..1025e9e72 --- /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 000000000..3a0328b51 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 000000000..89fee6a45 --- /dev/null +++ b/upgrade_analysis/static/description/index.html @@ -0,0 +1,444 @@ + + + + + + +Upgrade Analysis + + + +
+

Upgrade Analysis

+ + +

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

+

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 smashing 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 000000000..ab598ae4d --- /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 000000000..d9b96c4fa --- /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 000000000..fbb54e505 --- /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 000000000..429c33f19 --- /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 = "{}.{}".format(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 000000000..e12c75236 --- /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 000000000..c664fa70d --- /dev/null +++ b/upgrade_analysis/views/view_upgrade_analysis.xml @@ -0,0 +1,75 @@ + + + + + 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 000000000..475d6e186 --- /dev/null +++ b/upgrade_analysis/views/view_upgrade_comparison_config.xml @@ -0,0 +1,79 @@ + + + + + upgrade.comparison.config + + + + + + + + + + + + upgrade.comparison.config + +
+
+
+ +
+ +
+ + + + + + + + + +