diff --git a/README.md b/README.md
index 82ba5aa676e..6277f7a3db4 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ addon | version | maintainers | summary
[test_auditlog](test_auditlog/) | 19.0.1.0.0 | | Additional unit tests for Audit Log based on accounting models
[tracking_manager](tracking_manager/) | 19.0.1.0.0 |
| This module tracks all fields of a model, including one2many and many2many ones.
[tracking_manager_domain](tracking_manager_domain/) | 19.0.1.0.0 |
| This module extends the tracking manager to allow to define a domain on fields to track changes only when certain conditions apply.
-[upgrade_analysis](upgrade_analysis/) | 19.0.1.0.4 |
| Performs a difference analysis between modules installed on two different Odoo instances
+[upgrade_analysis](upgrade_analysis/) | 19.0.1.1.1 |
| Performs a difference analysis between modules installed on two different Odoo instances
[//]: # (end addons)
diff --git a/upgrade_analysis/README.rst b/upgrade_analysis/README.rst
index a04184f09af..6a77d2e0e53 100644
--- a/upgrade_analysis/README.rst
+++ b/upgrade_analysis/README.rst
@@ -11,7 +11,7 @@ Upgrade Analysis
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:89a10d9f558bdf888f62fbc7c03e763b17b3ee1c836644ed5dc752d1fafe9dd9
+ !! source digest: sha256:2f7fa794d0292655d9bb5907cd6c1dacce384ed506e9b9c7f6b0ae19ef3a2c93
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/upgrade_analysis/__manifest__.py b/upgrade_analysis/__manifest__.py
index bdb164acd0e..475441cfc32 100644
--- a/upgrade_analysis/__manifest__.py
+++ b/upgrade_analysis/__manifest__.py
@@ -5,7 +5,7 @@
"name": "Upgrade Analysis",
"summary": "Performs a difference analysis between modules"
" installed on two different Odoo instances",
- "version": "19.0.1.0.4",
+ "version": "19.0.1.1.1",
"category": "Migration",
"author": "Therp BV, Opener B.V., GRAP, Odoo Community Association (OCA)",
"maintainers": ["StefanRijnhart", "legalsylvain"],
diff --git a/upgrade_analysis/compare.py b/upgrade_analysis/compare.py
index 044e4a959ec..8760cc09834 100644
--- a/upgrade_analysis/compare.py
+++ b/upgrade_analysis/compare.py
@@ -540,7 +540,7 @@ def match(match_fields, match_type="direct"):
if entry["noupdate_switched"]:
content += " (noupdate switched)"
reprs[module_map(entry["module"])].append(content)
- return reprs
+ return reprs, moved_records, renamed_records, modified_records
def compare_model_sets(old_records, new_records):
diff --git a/upgrade_analysis/models/upgrade_analysis.py b/upgrade_analysis/models/upgrade_analysis.py
index 8eefad21ba7..23b42785911 100644
--- a/upgrade_analysis/models/upgrade_analysis.py
+++ b/upgrade_analysis/models/upgrade_analysis.py
@@ -164,7 +164,9 @@ def analyze(self):
{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)
+ res_xml, moved_xml_records, renamed_xml_records, modified_xml_records = (
+ compare.compare_xml_sets(remote_xml_records, local_xml_records)
+ )
# Retrieve model representations and compare
flds = [
@@ -270,7 +272,9 @@ def analyze(self):
)
noupdate_modules = []
try:
- noupdate_modules = self.generate_noupdate_changes()
+ noupdate_modules = self.generate_noupdate_changes(
+ moved_xml_records, renamed_xml_records, modified_xml_records
+ )
except Exception as e:
_logger.exception(f"Error generating noupdate changes: {e}")
general_log += "ERROR: error when generating noupdate changes: {e}\n"
@@ -366,11 +370,23 @@ def _get_xml_diff(
"name"
}:
# if previous version has set a field but current version
- # doesn't, set whatever the NULL value of the field is
- # (usually None)
+ # doesn't, reset it to the value a fresh install would give
+ # it: the field's default. Only fall back to the field's
+ # falsy value when there is no default. Using falsy_value
+ # unconditionally is wrong for fields whose default is truthy
+ # (e.g. ir.rule.perm_* or active) and can even produce
+ # records that violate model constraints.
model = self.env[local_record.attrib["model"]]
field = model._fields[attribs["name"]]
- eval_constant = ast.unparse(ast.Constant(field.falsy_value))
+ reset_value = model.default_get([field.name]).get(
+ field.name, field.falsy_value
+ )
+ try:
+ eval_constant = ast.unparse(ast.Constant(reset_value))
+ except ValueError:
+ # non-scalar default (e.g. x2many command list):
+ # keep the previous behaviour
+ eval_constant = ast.unparse(ast.Constant(field.falsy_value))
if eval_constant != "''":
attribs["eval"] = eval_constant
element.append(etree.Element(record_remote_dict[key].tag, attribs))
@@ -486,7 +502,9 @@ def _parse_files(self, xml_files, module_name):
return records_update, records_noupdate
- def generate_noupdate_changes(self):
+ def generate_noupdate_changes(
+ self, moved_xml_records, renamed_xml_records, modified_xml_records
+ ):
"""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()
@@ -498,6 +516,14 @@ def generate_noupdate_changes(self):
local_modules = local_record_obj.list_modules()
all_remote_modules = remote_record_obj.list_modules()
changed_modules = []
+ # {new_module: {name: previous_module}}
+ renamed_xmlids = {}
+ for renamed_xml_record in renamed_xml_records:
+ if renamed_xml_record.get("old") or not renamed_xml_record.get("new"):
+ continue
+ renamed_xmlids.setdefault(renamed_xml_record["module"], {}).update(
+ {renamed_xml_record["suffix"]: renamed_xml_record["renamed"]}
+ )
for local_module in local_modules:
remote_files = []
remote_modules = []
@@ -515,6 +541,35 @@ def generate_noupdate_changes(self):
)
remote_update.update(add_remote_update)
remote_noupdate.update(add_remote_noupdate)
+ if any(
+ renamed_from_module == remote_module
+ for renamed_from_module in renamed_xmlids.get(
+ local_module, {}
+ ).values()
+ ):
+ # if xmlids have been renamed (moved) to the current module, query
+ # their definition from the module that contained it previously
+ remote_modules.append(remote_module)
+ renamed_from_module_files = remote_record_obj.get_xml_records(
+ remote_module
+ )
+ renamed_from_module_update, renamed_from_module_noupdate = (
+ self._parse_files(renamed_from_module_files, remote_module)
+ )
+ remote_update.update(
+ {
+ name: xml
+ for name, xml in renamed_from_module_update.items()
+ if renamed_xmlids[local_module].get(name) == remote_module
+ }
+ )
+ remote_noupdate.update(
+ {
+ name: xml
+ for name, xml in renamed_from_module_noupdate.items()
+ if renamed_xmlids[local_module].get(name) == remote_module
+ }
+ )
if not remote_modules:
continue
local_files = local_record_obj.get_xml_records(local_module)
diff --git a/upgrade_analysis/static/description/index.html b/upgrade_analysis/static/description/index.html
index 65e50089f39..c6aa59edbda 100644
--- a/upgrade_analysis/static/description/index.html
+++ b/upgrade_analysis/static/description/index.html
@@ -372,7 +372,7 @@
This module provides the tool to generate the database analysis files
diff --git a/upgrade_analysis/tests/test_module.py b/upgrade_analysis/tests/test_module.py
index 65d93b8d924..f7cfd34cea3 100644
--- a/upgrade_analysis/tests/test_module.py
+++ b/upgrade_analysis/tests/test_module.py
@@ -1,4 +1,5 @@
from copy import deepcopy
+from unittest.mock import patch
from lxml import etree
@@ -152,3 +153,142 @@ def test_xml_comparison(self):
)
self.assertIn('