From a3a32c66fde744bf1a7b5677a5f7f44c98bfdf76 Mon Sep 17 00:00:00 2001 From: William Braeckman Date: Thu, 30 Dec 2021 13:01:01 +0000 Subject: [PATCH] [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 Signed-off-by: Laurent Stukkens (ltu) --- addons/project/models/project.py | 57 +++++++--------------- addons/project/tests/__init__.py | 1 + addons/project/tests/test_task_tracking.py | 39 +++++++++++++++ 3 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 addons/project/tests/test_task_tracking.py diff --git a/addons/project/models/project.py b/addons/project/models/project.py index fb8904bf4e6..04ac85b7c88 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -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) diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py index 5195e1dfbf1..07a96d5e1af 100644 --- a/addons/project/tests/__init__.py +++ b/addons/project/tests/__init__.py @@ -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 diff --git a/addons/project/tests/test_task_tracking.py b/addons/project/tests/test_task_tracking.py new file mode 100644 index 00000000000..239d576c280 --- /dev/null +++ b/addons/project/tests/test_task_tracking.py @@ -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