summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier LAURENT <olivier.laurent@acsone.eu>2015-08-31 17:42:13 +0200
committerDenis Ledoux <dle@odoo.com>2015-08-31 17:45:21 +0200
commit017688cc29e71270762c828760feb062bbe9ba24 (patch)
tree2c5ce5f59bcfd80ceca32869c4c249b809553bd0
parent408ac6a5f03b4993027f41c1e66effc84b2d7426 (diff)
[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
-rw-r--r--openerp/addons/test_new_api/__openerp__.py4
-rw-r--r--openerp/addons/test_new_api/demo_data.xml2
-rw-r--r--openerp/addons/test_new_api/ir.model.access.csv2
-rw-r--r--openerp/addons/test_new_api/models.py50
-rw-r--r--openerp/addons/test_new_api/tests/__init__.py1
-rw-r--r--openerp/addons/test_new_api/tests/test_no_infinite_recursion.py31
-rw-r--r--openerp/models.py42
7 files changed, 113 insertions, 19 deletions
diff --git a/openerp/addons/test_new_api/__openerp__.py b/openerp/addons/test_new_api/__openerp__.py
index 9c859de4445..89355ac19ee 100644
--- a/openerp/addons/test_new_api/__openerp__.py
+++ b/openerp/addons/test_new_api/__openerp__.py
@@ -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',
diff --git a/openerp/addons/test_new_api/demo_data.xml b/openerp/addons/test_new_api/demo_data.xml
index acb53a7b07f..1715b024cd7 100644
--- a/openerp/addons/test_new_api/demo_data.xml
+++ b/openerp/addons/test_new_api/demo_data.xml
@@ -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>
diff --git a/openerp/addons/test_new_api/ir.model.access.csv b/openerp/addons/test_new_api/ir.model.access.csv
index 5b62045ae08..2bbd1adf12d 100644
--- a/openerp/addons/test_new_api/ir.model.access.csv
+++ b/openerp/addons/test_new_api/ir.model.access.csv
@@ -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
diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py
index 4399cb0ed49..173935a8985 100644
--- a/openerp/addons/test_new_api/models.py
+++ b/openerp/addons/test_new_api/models.py
@@ -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, _
diff --git a/openerp/addons/test_new_api/tests/__init__.py b/openerp/addons/test_new_api/tests/__init__.py
index 790ad9c9a59..f4803bd4837 100644
--- a/openerp/addons/test_new_api/tests/__init__.py
+++ b/openerp/addons/test_new_api/tests/__init__.py
@@ -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
diff --git a/openerp/addons/test_new_api/tests/test_no_infinite_recursion.py b/openerp/addons/test_new_api/tests/test_no_infinite_recursion.py
new file mode 100644
index 00000000000..68ef3fcf3b9
--- /dev/null
+++ b/openerp/addons/test_new_api/tests/test_no_infinite_recursion.py
@@ -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')
diff --git a/openerp/models.py b/openerp/models.py
index 35944ff4873..b0c7762bbd7 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -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()