From bac6889c5324edaeafc407ec751cf3ab311759a2 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 21 Jan 2016 17:25:57 +0100 Subject: [PATCH] [FIX] fields: make `related_sudo` work for draft records (used by onchange) Accessing a related field with `related_sudo=True` on a draft record should effectively traverse the fields as the admin user. Traversing new records in a different cache requires that all new records on the path are copied across caches. Make the copy across caches recursive when the first record on the path is a new record. --- .../test_new_api/tests/test_new_fields.py | 48 +++++++++++++++++-- openerp/fields.py | 17 ++++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py index e1ceca9ea51..ed3e340e681 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -3,9 +3,9 @@ # from datetime import date, datetime -from openerp.exceptions import AccessError +from openerp.exceptions import AccessError, except_orm from openerp.tests import common -from openerp.exceptions import except_orm +from openerp.tools import mute_logger class TestNewFields(common.TransactionCase): @@ -420,7 +420,49 @@ class TestNewFields(common.TransactionCase): self.assertEqual(message.name, "[%s] %s" % (discussion.name, '')) self.assertEqual(message.size, len(BODY)) - def test_41_defaults(self): + @mute_logger('openerp.addons.base.ir.ir_model') + def test_41_new_related(self): + """ test the behavior of related fields starting on new records. """ + # make discussions unreadable for demo user + access = self.env.ref('test_new_api.access_discussion') + access.write({'perm_read': False}) + + # create an environment for demo user + env = self.env(user=self.env.ref('base.user_demo')) + self.assertEqual(env.user.login, "demo") + + # create a new message as demo user + discussion = self.env.ref('test_new_api.discussion_0') + message = env['test_new_api.message'].new({'discussion': discussion}) + self.assertEqual(message.discussion, discussion) + + # read the related field discussion_name + self.assertEqual(message.discussion.env, env) + self.assertEqual(message.discussion_name, discussion.name) + with self.assertRaises(AccessError): + message.discussion.name + + @mute_logger('openerp.addons.base.ir.ir_model') + def test_42_new_related(self): + """ test the behavior of related fields traversing new records. """ + # make discussions unreadable for demo user + access = self.env.ref('test_new_api.access_discussion') + access.write({'perm_read': False}) + + # create an environment for demo user + env = self.env(user=self.env.ref('base.user_demo')) + self.assertEqual(env.user.login, "demo") + + # create a new discussion and a new message as demo user + discussion = env['test_new_api.discussion'].new({'name': 'Stuff'}) + message = env['test_new_api.message'].new({'discussion': discussion}) + self.assertEqual(message.discussion, discussion) + + # read the related field discussion_name + self.assertNotEqual(message.sudo().env, message.env) + self.assertEqual(message.discussion_name, discussion.name) + + def test_50_defaults(self): """ test default values. """ fields = ['discussion', 'body', 'author', 'size'] defaults = self.env['test_new_api.message'].default_get(fields) diff --git a/openerp/fields.py b/openerp/fields.py index 19bf4184574..081e75f6fba 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -42,6 +42,17 @@ def _check_value(value): """ Return ``value``, or call its getter if ``value`` is a :class:`SpecialValue`. """ return value.get() if isinstance(value, SpecialValue) else value +def copy_cache(records, env): + """ Recursively copy the cache of ``records`` to the environment ``env``. """ + for record, target in zip(records, records.with_env(env)): + if not target._cache: + for name, value in record._cache.iteritems(): + if isinstance(value, BaseModel): + target._cache[name] = value.with_env(env) + copy_cache(value, env) + else: + target._cache[name] = value + def resolve_mro(model, name, predicate): """ Return the list of successively overridden values of attribute ``name`` @@ -561,8 +572,10 @@ class Field(object): # when related_sudo, bypass access rights checks when reading values others = records.sudo() if self.related_sudo else records for record, other in zip(records, others): - # do not switch to another environment if record is a draft one - other, field = self.traverse_related(other if record.id else record) + if not record.id and record.env != other.env: + # draft records: copy record's cache to other's cache first + copy_cache(record, other.env) + other, field = self.traverse_related(other) record[self.name] = other[field.name] def _inverse_related(self, records):