From 63777d3c3fc75c8f3c1e94fb91b36f448e5b8315 Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Thu, 18 Feb 2021 15:53:39 +0100 Subject: [PATCH] [IMP] project: check no cyclic task dependencies Before this commit, if we have for instance 3 tasks called A, B and C where A depends on B and B depends on A then we have a cyclic dependency. Another example with the same tasks, we can have a cyclic dependency if A depends on B, B depends on C and C depends on A. This commit checks we have no cyclic dependencies when the user adds a dependency on a task. task-2387984 closes #66740 --- addons/project/models/project.py | 5 +++ .../project/tests/test_task_dependencies.py | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index a14b037cce1..eb41cfa45dc 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -737,6 +737,11 @@ class Task(models.Model): repeat_show_week = fields.Boolean(compute='_compute_repeat_visibility') repeat_show_month = fields.Boolean(compute='_compute_repeat_visibility') + @api.constrains('depend_on_ids') + def _check_no_cyclic_dependencies(self): + if not self._check_m2m_recursion('depend_on_ids'): + raise ValidationError(_("You cannot create cyclic dependency.")) + @api.model def _get_recurrence_fields(self): return ['repeat_interval', 'repeat_unit', 'repeat_type', 'repeat_until', 'repeat_number', diff --git a/addons/project/tests/test_task_dependencies.py b/addons/project/tests/test_task_dependencies.py index 5650780d3fc..42eda7fecb2 100644 --- a/addons/project/tests/test_task_dependencies.py +++ b/addons/project/tests/test_task_dependencies.py @@ -40,3 +40,48 @@ class TestTaskDependencies(TestProjectCommon): 'depend_on_ids': [Command.link(self.task_3.id)], }) self.assertEqual(len(self.task_1.depend_on_ids), 2, "The task 1 should have two dependencies.") + + def test_cyclic_dependencies(self): + """ Test the cyclic dependencies + + Test Case: + ========= + 1) Check initial setting on three tasks + 2) Add task2 as dependency in task1 + 3) Add task3 as dependency in task2 + 4) Add task1 as dependency in task3 and check a validation error is raised + 5) Add task1 as dependency in task2 and check a validation error is raised + """ + # 1) Check initial setting on three tasks + self.assertTrue( + len(self.task_1.depend_on_ids) == len(self.task_2.depend_on_ids) == len(self.task_3.depend_on_ids) == 0, + "The three tasks should depend on no tasks.") + self.assertTrue(self.task_1.allow_task_dependencies, 'The task dependencies feature should be enable.') + self.assertTrue(self.task_2.allow_task_dependencies, 'The task dependencies feature should be enable.') + self.assertTrue(self.task_3.allow_task_dependencies, 'The task dependencies feature should be enable.') + + # 2) Add task2 as dependency in task1 + self.task_1.write({ + 'depend_on_ids': [Command.link(self.task_2.id)], + }) + self.assertEqual(len(self.task_1.depend_on_ids), 1, 'The task 1 should have one dependency.') + + # 3) Add task3 as dependency in task2 + self.task_2.write({ + 'depend_on_ids': [Command.link(self.task_3.id)], + }) + self.assertEqual(len(self.task_2.depend_on_ids), 1, "The task 2 should have one dependency.") + + # 4) Add task1 as dependency in task3 and check a validation error is raised + with self.assertRaises(ValidationError), self.cr.savepoint(): + self.task_3.write({ + 'depend_on_ids': [Command.link(self.task_1.id)], + }) + self.assertEqual(len(self.task_3.depend_on_ids), 0, "The dependency should not be added in the task 3 because of a cyclic dependency.") + + # 5) Add task1 as dependency in task2 and check a validation error is raised + with self.assertRaises(ValidationError), self.cr.savepoint(): + self.task_2.write({ + 'depend_on_ids': [Command.link(self.task_1.id)], + }) + self.assertEqual(len(self.task_2.depend_on_ids), 1, "The number of dependencies should no change in the task 2 because of a cyclic dependency.")