[IMP] project: improve on x2many tracking

While investigating an issue with the x2many tracking, we found a
better way that to track those field while also fixing the issue.

Fixes an issue with `default_*` context keys that are used in the
project app upon writing on tasks.

TaskId-2725014

closes odoo/odoo#82189

X-original-commit: a281a18fc84a09177f7b5e8aab98b8dc34dcc3b8
Signed-off-by: Xavier <xbo@odoo.com>
Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
William Braeckman
2022-01-04 08:47:07 +00:00
committed by Xavier BOL (xbo)
parent 5df1bf140b
commit a3a32c66fd
3 changed files with 58 additions and 39 deletions
+18 -39
View File
@@ -950,7 +950,7 @@ class Task(models.Model):
subtask_planned_hours = fields.Float("Sub-tasks Planned Hours", compute='_compute_subtask_planned_hours',
help="Sum of the time planned of all the sub-tasks linked to this task. Usually less than or equal to the initially planned time of this task.")
# Tracking of this field is done in the write function
user_ids = fields.Many2many('res.users', relation='project_task_user_rel', column1='task_id', column2='user_id', string='Assignees', context={'active_test': False})
user_ids = fields.Many2many('res.users', relation='project_task_user_rel', column1='task_id', column2='user_id', string='Assignees', context={'active_test': False}, tracking=True)
# User names displayed in project sharing views
portal_user_names = fields.Char(compute='_compute_portal_user_names', compute_sudo=True, search='_search_portal_user_names')
# Second Many2many containing the actual personal stage for the current user
@@ -1015,7 +1015,7 @@ class Task(models.Model):
allow_task_dependencies = fields.Boolean(related='project_id.allow_task_dependencies')
# Tracking of this field is done in the write function
depend_on_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="task_id",
column2="depends_on_id", string="Blocked By",
column2="depends_on_id", string="Blocked By", tracking=True,
domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]")
dependent_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="depends_on_id",
column2="task_id", string="Block",
@@ -1763,41 +1763,7 @@ class Task(models.Model):
# Track user_ids to send assignment notifications
old_user_ids = {t: t.user_ids for t in self}
# X2Many Field Tracking
# Extract to a separate function if necessary
x2m_tracked_fields = {'user_ids', 'depend_on_ids'}
x2m_vals_common_fields = vals.keys() & x2m_tracked_fields
x2m_tracking_values = dict()
# Structured like so
# {
# task: {
# field_name: (value, display_value)
# }
# }
if not self._context.get('mail_notrack') and x2m_vals_common_fields:
# Compute the value before the changes
for task in self:
task_values = x2m_tracking_values.setdefault(task, {})
for field in x2m_vals_common_fields:
task_values[field] = (task[field], ', '.join(record.display_name for record in task[field]))
result = super(Task, tasks).write(vals)
if x2m_tracking_values:
for task, tracking_values in x2m_tracking_values.items():
# Compile the different changes
MailTracking = self.env['mail.tracking.value']
tracking_value_ids = []
# Use mail.tracking.value to track our changes, this is to use the same format as the default tracking one
# we just hack the record to think it is a text field and compile the data ourself beforehand
for field in tracking_values:
if task[field] != tracking_values[field][0]:
field_desc = task._fields[field]._description_string(self.env)
old_value = tracking_values[field][1]
new_value = ', '.join(record.display_name for record in task[field])
tracking_value_ids.append((0, 0, MailTracking.create_tracking_values(
old_value, new_value, field, {'type': 'char', 'string': field_desc}, 100, self._name)))
if tracking_value_ids:
task._message_log(tracking_value_ids=tracking_value_ids)
self._task_message_auto_subscribe_notify({task: task.user_ids - old_user_ids[task] - self.env.user for task in self})
@@ -1903,8 +1869,21 @@ class Task(models.Model):
return new_followers
def _mail_track(self, tracked_fields, initial_values):
result = super()._mail_track(tracked_fields, initial_values)
changes, tracking_value_ids = result
changes, tracking_value_ids = super()._mail_track(tracked_fields, initial_values)
# Many2many tracking
if len(changes) > len(tracking_value_ids):
for changed_field in changes:
if tracked_fields[changed_field]['type'] in ['one2many', 'many2many']:
field = self.env['ir.model.fields']._get(self._name, changed_field)
vals = {
'field': field.id,
'field_desc': field.field_description,
'field_type': field.ttype,
'tracking_sequence': field.tracking,
'old_value_char': ', '.join(initial_values[changed_field].mapped('name')),
'new_value_char': ', '.join(self[changed_field].mapped('name')),
}
tracking_value_ids.append(Command.create(vals))
# Track changes on depending tasks
depends_tracked_fields = self._get_depends_tracked_fields()
depends_changes = changes & depends_tracked_fields
@@ -1930,7 +1909,7 @@ class Task(models.Model):
})
for p in parent_ids:
p.message_post(body=body, subtype_id=subtype, tracking_value_ids=depends_tracking_value_ids)
return result
return changes, tracking_value_ids
def _track_template(self, changes):
res = super(Task, self)._track_template(changes)
+1
View File
@@ -19,3 +19,4 @@ from . import test_multicompany
from . import test_personal_stages
from . import test_task_dependencies
from . import test_task_follow
from . import test_task_tracking
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from odoo.tests import tagged
from odoo.addons.project.tests.test_project_base import TestProjectCommon
@tagged('-at_install', 'post_install')
class TestTaskTracking(TestProjectCommon):
def flush_tracking(self):
""" Force the creation of tracking values. """
self.env['base'].flush()
self.cr.precommit.run()
def test_many2many_tracking(self):
# Basic test
# Assign new user
self.cr.precommit.clear()
self.task_1.user_ids += self.user_projectmanager
self.flush_tracking()
self.assertEqual(len(self.task_1.message_ids), 1,
"Assigning a new user should log a message.")
# No change
self.task_1.user_ids += self.user_projectmanager
self.flush_tracking()
self.assertEqual(len(self.task_1.message_ids), 1,
"Assigning an already assigned user should not log a message.")
# Removing assigness
self.task_1.user_ids = False
self.flush_tracking()
self.assertEqual(len(self.task_1.message_ids), 2,
"Removing both assignees should only log one message.")
def test_many2many_tracking_context(self):
# Test that the many2many tracking does not throw an error when using
# default values for fields that exists both on tasks and on messages
# Using an invalid value for this test
self.task_1.with_context(default_parent_id=-1).user_ids += self.user_projectmanager