[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:
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user