[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
This commit is contained in:
Romain Derie
2018-12-19 18:24:53 +00:00
parent 9411ac0acd
commit ae8232136d
3 changed files with 46 additions and 18 deletions
+9 -4
View File
@@ -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):
+6 -14
View File
@@ -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.
+31
View File
@@ -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': '<div position="inside">, sub ext</div>',
'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': '<div position="inside">, sub sibling specific</div>',
'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")