[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:
@@ -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
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_converter
|
||||
from . import test_views
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user