From ae8232136de496294383420abb3282fb51fcb292 Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Thu, 13 Dec 2018 11:16:52 +0000 Subject: [PATCH] [FIX] website: get only specific view children on _views_get() Before this comit: The `_views_get()` method would be called recursively for every `inherit_children_ids`. At the end of the method, we would get the complete view hierarchy, including duplicate hierarchies for website specific copies (COW). Then, the `get_related_views` that is calling `_views_get()` would be overriden in website module to keep only most specific views. But this would not be enough as it would leave orphans from other trees: - Have a base view 'B' - That has an inherited view 'I' - That has itself an inherited view 'II' - Now make the hierarchy specific on a website from 'I' - And add another inherited view to 'I' on the generic tree This become: B / \ / \ I I' / \ | II II2 II' As explained, filtering duplicates would not be enough as it would correctly remove 'I' and 'II' from the generic tree but would not remove 'II2'. At the end, it would return "B", "II2", "I'" and "II'". Now, we correctly return only "B", "I'" and "II'" by ignoring generic trees when there is a specific one. Closes #29347 --- addons/web_editor/models/ir_ui_view.py | 13 +++++++---- addons/website/models/ir_ui_view.py | 20 +++++------------ addons/website/tests/test_views.py | 31 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py index 908f58e6b8c..bb38e2d0652 100644 --- a/addons/web_editor/models/ir_ui_view.py +++ b/addons/web_editor/models/ir_ui_view.py @@ -199,6 +199,14 @@ class IrUiView(models.Model): self.sudo().model_data_id.write({'noupdate': True}) # TODO check if we remove this self.write({'arch': self._pretty_arch(new_arch)}) + @api.model + def _view_get_inherited_children(self, view, options): + extensions = view.inherit_children_ids + if not options: + # only active children + extensions = extensions.filtered(lambda view: view.active) + return extensions + @api.model def _view_obj(self, view_id): if isinstance(view_id, pycompat.string_types): @@ -244,10 +252,7 @@ class IrUiView(models.Model): if called_view and called_view not in views_to_return: views_to_return += self._views_get(called_view, options=options, bundles=bundles) - extensions = view.inherit_children_ids - if not options: - # only active children - extensions = view.inherit_children_ids.filtered(lambda view: view.active) + extensions = self._view_get_inherited_children(view, options) # Keep options in a deterministic order regardless of their applicability for extension in extensions.sorted(key=lambda v: v.id): diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index bead9cbb5ee..5a4c4611ff2 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -130,20 +130,7 @@ class View(models.Model): # method should never be called in a generic context, even for # tests) self = self.with_context(website_id=self.env['website'].get_current_website().id) - views = super(View, self).get_related_views(key, bundles=bundles) - current_website_id = self._context.get('website_id') - most_specific_views = self.env['ir.ui.view'] - - if not current_website_id: - return views - - for view in views: - if view.website_id and view.website_id.id == current_website_id: - most_specific_views |= view - elif not view.website_id and not any(view.key == view2.key and view2.website_id and view2.website_id.id == current_website_id for view2 in views): - most_specific_views |= view - - return most_specific_views + return super(View, self).get_related_views(key, bundles=bundles) @api.multi def _sort_suitability_key(self): @@ -167,6 +154,11 @@ class View(models.Model): filtered += sorted(group, key=lambda record: record._sort_suitability_key())[0] return (filtered + view_without_key).sorted(key=lambda view: (view.priority, view.id)) + @api.model + def _view_get_inherited_children(self, view, options): + extensions = super(View, self)._view_get_inherited_children(view, options) + return extensions.filter_duplicate() + @api.model def _view_obj(self, view_id): ''' Given an xml_id or a view_id, return the corresponding view record. diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py index 99fc814a271..0f4dcb1a91e 100644 --- a/addons/website/tests/test_views.py +++ b/addons/website/tests/test_views.py @@ -590,3 +590,34 @@ class TestCowViewSaving(common.TransactionCase): self.assertEqual(view.active, True, "filter_duplicate should return the generic one") view = View.with_context(active_test=False, website_id=1).search([('key', '=', self.inherit_view.key)]).filter_duplicate() self.assertEqual(view.active, False, "filter_duplicate should return the specific one") + + def test_get_related_views_tree(self): + View = self.env['ir.ui.view'] + + self.base_view.write({'name': 'B', 'key': 'B'}) + self.inherit_view.write({'name': 'I', 'key': 'I'}) + View.create({ + 'name': 'II', + 'mode': 'extension', + 'inherit_id': self.inherit_view.id, + 'arch': '
, sub ext
', + 'key': 'II', + }) + self.inherit_view.with_context(website_id=1).write({'name': 'Extension'}) # Trigger cow on hierarchy + View.create({ + 'name': 'II2', + 'mode': 'extension', + 'inherit_id': self.inherit_view.id, + 'arch': '
, sub sibling specific
', + 'key': 'II2', + }) + + # B + # / \ + # / \ + # I I' + # / \ | + # II II2 II' + + views = View.with_context(website_id=1).get_related_views('B') + self.assertEqual(views.mapped('key'), ['B', 'I', 'II'], "Should only return the specific tree")