From e01bb0db02d000b8216c1ca4f7c260cdc5506701 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 20 Jun 2025 14:52:43 +0200 Subject: [PATCH 1/6] [FIX] util.inherit_parents Don't yield grand_parent multiple times. --- src/util/inherit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/inherit.py b/src/util/inherit.py index 5ed4570e9..67b43f361 100644 --- a/src/util/inherit.py +++ b/src/util/inherit.py @@ -121,3 +121,4 @@ def inherit_parents(cr, model, skip=(), interval="[)"): skip.add(parent) for grand_parent in inherit_parents(cr, parent, skip=skip, interval=interval): yield grand_parent + skip.add(grand_parent) From ed49e8af7243d73dae44c94ef35177c4edd2a916 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 20 Jun 2025 15:04:14 +0200 Subject: [PATCH 2/6] [ADD] util.inherits_parents Function that yield inherit*s* parent and the delegate m2o field. --- src/base/tests/test_util.py | 6 ++++++ src/util/inherit.py | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/base/tests/test_util.py b/src/base/tests/test_util.py index b0a15b456..742a37d9f 100644 --- a/src/base/tests/test_util.py +++ b/src/base/tests/test_util.py @@ -1092,6 +1092,12 @@ def test_direct_inherit_parents(self): self.assertTrue(all(inh.model == "product.product" for inh in inhs)) self.assertEqual([inh.via for inh in inhs], [None, None, "product_tmpl_id"]) + def test_inherits_parents(self): + self.assertEqual( + list(util.inherits_parents(self.env.cr, "crm.team")), + [("mail.alias", "alias_id")], + ) + class TestNamedCursors(UnitTestCase): @staticmethod diff --git a/src/util/inherit.py b/src/util/inherit.py index 67b43f361..ee359cb2c 100644 --- a/src/util/inherit.py +++ b/src/util/inherit.py @@ -95,7 +95,7 @@ def for_each_inherit(cr, model, skip=(), interval="[)"): def direct_inherit_parents(cr, model, skip=(), interval="[)"): - """Yield the *direct* inherits parents.""" + """Yield the *direct* inherit parents.""" if skip == "*": return skip = set(skip) @@ -122,3 +122,19 @@ def inherit_parents(cr, model, skip=(), interval="[)"): for grand_parent in inherit_parents(cr, parent, skip=skip, interval=interval): yield grand_parent skip.add(grand_parent) + + +def inherits_parents(cr, model, skip=(), interval="[)"): + """Yield the inherit*s* parents.""" + if skip == "*": + return + skip = set(skip) + + for parent, inh in direct_inherit_parents(cr, model, skip, interval): + if inh.via: + yield parent, inh.via + skip.add(parent) + else: + for grand_parent, fieldname in inherits_parents(cr, parent, skip, interval): + yield grand_parent, fieldname + skip.add(grand_parent) From ebbf944c52ea392ef15441aafd315e35978b6ad7 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 20 Jun 2025 15:11:00 +0200 Subject: [PATCH 3/6] [FIX] util.rename_xmlid Use the new `inherits_parents` function to get the inherits models. --- src/util/records.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/util/records.py b/src/util/records.py index a308c2b08..7a9235f14 100644 --- a/src/util/records.py +++ b/src/util/records.py @@ -33,7 +33,7 @@ ) from .inconsistencies import break_recursive_loops from .indirect_references import indirect_references -from .inherit import direct_inherit_parents, for_each_inherit +from .inherit import for_each_inherit, inherits_parents from .misc import Sentinel, chunks, parse_version, version_gte from .orm import env, flush from .pg import ( @@ -841,16 +841,15 @@ def rename_xmlid(cr, old, new, noupdate=None, on_collision="fail"): """ cr.execute(query, {"old": r"\y{}\y".format(re.escape(old)), "new": new}) - for parent_model, inh in direct_inherit_parents(cr, model): - if inh.via: - parent = parent_model.replace(".", "_") - rename_xmlid( - cr, - "{}_{}".format(old, parent), - "{}_{}".format(new, parent), - noupdate=noupdate, - on_collision=on_collision, - ) + for parent_model, _ in inherits_parents(cr, model): + parent = parent_model.replace(".", "_") + rename_xmlid( + cr, + "{}_{}".format(old, parent), + "{}_{}".format(new, parent), + noupdate=noupdate, + on_collision=on_collision, + ) return new_id return None From c789373bad6a040a73cd6041eaa894e8dc771bb7 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 20 Jun 2025 11:13:45 +0200 Subject: [PATCH 4/6] [FIX] util.update_record_from_xml As the purpose of this function is to bypass the `noupdate=1` attribute in the XML declarations, when we create the record, we should flag it as noupdate. --- src/util/records.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/records.py b/src/util/records.py index 7a9235f14..af56a76c0 100644 --- a/src/util/records.py +++ b/src/util/records.py @@ -1107,7 +1107,8 @@ def __update_record_from_xml( return else: # The xmlid doesn't already exists, nothing to reset - reset_write_metadata = noupdate = reset_translations = False + reset_write_metadata = reset_translations = False + noupdate = True fields = None write_data = None From 7d88dabf6467255450b36f480eb3a88d164e6fa4 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 20 Jun 2025 11:21:22 +0200 Subject: [PATCH 5/6] [IMP] util.force_noupdate: change the flag on parent records For models with `_inherits`, an xmlid is created for the parent record. We also need to update those xmlids. As example, when called on a `product.product` record, we now also set the flag on the xmlid of the `product.template` record. --- src/util/records.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/util/records.py b/src/util/records.py index af56a76c0..ddabc5ddc 100644 --- a/src/util/records.py +++ b/src/util/records.py @@ -901,11 +901,19 @@ def force_noupdate(cr, xmlid, noupdate=True, warn=False): WHERE module = %s AND name = %s AND noupdate != %s + RETURNING model """, [noupdate, module, name, noupdate], ) - if noupdate is False and cr.rowcount and warn: - _logger.warning("Customizations on `%s` might be lost!", xmlid) + if cr.rowcount: + if noupdate is False and warn: + _logger.warning("Customizations on `%s` might be lost!", xmlid) + + [model] = cr.fetchone() + for parent_model, _ in inherits_parents(cr, model): + parent = parent_model.replace(".", "_") + force_noupdate(cr, "{}_{}".format(xmlid, parent), noupdate=noupdate, warn=warn) + return cr.rowcount From 75b0d78db9b7016712c0f6c196545fe4808330a9 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 20 Jun 2025 11:43:45 +0200 Subject: [PATCH 6/6] [IMP] util.update_record_from_xml Set the record in noupdate=False using the `force_noupdate` function, taking adventage of its new behavior of also updating the parent records. --- src/util/records.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/util/records.py b/src/util/records.py index ddabc5ddc..91ad24049 100644 --- a/src/util/records.py +++ b/src/util/records.py @@ -1096,13 +1096,10 @@ def __update_record_from_xml( cr.execute( """ - UPDATE ir_model_data d - SET noupdate = false - FROM ir_model_data o - WHERE o.id = d.id - AND d.module = %s - AND d.name = %s - RETURNING d.model, d.res_id, o.noupdate + SELECT model, res_id, noupdate + FROM ir_model_data + WHERE module = %s + AND name = %s """, [module, name], ) @@ -1110,6 +1107,7 @@ def __update_record_from_xml( model, res_id, noupdate = cr.fetchone() if model == "ir.model": return + force_noupdate(cr, xmlid, noupdate=False) elif not force_create: _logger.warning("Record %r not found in database. Skip update.", xmlid) return