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: