[FIX] models: old api, prevent infinite recursion in stored function fields

The risk was introduced by b7f1b9c.
IF _store_set_values() recall another _create() or _write(),
the recomputation mechanism enter in an infinite recursion
trying to reevaluate for each call exactly the same fields
for the same records than the previous one

This revision replaces the loop of _store_set_values()
by 2 nested loops:

 - that not breaks the entire consumption
   of recompute_old queue
   (Tested thanks to revision a922d39),
 - that allows to clear the queue
   before each recomputations bundle fixing thereby the recursion

Closes #7558
This commit is contained in:
Olivier LAURENT 2015-08-31 17:42:13 +02:00 committed by Denis Ledoux
parent 408ac6a5f0
commit 017688cc29
7 changed files with 113 additions and 19 deletions

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
{
'name': 'Test New API',
'name': 'Test API',
'version': '1.0',
'category': 'Tests',
'description': """A module to test the new API.""",
'description': """A module to test the API.""",
'author': 'OpenERP SA',
'maintainer': 'OpenERP SA',
'website': 'http://www.openerp.com',

View File

@ -26,5 +26,7 @@
<field name="discussion" ref="discussion_0"/>
<field name="body">This is a much longer message</field>
</record>
<record id="c1" model="test_old_api.function_counter">
</record>
</data>
</openerp>

View File

@ -3,3 +3,5 @@ access_category,test_new_api_category,test_new_api.model_test_new_api_category,,
access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1
access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1
access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1
access_test_function_noinfiniterecursion,access_test_function_noinfiniterecursion,model_test_old_api_function_noinfiniterecursion,,1,1,1,1
access_test_function_counter,access_test_function_counter,model_test_old_api_function_counter,,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_discussion test_new_api_discussion test_new_api.model_test_new_api_discussion 1 1 1 1
4 access_message test_new_api_message test_new_api.model_test_new_api_message 1 1 1 1
5 access_mixed test_new_api_mixed test_new_api.model_test_new_api_mixed 1 1 1 1
6 access_test_function_noinfiniterecursion access_test_function_noinfiniterecursion model_test_old_api_function_noinfiniterecursion 1 1 1 1
7 access_test_function_counter access_test_function_counter model_test_old_api_function_counter 1 1 1 1

View File

@ -19,9 +19,17 @@
#
##############################################################################
import datetime
from openerp.exceptions import AccessError
##############################################################################
#
# OLD API
#
##############################################################################
from openerp.osv import osv, fields
class res_partner(osv.Model):
_inherit = 'res.partner'
@ -41,6 +49,48 @@ class res_partner(osv.Model):
}
class TestFunctionCounter(osv.Model):
_name = 'test_old_api.function_counter'
def _compute_cnt(self, cr, uid, ids, fname, arg, context=None):
res = {}
for cnt in self.browse(cr, uid, ids, context=context):
res[cnt.id] = cnt.access and cnt.cnt + 1 or 0
return res
_columns = {
'access': fields.datetime('Datetime Field'),
'cnt': fields.function(
_compute_cnt, type='integer', string='Function Field', store=True),
}
class TestFunctionNoInfiniteRecursion(osv.Model):
_name = 'test_old_api.function_noinfiniterecursion'
def _compute_f1(self, cr, uid, ids, fname, arg, context=None):
res = {}
for tf in self.browse(cr, uid, ids, context=context):
res[tf.id] = 'create' in tf.f0 and 'create' or 'write'
cntobj = self.pool['test_old_api.function_counter']
cnt_id = self.pool['ir.model.data'].xmlid_to_res_id(
cr, uid, 'test_new_api.c1')
cntobj.write(
cr, uid, cnt_id, {'access': datetime.datetime.now()},
context=context)
return res
_columns = {
'f0': fields.char('Char Field'),
'f1': fields.function(
_compute_f1, type='char', string='Function Field', store=True),
}
##############################################################################
#
# NEW API
#
##############################################################################
from openerp import models, fields, api, _

View File

@ -5,3 +5,4 @@ from . import test_new_fields
from . import test_onchange
from . import test_field_conversions
from . import test_attributes
from . import test_no_infinite_recursion

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from openerp.tests import common
class test_no_infinite_recursion(common.TransactionCase):
def setUp(self):
super(test_no_infinite_recursion, self).setUp()
self.tstfct = self.registry['test_old_api.function_noinfiniterecursion']
def test_00_create_and_update(self):
"""
Check that computing old api function field does not cycle infinitely
See https://github.com/odoo/odoo/pull/7558
"""
cr, uid, context, tstfct = self.cr, self.uid, {}, self.tstfct
vals = {
'f0': 'Test create',
}
idnew = tstfct.create(cr, uid, vals, context=context)
tst = tstfct.browse(cr, uid, idnew, context=context)
self.assertEqual(tst.f1, 'create')
vals = {
'f0': 'Test write',
}
tstfct.write(cr, uid, idnew, vals, context=context)
self.assertEqual(tst.f1, 'write')

View File

@ -4007,18 +4007,22 @@ class BaseModel(object):
done = {}
recs.env.recompute_old.extend(result)
for order, model_name, ids_to_update, fields_to_recompute in sorted(recs.env.recompute_old):
key = (model_name, tuple(fields_to_recompute))
done.setdefault(key, {})
# avoid to do several times the same computation
todo = []
for id in ids_to_update:
if id not in done[key]:
done[key][id] = True
if id not in deleted_related[model_name]:
todo.append(id)
self.pool[model_name]._store_set_values(cr, user, todo, fields_to_recompute, context)
recs.env.clear_recompute_old()
while recs.env.recompute_old:
sorted_recompute_old = sorted(recs.env.recompute_old)
recs.env.clear_recompute_old()
for __, model_name, ids_to_update, fields_to_recompute in \
sorted_recompute_old:
key = (model_name, tuple(fields_to_recompute))
done.setdefault(key, {})
# avoid to do several times the same computation
todo = []
for id in ids_to_update:
if id not in done[key]:
done[key][id] = True
if id not in deleted_related[model_name]:
todo.append(id)
self.pool[model_name]._store_set_values(
cr, user, todo, fields_to_recompute, context)
# recompute new-style fields
if recs.env.recompute and context.get('recompute', True):
@ -4275,11 +4279,15 @@ class BaseModel(object):
if recs.env.recompute and context.get('recompute', True):
done = []
for order, model_name, ids, fields2 in sorted(recs.env.recompute_old):
if not (model_name, ids, fields2) in done:
self.pool[model_name]._store_set_values(cr, user, ids, fields2, context)
done.append((model_name, ids, fields2))
recs.env.clear_recompute_old()
while recs.env.recompute_old:
sorted_recompute_old = sorted(recs.env.recompute_old)
recs.env.clear_recompute_old()
for __, model_name, ids, fields2 in sorted_recompute_old:
if not (model_name, ids, fields2) in done:
self.pool[model_name]._store_set_values(
cr, user, ids, fields2, context)
done.append((model_name, ids, fields2))
# recompute new-style fields
recs.recompute()