diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index 71fbb699e13..9118e3ce48e 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -950,6 +950,182 @@ class TestTemplating(ViewCase):
initial.get('data-oe-xpath'),
"The node's xpath position should be correct")
+ def test_branding_inherit_multi_replace_node(self):
+ view1 = self.View.create({
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+
+
+
+
+
+ """
+ })
+ view2 = self.View.create({
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view1.id,
+ 'arch': """
+
+
+
+
+
+
+ """
+ })
+ self.View.create({ # Inherit from the child view and target the added element
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view2.id,
+ 'arch': """
+
+
+
+
+
+ """
+ })
+
+ arch_string = view1.with_context(inherit_branding=True).get_combined_arch()
+ arch = etree.fromstring(arch_string)
+ self.View.distribute_branding(arch)
+
+ # Check if the replacement inside the child view did not mess up the
+ # branding of elements in that child view
+ [initial] = arch.xpath('//world[hasclass("z")]')
+ self.assertEqual(
+ '/data/xpath/world[2]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
+ # Check if the replacement of the first worlds did not mess up the
+ # branding of the last world.
+ [initial] = arch.xpath('//world[hasclass("c")]')
+ self.assertEqual(
+ '/hello[1]/world[3]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
+ def test_branding_inherit_multi_replace_node2(self):
+ view1 = self.View.create({
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+
+
+
+
+
+ """
+ })
+ self.View.create({
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view1.id,
+ 'arch': """
+
+
+
+
+
+
+ """
+ })
+ self.View.create({ # Inherit from the parent view but actually target
+ # the element added by the first child view
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view1.id,
+ 'arch': """
+
+
+
+
+
+ """
+ })
+
+ arch_string = view1.with_context(inherit_branding=True).get_combined_arch()
+ arch = etree.fromstring(arch_string)
+ self.View.distribute_branding(arch)
+
+ # Check if the replacement inside the child view did not mess up the
+ # branding of elements in that child view
+ [initial] = arch.xpath('//world[hasclass("z")]')
+ self.assertEqual(
+ '/data/xpath/world[2]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
+ # Check if the replacement of the first worlds did not mess up the
+ # branding of the last world.
+ [initial] = arch.xpath('//world[hasclass("c")]')
+ self.assertEqual(
+ '/hello[1]/world[3]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
+ def test_branding_inherit_remove_added_from_inheritance(self):
+ view1 = self.View.create({
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+
+
+
+
+ """
+ })
+ view2 = self.View.create({
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view1.id,
+ # Note: class="x" instead of t-field="x" in this arch, should lead
+ # to the same result that this test is ensuring but was actually
+ # a different case in old stable versions.
+ 'arch': """
+
+
+
+
+
+
+ """
+ })
+ self.View.create({ # Inherit from the child view and target the added element
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view2.id,
+ 'arch': """
+
+
+
+ """
+ })
+
+ arch_string = view1.with_context(inherit_branding=True).get_combined_arch()
+ arch = etree.fromstring(arch_string)
+ self.View.distribute_branding(arch)
+
+ # Check if the replacement inside the child view did not mess up the
+ # branding of elements in that child view, should not be the case as
+ # that root level branding is not distributed.
+ [initial] = arch.xpath('//world[hasclass("y")]')
+ self.assertEqual(
+ '/data/xpath/world[2]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
+ # Check if the child view replacement of added nodes did not mess up
+ # the branding of last world in the parent view.
+ [initial] = arch.xpath('//world[hasclass("b")]')
+ self.assertEqual(
+ '/hello[1]/world[2]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
def test_branding_inherit_remove_node_processing_instruction(self):
view1 = self.View.create({
'name': "Base view",
diff --git a/odoo/tools/template_inheritance.py b/odoo/tools/template_inheritance.py
index 45e7d7f96f5..77f0057cd40 100644
--- a/odoo/tools/template_inheritance.py
+++ b/odoo/tools/template_inheritance.py
@@ -183,7 +183,13 @@ def apply_inheritance_specs(source, specs_tree, inherit_branding=False, pre_loca
# fix, this solution was chosen: the location is marked
# with a "ProcessingInstruction" which will not impact
# the "Element" structure of the resulting tree.
- if inherit_branding:
+ # Exception: if we happen to replace a node that already
+ # has xpath branding (root level nodes), do not mark the
+ # location of the removal as it will mess up the branding
+ # of siblings elements coming from other views, after the
+ # branding is distributed (and those processing instructions
+ # removed).
+ if inherit_branding and not node.get('data-oe-xpath'):
node.addprevious(etree.ProcessingInstruction('apply-inheritance-specs-node-removal', node.tag))
for child in spec: