diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py
index b1a00d904b6ff..6c2570792b24e 100644
--- a/odoo/addons/base/models/ir_ui_view.py
+++ b/odoo/addons/base/models/ir_ui_view.py
@@ -646,9 +646,19 @@ def extract(spec):
if node.getparent() is None:
source = copy.deepcopy(spec[0])
else:
+ replaced_node_tag = None
for child in spec:
if child.get('position') == 'move':
child = extract(child)
+
+ if self._context.get('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)
+ replaced_node_tag = node.tag # We just store the replaced node tag on the first child of the xpath replacing it
+
node.addprevious(child)
node.getparent().remove(node)
elif pos == 'attributes':
@@ -1246,6 +1256,11 @@ def distribute_branding(self, e, branding=None, parent_xpath='',
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)
else:
indexes[child.tag] += 1
diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index 7b8deb8ea33ae..403e7f9260d46 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -674,6 +674,115 @@ def test_branding_inherit(self):
second.get('data-oe-id'),
"second should come from the extension view")
+ def test_branding_inherit_replace_node(self):
+ view1 = self.View.create({
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+
+
+
+
+ """
+ })
+ self.View.create({
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': view1.id,
+ 'arch': """
+ Is a ghetto
+ Wonder when I'll find paradise
+
+ """
+ })
+
+ arch_string = view1.with_context(inherit_branding=True).read_combined(['arch'])['arch']
+
+ arch = etree.fromstring(arch_string)
+ self.View.distribute_branding(arch)
+
+ # First world - has been replaced by inheritance
+ [initial] = arch.xpath('/hello[1]/world[1]')
+ self.assertEqual(
+ '/xpath/world[1]',
+ initial.get('data-oe-xpath'),
+ 'Inherited nodes have correct xpath')
+
+ # Second world added by inheritance
+ [initial] = arch.xpath('/hello[1]/world[2]')
+ self.assertEqual(
+ '/xpath/world[2]',
+ initial.get('data-oe-xpath'),
+ 'Inherited nodes have correct xpath')
+
+ # Third world - is not editable
+ [initial] = arch.xpath('/hello[1]/world[3]')
+ self.assertFalse(
+ initial.get('data-oe-xpath'),
+ 'node containing t-esc is not branded')
+
+ # The most important assert
+ # Fourth world - should have a correct oe-xpath, which is 3rd in main view
+ [initial] = arch.xpath('/hello[1]/world[4]')
+ self.assertEqual(
+ '/hello[1]/world[3]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
+ def test_branding_inherit_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': """
+ Is a ghetto
+ Wonder when I'll find paradise
+
+ """
+ })
+
+ arch_string = view1.with_context(inherit_branding=True).read_combined(['arch'])['arch']
+
+ arch = etree.fromstring(arch_string)
+ self.View.distribute_branding(arch)
+
+ [initial] = arch.xpath('/hello[1]/war[1]')
+ self.assertEqual(
+ '/xpath/war',
+ initial.get('data-oe-xpath'),
+ 'Inherited nodes have correct xpath')
+
+ # First world: from inheritance
+ [initial] = arch.xpath('/hello[1]/world[1]')
+ self.assertEqual(
+ '/xpath/world',
+ initial.get('data-oe-xpath'),
+ 'Inherited nodes have correct xpath')
+
+ # Second world - is not editable
+ [initial] = arch.xpath('/hello[1]/world[2]')
+ self.assertFalse(
+ initial.get('data-oe-xpath'),
+ 'node containing t-esc is not branded')
+
+ # The most important assert
+ # Third world - should have a correct oe-xpath, which is 3rd in main view
+ [initial] = arch.xpath('/hello[1]/world[3]')
+ self.assertEqual(
+ '/hello[1]/world[3]',
+ initial.get('data-oe-xpath'),
+ "The node's xpath position should be correct")
+
def test_branding_primary_inherit(self):
view1 = self.View.create({
'name': "Base view",