diff --git a/addons/knowledge/models/knowledge_article.py b/addons/knowledge/models/knowledge_article.py index 1ff07ba61f1..8fa05ef2c04 100644 --- a/addons/knowledge/models/knowledge_article.py +++ b/addons/knowledge/models/knowledge_article.py @@ -1156,6 +1156,10 @@ class Article(models.Model): This method takes care of correctly detaching those children and returns a subset of children to which the user effectively has write access to. + As the children are moved to "root" articles we also reset their desync + status. Indeed, a root article cannot be desyncronized as stated by SQL + constraints. + Note: this might produce funny results when involving a hierarchy with invisible nodes in it (A-B-C where B is not achievable). You might archive / privatize articles and break hierarchy without knowing it. @@ -1167,18 +1171,23 @@ class Article(models.Model): other_descendants_sudo = all_descendants_sudo - writable_descendants # copy rights to allow breaking the hierarchy while keeping access for members - for article_sudo in other_descendants_sudo: + # do this on synchronized articles as desynchronized one do not inherit from parent + for article_sudo in other_descendants_sudo.filtered(lambda article: not article.is_desynchronized): article_sudo.write({ 'article_member_ids': article_sudo._copy_access_from_parents_commands() }) - # create new root articles: direct children of these articles + # create new root articles: direct children of these articles, reset desync new_roots_woperm = other_descendants_sudo.filtered(lambda article: article.parent_id in self and not article.internal_permission) new_roots_wperm = other_descendants_sudo.filtered(lambda article: article.parent_id in self and article.internal_permission) if new_roots_wperm: - new_roots_wperm.write({'parent_id': False}) + new_roots_wperm.write({ + 'is_desynchronized': False, + 'parent_id': False + }) for new_root in new_roots_woperm: new_root.write({ + 'is_desynchronized': False, 'internal_permission': new_root.inherited_permission, 'parent_id': False, }) diff --git a/addons/knowledge/tests/test_knowledge_article_business.py b/addons/knowledge/tests/test_knowledge_article_business.py index cb1b7ddcc10..73fbab5393e 100644 --- a/addons/knowledge/tests/test_knowledge_article_business.py +++ b/addons/knowledge/tests/test_knowledge_article_business.py @@ -607,6 +607,78 @@ class TestKnowledgeArticleBusiness(KnowledgeCommonWData): wkspace_child_no_access.with_env(self.env).body wkspace_child_no_access.with_user(self.user_employee2).body + @mute_logger('odoo.addons.base.models.ir_rule', 'odoo.models.unlink') + @users('employee') + def test_article_make_private_w_desynchronized(self): + """ Test a special case when making private: we have desynchronized children. + Children that are de-synchronized should NOT have members from their parent(s) + copied onto them when they are detached. + + Specific setup for this test + # - Playground workspace w+ (customer-r+,employee-w+) + # - Child1 " w+DES (customer-r+,employee-r+) + # - Gd Child1 " " + # - Gd Child2 " " + # - GdGd Child1 " " + # - Child2 " " + # - Gd Child1 " " + # - GdGd Child " " + """ + # add employee on playground and desynchronize its child + self.article_workspace._add_members(self.partner_employee, 'write') + self.workspace_children[0]._set_member_permission( + self.article_workspace.article_member_ids.filtered( + lambda member: member.partner_id == self.partner_employee + ), + 'read', + is_based_on=True, + ) + # check updated data + self.assertTrue(self.workspace_children[0].is_desynchronized) + self.assertMembers( + self.article_workspace, + 'write', + {self.partner_employee: 'write'} + ) + self.assertMembers( + self.workspace_children[0], + 'write', + {self.partner_employee: 'read'} + ) + + article_workspace = self.article_workspace.with_env(self.env) + [workspace_child_desync, workspace_child_tosync] = self.workspace_children.with_env(self.env) + + # add a member on the parent, it will NOT be propagated to the desync child + article_workspace._add_members(self.customer, 'read') + self.assertMembers( + article_workspace, + 'write', + {self.customer: 'read', self.partner_employee: 'write'} + ) + + # now move the article to private and check the post-processing + article_workspace._move_and_make_private() + + # 1. desync article: moved to root (as employee does not have write access) + # and is not desynchronized (root articles are never desynchornized) + # should be moved to root (as employee does not have write access) + self.assertEqual(workspace_child_desync.category, "workspace") + self.assertFalse(workspace_child_desync.is_desynchronized) + self.assertFalse(workspace_child_desync.parent_id) + # it should NOT have had customer access copied onto it as it was desync when we moved it + self.assertMembers( + workspace_child_desync, + "write", + {self.partner_employee: 'read'} + ) + + # 2. sync article: NOT moved to root (as employee has write access) and is + # still sunchronized + self.assertEqual(workspace_child_tosync.category, "private") + self.assertFalse(workspace_child_desync.is_desynchronized) + self.assertEqual(workspace_child_tosync.parent_id, article_workspace) + @mute_logger('odoo.addons.base.models.ir_rule', 'odoo.models.unlink') @users('employee_manager') def test_article_make_private_w_parent(self):