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",