diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py
index 094de41a9e7..fefd9d9d2a3 100644
--- a/odoo/addons/base/models/ir_ui_view.py
+++ b/odoo/addons/base/models/ir_ui_view.py
@@ -1956,16 +1956,18 @@ actual arch.
# TODO: collections.Counter if remove p2.6 compat
# running index by tag type, for XPath query generation
indexes = collections.defaultdict(lambda: 0)
- for child in e.iterchildren(tag=etree.Element):
+ for child in e.iterchildren(etree.Element, etree.ProcessingInstruction):
if child.get('data-oe-xpath'):
# injected by view inheritance, skip otherwise
# generated xpath is incorrect
- # Also, if a node is known to have been replaced during applying xpath
- # increment its index to compute an accurate xpath for susequent nodes
- replaced_node_tag = child.attrib.pop('meta-oe-xpath-replacing', None)
- if replaced_node_tag:
- indexes[replaced_node_tag] += 1
self.distribute_branding(child)
+ elif child.tag is etree.ProcessingInstruction:
+ # If a node is known to have been replaced during
+ # applying an inheritance, increment its index to
+ # compute an accurate xpath for subsequent nodes
+ if child.target == 'apply-inheritance-specs-node-removal':
+ indexes[child.text] += 1
+ e.remove(child)
else:
indexes[child.tag] += 1
self.distribute_branding(
diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index cb5b91298e2..7ad8a1f9442 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -870,6 +870,44 @@ class TestTemplating(ViewCase):
initial.get('data-oe-xpath'),
"The node's xpath position should be correct")
+ def test_branding_inherit_remove_node(self):
+ view1 = self.View.create({
+ 'name': "Base view",
+ 'type': 'qweb',
+ # The t-esc node is to ensure branding is distributed to both
+ # elements from the start
+ 'arch': """
+
+
+
+
+
+
+ """
+ })
+ self.View.create({
+ '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)
+
+ # Only remaining world but still the second in original view
+ [initial] = arch.xpath('/hello[1]/world[1]')
+ self.assertEqual(
+ '/hello[1]/world[2]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
def test_branding_inherit_top_t_field(self):
view1 = self.View.create({
'name': "Base view",
diff --git a/odoo/tools/template_inheritance.py b/odoo/tools/template_inheritance.py
index e6e62b78d88..45e7d7f96f5 100644
--- a/odoo/tools/template_inheritance.py
+++ b/odoo/tools/template_inheritance.py
@@ -176,19 +176,19 @@ def apply_inheritance_specs(source, specs_tree, inherit_branding=False, pre_loca
comment.tail = text
source.insert(0, comment)
else:
- replaced_node_tag = None
+ # TODO ideally the notion of 'inherit_branding' should
+ # not exist in this function. Given the current state of
+ # the code, it is however necessary to know where nodes
+ # were removed when distributing branding. As a stable
+ # 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:
+ node.addprevious(etree.ProcessingInstruction('apply-inheritance-specs-node-removal', node.tag))
+
for child in spec:
if child.get('position') == 'move':
child = extract(child)
- if inherit_branding and not replaced_node_tag and child.tag is not etree.Comment:
- # To make a correct branding, we need to
- # - know exactly which node has been replaced
- # - store it before anything else has altered the Tree
- # Do it exactly here :D
- child.set('meta-oe-xpath-replacing', node.tag)
- # We just store the replaced node tag on the first
- # child of the xpath replacing it
- replaced_node_tag = node.tag
node.addprevious(child)
node.getparent().remove(node)
elif mode == "inner":