From 1be1ccf9debf87673c44e53f2b433f16746559fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Ra=C3=AFch?= Date: Wed, 15 Jul 2026 12:35:17 +0200 Subject: [PATCH] [FIX] upgrade_analysis: reset discarded fields to their default, not falsy value When the previous version set a field explicitly and the new version omits it, _get_xml_diff generated eval="False" using field.falsy_value. This is wrong for fields whose default is truthy (e.g. ir.rule.perm_read/write/create/unlink or active): a fresh install applies the field's default, not its falsy value. The generated noupdate_changes.xml then mismatched a clean install and could even violate model constraints (e.g. ir.rule's _no_access_rights CHECK), breaking the migration. Use model.default_get() to reset omitted fields to the value a fresh install would give them, falling back to falsy_value when there is no default or it is not a scalar. --- upgrade_analysis/models/upgrade_analysis.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/upgrade_analysis/models/upgrade_analysis.py b/upgrade_analysis/models/upgrade_analysis.py index f60ca97afe6..23b42785911 100644 --- a/upgrade_analysis/models/upgrade_analysis.py +++ b/upgrade_analysis/models/upgrade_analysis.py @@ -370,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))