From d814c9bee4506e12880a0236d531ffbb801c01ca Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 21 Dec 2022 12:58:12 +0000 Subject: [PATCH] [IMP] base: constraint to check view root node is matching the view type This PR follows odoo/odoo#108073 Just ensure the root node of the view architecture matches the view type. e.g. Valid: ```xml
...
``` Invalid: ```xml
...
...
``` closes odoo/odoo#108525 Related: odoo/upgrade#4155 Related: odoo/enterprise#35501 Signed-off-by: Denis Ledoux (dle) --- odoo/addons/base/models/ir_ui_view.py | 15 +++++++++---- odoo/addons/base/tests/test_views.py | 31 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index ab21145fba3..4f294764fcc 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -1394,7 +1394,7 @@ actual arch. # view validation #------------------------------------------------------------------- - def _validate_view(self, node, model_name, editable=True, full=False): + def _validate_view(self, node, model_name, view_type=None, editable=True, full=False): """ Validate the given architecture node, and return its corresponding NameManager. @@ -1407,6 +1407,13 @@ actual arch. """ self.ensure_one() + view_type = view_type or self.type + if node.tag != view_type: + self._raise_view_error(_( + 'The root node of a %(view_type)s view should be a <%(view_type)s>, not a <%(tag)s>', + view_type=view_type, tag=node.tag, + ), node) + if model_name not in self.env: self._raise_view_error(_('Model not found: %(model)s', model=model_name), node) @@ -1492,7 +1499,7 @@ actual arch. if len(searchpanels) > 1: self._raise_view_error(_('Search tag can only contain one search panel'), node) node.remove(searchpanels[0]) - self._validate_view(searchpanels[0], name_manager.model._name, + self._validate_view(searchpanels[0], name_manager.model._name, view_type="searchpanel", editable=False, full=node_info['validate']) def _validate_tag_field(self, node, name_manager, node_info): @@ -1531,7 +1538,7 @@ actual arch. continue node.remove(child) sub_manager = self._validate_view( - child, field.comodel_name, editable=node_info['editable'], full=validate, + child, field.comodel_name, view_type=child.tag, editable=node_info['editable'], full=validate, ) for fname, groups_uses in sub_manager.mandatory_parent_fields.items(): for groups, use in groups_uses.items(): @@ -1645,7 +1652,7 @@ actual arch. groupby_node = E.groupby(*node) # validate the node as a nested view sub_manager = self._validate_view( - groupby_node, field.comodel_name, editable=False, full=node_info['validate'], + groupby_node, field.comodel_name, view_type="groupby", editable=False, full=node_info['validate'], ) name_manager.has_field(node, name) for fname, groups_uses in sub_manager.mandatory_parent_fields.items(): diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index e1631cbf8f6..0fc7164d969 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -1548,6 +1548,11 @@ class TestViews(ViewCase): kw.setdefault('active', True) if 'arch_db' in kw: arch_db = kw['arch_db'] + if kw.get('inherit_id'): + self.cr.execute('SELECT type FROM ir_ui_view WHERE id = %s', [kw['inherit_id']]) + kw['type'] = self.cr.fetchone()[0] + else: + kw['type'] = etree.fromstring(arch_db).tag kw['arch_db'] = Json({'en_US': arch_db}) if self.env.lang == 'en_US' else Json({'en_US': arch_db, self.env.lang: arch_db}) keys = sorted(kw) @@ -1558,6 +1563,32 @@ class TestViews(ViewCase): self.cr.execute(query, kw) return self.cr.fetchone()[0] + def test_view_root_node_matches_view_type(self): + view = self.View.create({ + 'name': 'foo', + 'model': 'ir.ui.view', + 'arch': """ +
+
+ """, + }) + self.assertEqual(view.type, 'form') + + with self.assertRaises(ValidationError): + self.View.create({ + 'name': 'foo', + 'model': 'ir.ui.view', + 'type': 'form', + 'arch': """ + +
+
+
+
+
+ """, + }) + def test_custom_view_validation(self): model = 'ir.actions.act_url' validate = partial(self.View._validate_custom_views, model)