From be43710ab8da3be5677ae4cd3fd2e3dbe1be637b Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Fri, 9 Jul 2021 14:54:54 +0000 Subject: [PATCH] [IMP] tools: xpath - support mode inner for position replace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a new attribute mode for position 'replace' to the xpath feature. This mode can take 2 values: - 'outer' (default mode if not provided) that will replace the sibling target - 'inner' that will preserve the sibling target If base arch is: ```html

yyy

``` `zzz` ```html

zzz

``` `zzz` ```html

zzz

``` Mode inner is useful for theme or render flat content, but not recommanded to be used in page editable since we ignore the inherit-branding part until now. task-2172208 closes odoo/odoo#48679 Signed-off-by: Jérémy Kersten (jke) Co-authored-by: Okan SUMER (osu) Co-authored-by: Jérémy Kersten --- odoo/addons/base/tests/test_views.py | 38 +++++++++++- odoo/tools/template_inheritance.py | 88 ++++++++++++++++------------ 2 files changed, 88 insertions(+), 38 deletions(-) diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index e8a0c4d347b..39a88ee5d44 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -300,8 +300,18 @@ class TestApplyInheritanceSpecs(ViewCase): self.base_arch = E.form( E.field(name="target"), string="Title") + self.adv_arch = E.form( + E.field( + "TEXT1", + E.field(name="subtarget"), + "TEXT2", + E.field(name="anothersubtarget"), + "TEXT3", + name="target", + ), + string="Title") - def test_replace(self): + def test_replace_outer(self): spec = E.field( E.field(name="replacement"), name="target", position="replace") @@ -366,6 +376,32 @@ class TestApplyInheritanceSpecs(ViewCase): name="target"), string="Title")) + def test_replace_inner(self): + spec = E.field( + "TEXT 4", + E.field(name="replacement"), + "TEXT 5", + E.field(name="replacement2"), + "TEXT 6", + name="target", position="replace", mode="inner") + + expected = E.form( + E.field( + "TEXT 4", + E.field(name="replacement"), + "TEXT 5", + E.field(name="replacement2"), + "TEXT 6", + name="target"), + string="Title") + + # applying spec to both base_arch and adv_arch is expected to give the same result + self.View.apply_inheritance_specs(self.base_arch, spec) + self.assertEqual(self.base_arch, expected) + + self.View.apply_inheritance_specs(self.adv_arch, spec) + self.assertEqual(self.adv_arch, expected) + def test_unpack_data(self): spec = E.data( E.field(E.field(name="inserted 0"), name="target"), diff --git a/odoo/tools/template_inheritance.py b/odoo/tools/template_inheritance.py index 4c5ea7c32dd..1b54b5c17e0 100644 --- a/odoo/tools/template_inheritance.py +++ b/odoo/tools/template_inheritance.py @@ -134,44 +134,58 @@ def apply_inheritance_specs(source, specs_tree, inherit_branding=False, pre_loca if node is not None: pos = spec.get('position', 'inside') if pos == 'replace': - for loc in spec.xpath(".//*[text()='$0']"): - loc.text = '' - loc.append(copy.deepcopy(node)) - if node.getparent() is None: - spec_content = None - comment = None - for content in spec: - if content.tag is not etree.Comment: - spec_content = content - break - else: - comment = content - source = copy.deepcopy(spec_content) - # only keep the t-name of a template root node - t_name = node.get('t-name') - if t_name: - source.set('t-name', t_name) - if comment is not None: - text = source.text - source.text = None - comment.tail = text - source.insert(0, comment) - else: - replaced_node_tag = None + mode = spec.get('mode', 'outer') + if mode == "outer": + for loc in spec.xpath(".//*[text()='$0']"): + loc.text = '' + loc.append(copy.deepcopy(node)) + if node.getparent() is None: + spec_content = None + comment = None + for content in spec: + if content.tag is not etree.Comment: + spec_content = content + break + else: + comment = content + source = copy.deepcopy(spec_content) + # only keep the t-name of a template root node + t_name = node.get('t-name') + if t_name: + source.set('t-name', t_name) + if comment is not None: + text = source.text + source.text = None + comment.tail = text + source.insert(0, comment) + else: + replaced_node_tag = None + 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": + # Replace the entire content of an element + for child in node: + node.remove(child) + node.text = None + 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) + node.append(copy.deepcopy(child)) + node.text = spec.text + + else: + raise ValueError(_("Invalid mode attribute:") + " '%s'" % mode) elif pos == 'attributes': for child in spec.getiterator('attribute'): attribute = child.get('name')