[FIX] test_new_api: Fix bad shared cache usage for stored computed fields

Purpose
=======

Since the 13.0, a regression has been introduced in hr_timesheet.

Considering:
- The account.analytic.line model, representing a timesheet entry
- The project.task model, representing a task, on which we have the fields:
    - timesheet_ids: One2many
    - planned_hours: Computed (sudo), stored, representing the sum of the timesheet amounts
- An ir.rule restricting the timesheets CRUD to his own only.

A user can only see its own timesheets on a task, but the field "Planned Hours",
which is stored-compute_sudo, should take all the timesheet lines into account

However, when adding a new line and then recomputing the value, no existing line
from another user is binded on self, then the value is erased and saved on the
database.

Specification
=============

This commit introduces a test that illustrates that bad usage of a shared cache.

A correct way to fix this could be to avoid using the cache if an ir.rule is pointing
to the related model. That would force the recomputation in a real sudoed environment.

closes odoo/odoo#55408

Taskid: 2285924
X-original-commit: 50e229fe66b2a82717d4260287e18a875bbd26f5
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Yannick Tivisse
2020-08-04 15:01:45 +00:00
parent eecb1e01fc
commit 62355b6ad3
6 changed files with 98 additions and 0 deletions
+1
View File
@@ -11,6 +11,7 @@
'auto_install': False,
'data': [
'security/ir.model.access.csv',
'security/test_new_api_security.xml',
'views/test_new_api_views.xml',
'views/test_new_api_templates.xml',
'data/test_new_api_data.xml',
@@ -1052,3 +1052,29 @@ class SelectionNonStored(models.Model):
('foo', "Foo"),
('bar', "Bar"),
], store=False)
# Special classes to ensure the correct usage of a shared cache amongst users.
# See the method test_shared_cache_computed_field
class SharedCacheComputeParent(models.Model):
_name = 'test_new_api.model_shared_cache_compute_parent'
_description = 'model_shared_cache_compute_parent'
name = fields.Char(string="Task Name")
line_ids = fields.One2many(
'test_new_api.model_shared_cache_compute_line', 'parent_id', string="Timesheets")
total_amount = fields.Integer(compute='_compute_total_amount', store=True, compute_sudo=True)
@api.depends('line_ids.amount')
def _compute_total_amount(self):
for parent in self:
parent.total_amount = sum(parent.line_ids.mapped('amount'))
class ShareCacheComputeLine(models.Model):
_name = 'test_new_api.model_shared_cache_compute_line'
_description = 'model_shared_cache_compute_line'
parent_id = fields.Many2one('test_new_api.model_shared_cache_compute_parent')
amount = fields.Integer()
user_id = fields.Many2one('res.users', default= lambda self: self.env.user) # Note: There is an ir.rule about this.
@@ -60,3 +60,5 @@ access_test_new_api_model_selection_required,access_test_new_api_model_selection
access_test_new_api_model_selection_non_stored,access_test_new_api_model_selection_non_stored,model_test_new_api_model_selection_non_stored,,1,1,1,1
access_test_new_api_move,access_test_new_api_move,model_test_new_api_move,,1,1,1,1
access_test_new_api_move_line,access_test_new_api_move_line,model_test_new_api_move_line,,1,1,1,1
access_test_new_api_model_shared_cache_compute_parent,access_test_new_api.model_shared_cache_compute_parent,model_test_new_api_model_shared_cache_compute_parent,,1,1,1,1
access_test_new_api_model_shared_cache_compute_line,access_test_new_api.model_shared_cache_compute_line,model_test_new_api_model_shared_cache_compute_line,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
60 access_test_new_api_model_selection_non_stored access_test_new_api_model_selection_non_stored model_test_new_api_model_selection_non_stored 1 1 1 1
61 access_test_new_api_move access_test_new_api_move model_test_new_api_move 1 1 1 1
62 access_test_new_api_move_line access_test_new_api_move_line model_test_new_api_move_line 1 1 1 1
63 access_test_new_api_model_shared_cache_compute_parent access_test_new_api.model_shared_cache_compute_parent model_test_new_api_model_shared_cache_compute_parent 1 1 1 1
64 access_test_new_api_model_shared_cache_compute_line access_test_new_api.model_shared_cache_compute_line model_test_new_api_model_shared_cache_compute_line 1 1 1 1
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="ir_rule_test_new_api_model_shared_cache_compute_line" model="ir.rule">
<field name="name">model_shared_cache_compute_line: See own lines</field>
<field name="model_id" ref="test_new_api.model_test_new_api_model_shared_cache_compute_line"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
</data>
</odoo>
@@ -2947,3 +2947,39 @@ class TestComputeQueries(common.TransactionCase):
record = model.create({'foo': 'Foo', 'bar': 'Bar'})
self.assertEqual(record.foo, 'Bar')
self.assertEqual(record.bar, 'Bar')
class test_shared_cache(TransactionCaseWithUserDemo):
def test_shared_cache_computed_field(self):
# Test case: Check that the shared cache is not used if a compute_sudo stored field
# is computed IF there is an ir.rule defined on this specific model.
# Real life example:
# A user can only see its own timesheets on a task, but the field "Planned Hours",
# which is stored-compute_sudo, should take all the timesheet lines into account
# However, when adding a new line and then recomputing the value, no existing line
# from another user is binded on self, then the value is erased and saved on the
# database.
task = self.env['test_new_api.model_shared_cache_compute_parent'].create({
'name': 'Shared Task'})
self.env['test_new_api.model_shared_cache_compute_line'].create({
'user_id': self.env.ref('base.user_admin').id,
'parent_id': task.id,
'amount': 1,
})
self.assertEqual(task.total_amount, 1)
self.env['base'].flush()
task.invalidate_cache() # Start fresh, as it would be the case on 2 different sessions.
task = task.with_user(self.user_demo)
with common.Form(task) as task_form:
# Use demo has no access to the already existing line
self.assertEqual(len(task_form.line_ids), 0)
# But see the real total_amount
self.assertEqual(task_form.total_amount, 1)
# Now let's add a new line (and retrigger the compute method)
with task_form.line_ids.new() as line:
line.amount = 2
# The new value for total_amount, should be 3, not 2.
self.assertEqual(task_form.total_amount, 2)
@@ -319,5 +319,27 @@
</form>
</field>
</record>
<record id="model_shared_cache_compute_parent_view_form" model="ir.ui.view">
<field name="name">model_shared_cache_compute_parent_view_form</field>
<field name="model">test_new_api.model_shared_cache_compute_parent</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="total_amount"/>
<field name="line_ids">
<tree editable="bottom">
<field name="parent_id"/>
<field name="amount"/>
<field name="user_id"/>
</tree>
</field>
</group>
</sheet>
</form>
</field>
</record>
</data>
</odoo>