[FIX] web_editor: fix _view_get infinite recursion

The _view_get function is a recursive function used to retieve all the
views related to a view (inherited or t-called).

The issue is that by an odd set of circumstances it is possible to have
a loop in the view graph. Resulting in the recursive function being
called until a "maximum recursion depth exceeded" error occurs.

Example of a loop: A t-call B and A inherit from B

This is possible on an update of a view that has been forked by website:
If the view A was doing a t-call on B and is has been duplicated with
the arch modified.
When we update with the changes A now inherit from B instead of t-call B
Since the arch was modified it will not be updated so A will still
t-call B but the inherit_id of A is unchanged so it will be updated to
reference B resulting in a loop.

closes odoo/odoo#51620

X-original-commit: 63e1e84ebd7e609d6e06d83dc0f0092688b013eb
Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
This commit is contained in:
fja-odoo
2020-05-20 13:20:35 +00:00
parent e3b4f3ddf4
commit 049745c5ff
3 changed files with 37 additions and 6 deletions
+9 -6
View File
@@ -224,7 +224,7 @@ class IrUiView(models.Model):
# Used by translation mechanism, SEO and optional templates
@api.model
def _views_get(self, view_id, get_children=True, bundles=False, root=True):
def _views_get(self, view_id, get_children=True, bundles=False, root=True, visited=None):
""" For a given view ``view_id``, should return:
* the view itself
* all views inheriting from it, enabled or not
@@ -238,6 +238,8 @@ class IrUiView(models.Model):
_logger.warning("Could not find view object with view_id '%s'", view_id)
return self.env['ir.ui.view']
if visited is None:
visited = []
while root and view.inherit_id:
view = view.inherit_id
@@ -252,8 +254,8 @@ class IrUiView(models.Model):
called_view = self._view_obj(child.get('t-call', child.get('t-call-assets')))
except ValueError:
continue
if called_view and called_view not in views_to_return:
views_to_return += self._views_get(called_view, get_children=get_children, bundles=bundles)
if called_view and called_view not in views_to_return and called_view.id not in visited:
views_to_return += self._views_get(called_view, get_children=get_children, bundles=bundles, visited=visited + views_to_return.ids)
if not get_children:
return views_to_return
@@ -263,9 +265,10 @@ class IrUiView(models.Model):
# Keep children in a deterministic order regardless of their applicability
for extension in extensions.sorted(key=lambda v: v.id):
# only return optional grandchildren if this child is enabled
for ext_view in self._views_get(extension, get_children=extension.active, root=False):
if ext_view not in views_to_return:
views_to_return += ext_view
if extension.id not in visited:
for ext_view in self._views_get(extension, get_children=extension.active, root=False, visited=visited + views_to_return.ids):
if ext_view not in views_to_return:
views_to_return += ext_view
return views_to_return
@api.model
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_converter
from . import test_views
+27
View File
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import TransactionCase
class TestViews(TransactionCase):
def test_infinite_inherit_loop(self):
# Creates an infinite loop: A t-call B and A inherit from B
View = self.env['ir.ui.view']
first_view = View.create({
'name': 'Test View 1',
'type': 'qweb',
'arch': '<div>Hello World</div>',
'key': 'web_editor.test_first_view',
})
second_view = View.create({
'name': 'Test View 2',
'type': 'qweb',
'arch': '<t t-call="web_editor.test_first_view"/>',
'key': 'web_editor.test_second_view',
})
second_view.write({
'inherit_id': first_view.id,
})
# Test for RecursionError: maximum recursion depth exceeded in this function
View._views_get(first_view)