[FIX] core: always evaluate field.depends upon setup
This is necessary when a field's dependencies are given by a callable. This functionality was broken with the support for an explicit parameter `depends` in the fields' definition, because the implementation could not distinguish between the field parameter `depends` and the `depends` evaluated from compute functions. The fix consists in storing the field parameter `depends` into `field._depends`, and the result of the setup into `field.depends`. closes odoo/odoo#58144 X-original-commit: aebe9a76c9b35e84eda1f46bec3ef6da152b1539 Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
@@ -595,6 +595,27 @@ class ComputeOnchangeLine(models.Model):
|
||||
required=True, ondelete='cascade')
|
||||
|
||||
|
||||
class ComputeDynamicDepends(models.Model):
|
||||
_name = 'test_new_api.compute.dynamic.depends'
|
||||
_description = "Computed field with dynamic dependencies"
|
||||
|
||||
name1 = fields.Char()
|
||||
name2 = fields.Char()
|
||||
name3 = fields.Char()
|
||||
full_name = fields.Char(compute='_compute_full_name')
|
||||
|
||||
def _get_full_name_fields(self):
|
||||
# the fields to use are stored in a config parameter
|
||||
depends = self.env['ir.config_parameter'].get_param('test_new_api.full_name', '')
|
||||
return depends.split(',') if depends else []
|
||||
|
||||
@api.depends(lambda self: self._get_full_name_fields())
|
||||
def _compute_full_name(self):
|
||||
fnames = self._get_full_name_fields()
|
||||
for record in self:
|
||||
record.full_name = ", ".join(filter(None, (record[fname] for fname in fnames)))
|
||||
|
||||
|
||||
class ComputeUnassigned(models.Model):
|
||||
_name = 'test_new_api.compute.unassigned'
|
||||
_description = "Model with computed fields left unassigned"
|
||||
|
||||
@@ -26,6 +26,7 @@ access_test_new_api_cascade,access_test_new_api_cascade,model_test_new_api_casca
|
||||
access_test_new_api_compute_readwrite,access_test_new_api_compute_readwrite,model_test_new_api_compute_readwrite,,1,1,1,1
|
||||
access_test_new_api_compute_onchange,access_test_new_api_compute_onchange,model_test_new_api_compute_onchange,,1,1,1,1
|
||||
access_test_new_api_compute_onchange_line,access_test_new_api_compute_onchange_line,model_test_new_api_compute_onchange_line,,1,1,1,1
|
||||
access_test_new_api_compute_dynamic_depends,access_test_new_api_compute_dynamic_depends,model_test_new_api_compute_dynamic_depends,,1,1,1,1
|
||||
access_test_new_api_compute_unassigned,access_test_new_api_compute_unassigned,model_test_new_api_compute_unassigned,,1,1,1,1
|
||||
access_test_new_api_binary_svg,access_test_new_api_binary_svg,model_test_new_api_binary_svg,,1,1,1,1
|
||||
access_test_new_api_monetary_base,access_test_new_api_monetary_base,model_test_new_api_monetary_base,,1,1,1,1
|
||||
|
||||
|
@@ -456,6 +456,18 @@ class TestFields(TransactionCaseWithUserDemo):
|
||||
record.foo = "Ho"
|
||||
self.assertEqual(record.baz, "<[Ho]>")
|
||||
|
||||
def test_12_dynamic_depends(self):
|
||||
Model = self.registry['test_new_api.compute.dynamic.depends']
|
||||
self.assertEqual(Model.full_name.depends, ())
|
||||
|
||||
# the dependencies of full_name are stored in a config parameter
|
||||
self.env['ir.config_parameter'].set_param('test_new_api.full_name', 'name1,name2')
|
||||
|
||||
# this must re-evaluate the field's dependencies
|
||||
self.env['base'].flush()
|
||||
self.registry.setup_models(self.cr)
|
||||
self.assertEqual(Model.full_name.depends, ('name1', 'name2'))
|
||||
|
||||
def test_13_inverse(self):
|
||||
""" test inverse computation of fields """
|
||||
Category = self.env['test_new_api.category']
|
||||
|
||||
+20
-8
@@ -222,8 +222,8 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
index = False # whether the field is indexed in database
|
||||
manual = False # whether the field is a custom field
|
||||
copy = True # whether the field is copied over by BaseModel.copy()
|
||||
depends = None # collection of field dependencies
|
||||
depends_context = None # collection of context key dependencies
|
||||
_depends = None # collection of field dependencies
|
||||
_depends_context = None # collection of context key dependencies
|
||||
recursive = False # whether self depends on itself
|
||||
compute = None # compute(recs) computes field on recs
|
||||
compute_sudo = False # whether field should be recomputed as superuser
|
||||
@@ -344,8 +344,13 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
if attrs.get('translate'):
|
||||
# by default, translatable fields are context-dependent
|
||||
attrs['depends_context'] = attrs.get('depends_context', ()) + ('lang',)
|
||||
|
||||
# parameters 'depends' and 'depends_context' are stored in attributes
|
||||
# '_depends' and '_depends_context', respectively
|
||||
if 'depends' in attrs:
|
||||
attrs['depends'] = tuple(attrs['depends'])
|
||||
attrs['_depends'] = tuple(attrs.pop('depends'))
|
||||
if 'depends_context' in attrs:
|
||||
attrs['_depends_context'] = tuple(attrs.pop('depends_context'))
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -407,7 +412,10 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
|
||||
def _setup_regular_full(self, model):
|
||||
""" Determine the dependencies and inverse field(s) of ``self``. """
|
||||
if self.depends is not None:
|
||||
if self._depends is not None:
|
||||
# the parameter 'depends' has priority over 'depends' on compute
|
||||
self.depends = self._depends
|
||||
self.depends_context = self._depends_context or ()
|
||||
return
|
||||
|
||||
# determine the functions implementing self.compute
|
||||
@@ -420,7 +428,7 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
|
||||
# collect depends and depends_context
|
||||
depends = []
|
||||
depends_context = list(self.depends_context or ())
|
||||
depends_context = list(self._depends_context or ())
|
||||
for func in funcs:
|
||||
deps = getattr(func, '_depends', ())
|
||||
depends.extend(deps(model) if callable(deps) else deps)
|
||||
@@ -460,7 +468,9 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
raise TypeError("Type of related field %s is inconsistent with %s" % (self, field))
|
||||
|
||||
# determine dependencies, compute, inverse, and search
|
||||
if self.depends is None:
|
||||
if self._depends is not None:
|
||||
self.depends = self._depends
|
||||
else:
|
||||
self.depends = ('.'.join(self.related),)
|
||||
self.compute = self._compute_related
|
||||
if self.inherited or not (self.readonly or field.readonly):
|
||||
@@ -486,7 +496,9 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
self.required = True
|
||||
self._modules.update(field._modules)
|
||||
|
||||
if field.depends_context:
|
||||
if self._depends_context is not None:
|
||||
self.depends_context = self._depends_context
|
||||
else:
|
||||
self.depends_context = field.depends_context
|
||||
|
||||
def traverse_related(self, record):
|
||||
@@ -1883,7 +1895,7 @@ class Binary(Field):
|
||||
type = 'binary'
|
||||
|
||||
prefetch = False # not prefetched by default
|
||||
depends_context = ('bin_size',) # depends on context (content or size)
|
||||
_depends_context = ('bin_size',) # depends on context (content or size)
|
||||
attachment = True # whether value is stored in attachment
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user