mirror of
https://github.com/bringout/oca-ocb-project.git
synced 2026-04-20 02:41:58 +02:00
19.0 vanilla
This commit is contained in:
parent
a2f74aefd8
commit
4a4d12c333
844 changed files with 212348 additions and 270090 deletions
|
|
@ -1,12 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.osv import expression
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.fields import Command, Domain
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import Form
|
||||
|
||||
from .test_project_base import TestProjectCommon
|
||||
|
||||
|
|
@ -74,6 +71,19 @@ class TestProjectSharingCommon(TestProjectCommon):
|
|||
view=self.project_sharing_form_view_xml_id
|
||||
)
|
||||
|
||||
def get_project_share_link(self):
|
||||
self.env['project.share.wizard'].create({
|
||||
'res_model': 'project.project',
|
||||
'res_id': self.project_no_collabo.id,
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id, 'access_mode': 'edit'}),
|
||||
],
|
||||
}).action_send_mail()
|
||||
return self.env['mail.message'].search([
|
||||
('partner_ids', 'in', self.user_portal.partner_id.id),
|
||||
])
|
||||
|
||||
|
||||
@tagged('project_sharing')
|
||||
class TestProjectSharing(TestProjectSharingCommon):
|
||||
|
||||
|
|
@ -86,24 +96,162 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
2) Check if no access rights are given to a portal user
|
||||
3) Add access rights to a portal user
|
||||
"""
|
||||
project_share_wizard = self.env['project.share.wizard'].create({
|
||||
'res_model': 'project.project',
|
||||
'res_id': self.project_portal.id,
|
||||
'access_mode': 'edit',
|
||||
})
|
||||
self.assertFalse(project_share_wizard.partner_ids, 'No collaborator should be in the wizard.')
|
||||
self.assertFalse(self.project_portal.with_user(self.user_portal)._check_project_sharing_access(), 'The portal user should not have accessed in project sharing views.')
|
||||
project_share_wizard.write({'partner_ids': [Command.link(self.user_portal.partner_id.id)]})
|
||||
self.project_portal.message_unsubscribe(partner_ids=self.user_portal.partner_id.ids)
|
||||
project_share_form = Form(self.env['project.share.wizard'].with_context(active_model='project.project', active_id=self.project_portal.id))
|
||||
self.assertFalse(project_share_form.collaborator_ids, 'No collaborator should be in the wizard.')
|
||||
with self.assertRaises(AccessError, msg='The public user should not have any access to project sharing feature of the portal project.'):
|
||||
self.project_portal.with_user(self.user_portal)._check_project_sharing_access()
|
||||
with project_share_form.collaborator_ids.new() as collaborator_form:
|
||||
collaborator_form.partner_id = self.user_portal.partner_id
|
||||
collaborator_form.access_mode = 'edit'
|
||||
project_share_wizard = project_share_form.save()
|
||||
project_share_wizard.action_send_mail()
|
||||
self.assertEqual(len(self.project_portal.collaborator_ids), 1, 'The access right added in project share wizard should be added in the project when the user confirm the access in the wizard.')
|
||||
self.assertDictEqual({
|
||||
'partner_id': self.project_portal.collaborator_ids.partner_id,
|
||||
'project_id': self.project_portal.collaborator_ids.project_id,
|
||||
'limited_access': self.project_portal.collaborator_ids.limited_access,
|
||||
}, {
|
||||
'partner_id': self.user_portal.partner_id,
|
||||
'project_id': self.project_portal,
|
||||
'limited_access': False,
|
||||
}, 'The access rights added should be the read access for the portal project for Chell Gladys.')
|
||||
self.assertTrue(self.project_portal.with_user(self.user_portal)._check_project_sharing_access(), 'The portal user should have read access to the portal project with project sharing feature.')
|
||||
project_share_wizard = self.env['project.share.wizard'].with_context(active_model="project.project", active_id=self.project_portal.id).new({})
|
||||
self.assertEqual(len(project_share_wizard.collaborator_ids), 1, 'The access right added in project share wizard should be added in the project when the user confirm the access in the wizard.')
|
||||
self.assertDictEqual({
|
||||
'partner_id': project_share_wizard.collaborator_ids.partner_id,
|
||||
'access_mode': project_share_wizard.collaborator_ids.access_mode,
|
||||
}, {
|
||||
'partner_id': self.user_portal.partner_id,
|
||||
'access_mode': 'edit',
|
||||
})
|
||||
|
||||
def test_project_share_wizard_add_collaborator_with_limited_access(self):
|
||||
ProjectShare = self.env['project.share.wizard'].with_context(active_model="project.project", active_id=self.project_portal.id)
|
||||
self.project_portal.write({
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.partner_1.id}),
|
||||
],
|
||||
})
|
||||
self.project_portal.message_unsubscribe(partner_ids=[self.user_portal.partner_id.id])
|
||||
project_share_form = Form(ProjectShare)
|
||||
self.assertEqual(len(project_share_form.collaborator_ids), 1)
|
||||
with project_share_form.collaborator_ids.new() as collaborator_form:
|
||||
collaborator_form.partner_id = self.user_portal.partner_id
|
||||
collaborator_form.access_mode = 'edit_limited'
|
||||
project_share_wizard = project_share_form.save()
|
||||
project_share_wizard.action_send_mail()
|
||||
self.assertEqual(len(self.project_portal.collaborator_ids), 2, 'The access right added in project share wizard should be added in the project when the user confirm the access in the wizard.')
|
||||
self.assertEqual(self.project_portal.collaborator_ids.partner_id, self.user_portal.partner_id + self.partner_1)
|
||||
for collaborator in self.project_portal.collaborator_ids:
|
||||
collaborator_vals = {
|
||||
'partner_id': collaborator.partner_id,
|
||||
'project_id': collaborator.project_id,
|
||||
'limited_access': collaborator.limited_access,
|
||||
}
|
||||
if collaborator.partner_id == self.user_portal.partner_id:
|
||||
self.assertDictEqual(collaborator_vals, {
|
||||
'partner_id': self.user_portal.partner_id,
|
||||
'project_id': self.project_portal,
|
||||
'limited_access': True,
|
||||
})
|
||||
else:
|
||||
self.assertDictEqual(collaborator_vals, {
|
||||
'partner_id': self.partner_1,
|
||||
'project_id': self.project_portal,
|
||||
'limited_access': False,
|
||||
})
|
||||
self.assertTrue(self.project_portal.with_user(self.user_portal)._check_project_sharing_access(), 'The portal user should have read access to the portal project with project sharing feature.')
|
||||
|
||||
project_share_wizard = ProjectShare.new({})
|
||||
self.assertEqual(len(project_share_wizard.collaborator_ids), 2, 'The access right added in project share wizard should be added in the project when the user confirm the access in the wizard.')
|
||||
for collaborator in project_share_wizard.collaborator_ids:
|
||||
collaborator_vals = {
|
||||
'partner_id': collaborator.partner_id,
|
||||
'access_mode': collaborator.access_mode,
|
||||
}
|
||||
if collaborator.partner_id == self.user_portal.partner_id:
|
||||
self.assertDictEqual(collaborator_vals, {
|
||||
'partner_id': self.user_portal.partner_id,
|
||||
'access_mode': 'edit_limited',
|
||||
})
|
||||
else:
|
||||
self.assertDictEqual(collaborator_vals, {
|
||||
'partner_id': self.partner_1,
|
||||
'access_mode': 'edit',
|
||||
})
|
||||
|
||||
def test_project_share_wizard_remove_collaborators(self):
|
||||
PortalShare = self.env['project.share.wizard'].with_context(active_model="project.project", active_id=self.project_portal.id)
|
||||
self.project_portal.write({
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id}),
|
||||
Command.create({'partner_id': self.partner_1.id, 'limited_access': True}),
|
||||
],
|
||||
})
|
||||
self.project_portal.message_subscribe(partner_ids=[self.partner_2.id])
|
||||
with Form(PortalShare) as project_share_form:
|
||||
self.assertEqual(len(project_share_form.collaborator_ids), 3, "2 external collaborators should be found for that project.")
|
||||
collaborator_vals_per_id = project_share_form.collaborator_ids._field_value._data
|
||||
collaborator_access_mode_per_partner_id = {
|
||||
c['partner_id']: c['access_mode']
|
||||
for c in collaborator_vals_per_id.values()
|
||||
}
|
||||
self.assertIn(self.user_portal.partner_id.id, collaborator_access_mode_per_partner_id)
|
||||
self.assertIn(self.partner_1.id, collaborator_access_mode_per_partner_id)
|
||||
self.assertIn(self.partner_2.id, collaborator_access_mode_per_partner_id)
|
||||
access_mode_expected_per_partner_id = {
|
||||
self.user_portal.partner_id.id: 'edit',
|
||||
self.partner_1.id: 'edit_limited',
|
||||
self.partner_2.id: 'read',
|
||||
}
|
||||
self.assertDictEqual(collaborator_access_mode_per_partner_id, access_mode_expected_per_partner_id)
|
||||
collaborator_ids_to_remove = {c_id for c_id, vals in collaborator_vals_per_id.items() if vals['access_mode'] != 'read'}
|
||||
index = 0
|
||||
for collaborator_id in project_share_form.collaborator_ids.ids:
|
||||
if collaborator_id in collaborator_ids_to_remove:
|
||||
project_share_form.collaborator_ids.remove(index)
|
||||
else:
|
||||
index += 1
|
||||
|
||||
self.assertFalse(self.project_portal.collaborator_ids)
|
||||
self.assertIn(self.partner_2, self.project_portal.message_partner_ids, "The readonly partner should still be a follower.")
|
||||
self.assertNotIn(self.user_portal.partner_id, self.project_portal.message_partner_ids, "The readonly partner should still be a follower.")
|
||||
self.assertNotIn(self.partner_1, self.project_portal.message_partner_ids, "The readonly partner should still be a follower.")
|
||||
|
||||
def test_project_share_wizard_alter_access_mode_collaborators(self):
|
||||
ProjectShare = self.env['project.share.wizard'].with_context(active_model="project.project", active_id=self.project_portal.id)
|
||||
self.project_portal.write({
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id}),
|
||||
Command.create({'partner_id': self.partner_1.id, 'limited_access': True}),
|
||||
],
|
||||
'message_partner_ids': [ # readonly access
|
||||
Command.link(self.partner_2.id),
|
||||
],
|
||||
})
|
||||
with Form(ProjectShare) as project_share_form:
|
||||
access_updated_per_partner_id = {
|
||||
self.user_portal.partner_id.id: 'edit_limited',
|
||||
self.partner_2.id: 'edit',
|
||||
}
|
||||
for index in range(len(project_share_form.collaborator_ids.ids)):
|
||||
with project_share_form.collaborator_ids.edit(index) as collaborator_form:
|
||||
if collaborator_form.partner_id.id in access_updated_per_partner_id:
|
||||
collaborator_form.access_mode = access_updated_per_partner_id[collaborator_form.partner_id.id]
|
||||
|
||||
self.assertEqual(len(self.project_portal.collaborator_ids), 3, "3 collaborators should be found for that project.")
|
||||
self.assertEqual(
|
||||
self.project_portal.collaborator_ids.partner_id,
|
||||
self.user_portal.partner_id + self.partner_1 + self.partner_2,
|
||||
"The collaborators should be the portal user, Valid Lelitre and Valid Poilvache.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.project_portal.collaborator_ids.filtered(lambda c: c.limited_access).partner_id,
|
||||
self.user_portal.partner_id + self.partner_1,
|
||||
"The portal user and Valid Lelitre should have limited access.",
|
||||
)
|
||||
|
||||
def test_project_sharing_access(self):
|
||||
""" Check if the different user types can access to project sharing feature as expected. """
|
||||
|
|
@ -125,7 +273,6 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
3) Give the 'edit' access mode to a portal user in a project and try to create task with this user.
|
||||
3.1) Try to change the project of the new task with this user.
|
||||
"""
|
||||
self.project_portal.allow_subtasks = True
|
||||
Task = self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_portal.id, 'default_user_ids': [(4, self.user_portal.id)]})
|
||||
# 1) Give the 'read' access mode to a portal user in a project and try to create task with this user.
|
||||
with self.assertRaises(AccessError, msg="Should not accept the portal user create a task in the project when he has not the edit access right."):
|
||||
|
|
@ -146,6 +293,7 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
self.assertEqual(task.name, 'Test')
|
||||
self.assertEqual(task.project_id, self.project_portal)
|
||||
self.assertFalse(task.portal_user_names)
|
||||
self.assertTrue(task.stage_id)
|
||||
|
||||
# Check creating a sub-task while creating the parent task works as expected.
|
||||
self.assertEqual(task.child_ids.name, 'Test Subtask')
|
||||
|
|
@ -170,32 +318,40 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
task = Task.with_context(default_parent_id=self.task_no_collabo.id).create({'name': 'foo'})
|
||||
|
||||
# Create/Update a forbidden task through child_ids
|
||||
with self.assertRaisesRegex(AccessError, "You cannot write on description"):
|
||||
Task.create({'name': 'foo', 'child_ids': [Command.create({'name': 'Foo', 'description': 'Foo'})]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
Task.create({'name': 'foo', 'child_ids': [Command.update(self.task_no_collabo.id, {'name': 'Foo'})]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to delete 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
Task.create({'name': 'foo', 'child_ids': [Command.delete(self.task_no_collabo.id)]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
Task.create({'name': 'foo', 'child_ids': [Command.unlink(self.task_no_collabo.id)]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
Task.create({'name': 'foo', 'child_ids': [Command.link(self.task_no_collabo.id)]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
Task.create({'name': 'foo', 'child_ids': [Command.set([self.task_no_collabo.id])]})
|
||||
|
||||
# Same thing but using context defaults
|
||||
with self.assertRaisesRegex(AccessError, "You cannot write on description"):
|
||||
Task.with_context(default_child_ids=[Command.create({'name': 'Foo', 'description': 'Foo'})]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
# However, cache is updated, but nothing is written.
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
Task.with_context(default_child_ids=[Command.update(self.task_no_collabo.id, {'name': 'Foo'})]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to delete 'Task'"):
|
||||
Task.with_context(default_child_ids=[Command.delete(self.task_no_collabo.id)]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
Task.with_context(default_child_ids=[Command.unlink(self.task_no_collabo.id)]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
Task.with_context(default_child_ids=[Command.link(self.task_no_collabo.id)]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
Task.with_context(default_child_ids=[Command.set([self.task_no_collabo.id])]).create({'name': 'foo'})
|
||||
with Task.env.cr.savepoint() as sp:
|
||||
task = Task.with_context(default_child_ids=[Command.delete(self.task_no_collabo.id)]).create({'name': 'foo'})
|
||||
task.env.invalidate_all()
|
||||
self.assertTrue(self.task_no_collabo.exists(), "Task should still be there, no delete is sent")
|
||||
sp.rollback()
|
||||
with self.env.cr.savepoint() as sp:
|
||||
self.task_no_collabo.parent_id = self.task_no_collabo.create({'name': 'parent collabo'})
|
||||
task = Task.with_context(default_child_ids=[Command.unlink(self.task_no_collabo.id)]).create({'name': 'foo'})
|
||||
task.env.invalidate_all()
|
||||
self.assertTrue(self.task_no_collabo.parent_id, "Task should still be there, no delete is sent")
|
||||
sp.rollback()
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task = Task.with_context(default_child_ids=[Command.link(self.task_no_collabo.id)]).create({'name': 'foo'})
|
||||
task.env.invalidate_all()
|
||||
self.assertFalse(task.child_ids)
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task = Task.with_context(default_child_ids=[Command.set([self.task_no_collabo.id])]).create({'name': 'foo'})
|
||||
task.env.invalidate_all()
|
||||
self.assertFalse(task.child_ids)
|
||||
|
||||
# Create/update a tag through tag_ids
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to create 'Project Tags'"):
|
||||
|
|
@ -208,15 +364,23 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
# Same thing but using context defaults
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to create 'Project Tags'"):
|
||||
Task.with_context(default_tag_ids=[Command.create({'name': 'Bar'})]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Project Tags'"):
|
||||
Task.with_context(default_tag_ids=[Command.update(self.task_tag.id, {'name': 'Bar'})]).create({'name': 'foo'})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to delete 'Project Tags'"):
|
||||
with Task.env.cr.savepoint() as sp:
|
||||
task = Task.with_context(default_tag_ids=[Command.update(self.task_tag.id, {'name': 'Bar'})]).create({'name': 'foo'})
|
||||
task.env.invalidate_all()
|
||||
self.assertNotEqual(self.task_tag.name, 'Bar')
|
||||
sp.rollback()
|
||||
with Task.env.cr.savepoint() as sp:
|
||||
Task.with_context(default_tag_ids=[Command.delete(self.task_tag.id)]).create({'name': 'foo'})
|
||||
task.env.invalidate_all()
|
||||
self.assertTrue(self.task_tag.exists())
|
||||
sp.rollback()
|
||||
|
||||
task = Task.create({'name': 'foo', 'tag_ids': [Command.link(self.task_tag.id)]})
|
||||
task = Task.create({'name': 'foo', 'color': 1, 'tag_ids': [Command.link(self.task_tag.id)]})
|
||||
self.assertEqual(task.color, 1)
|
||||
self.assertEqual(task.tag_ids, self.task_tag)
|
||||
|
||||
Task.create({'name': 'foo', 'tag_ids': [Command.set([self.task_tag.id])]})
|
||||
task = Task.create({'name': 'foo', 'color': 4, 'tag_ids': [Command.set([self.task_tag.id])]})
|
||||
self.assertEqual(task.color, 4)
|
||||
self.assertEqual(task.tag_ids, self.task_tag)
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.addons.base.models.ir_rule')
|
||||
|
|
@ -231,11 +395,9 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
3.1) Try to change the project of the new task with this user.
|
||||
3.2) Create a sub-task
|
||||
3.3) Create a second sub-task
|
||||
4.1) Restrict to edit with limited access and try to edit a task with and without following it
|
||||
4.2) Restrict to read and check he can no longer edit the tasks, even if he is within the followers
|
||||
"""
|
||||
# 0) Allow to create subtasks in the project tasks
|
||||
# Required for `child_ids` to be visible in the view
|
||||
# {'invisible': [('allow_subtasks', '=', False)]}
|
||||
self.project_cows.allow_subtasks = True
|
||||
# 1) Give the 'read' access mode to a portal user in a project and try to create task with this user.
|
||||
with self.assertRaises(AccessError, msg="Should not accept the portal user create a task in the project when he has not the edit access right."):
|
||||
with self.get_project_sharing_form_view(self.task_cow.with_context({'tracking_disable': True, 'default_project_id': self.project_cows.id}), self.user_portal) as form:
|
||||
|
|
@ -243,15 +405,15 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
task = form.save()
|
||||
|
||||
project_share_wizard = self.env['project.share.wizard'].create({
|
||||
'access_mode': 'edit',
|
||||
'res_model': 'project.project',
|
||||
'res_id': self.project_cows.id,
|
||||
'partner_ids': [
|
||||
Command.link(self.user_portal.partner_id.id),
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id, 'access_mode': 'edit'}),
|
||||
],
|
||||
})
|
||||
project_share_wizard.action_send_mail()
|
||||
|
||||
# the portal user is set as follower for the task_cow. Without it he does not have read access to the task, and thus can not access its view form
|
||||
self.task_cow.message_subscribe(partner_ids=self.user_portal.partner_id.ids)
|
||||
with self.get_project_sharing_form_view(self.task_cow.with_context({'tracking_disable': True, 'default_project_id': self.project_cows.id, 'uid': self.user_portal.id}), self.user_portal) as form:
|
||||
form.name = 'Test'
|
||||
task = form.save()
|
||||
|
|
@ -268,7 +430,7 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
with form.child_ids.new() as subtask_form:
|
||||
subtask_form.name = 'Test Subtask'
|
||||
with self.assertRaises(AssertionError, msg="Should not accept the portal user changes the project of the task."):
|
||||
subtask_form.display_project_id = self.project_portal
|
||||
subtask_form.project_id = self.project_portal
|
||||
self.assertEqual(task.child_ids.name, 'Test Subtask')
|
||||
self.assertEqual(task.child_ids.project_id, self.project_cows)
|
||||
self.assertFalse(task.child_ids.portal_user_names, 'by default no user should be assigned to a subtask created by the portal user.')
|
||||
|
|
@ -298,17 +460,15 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
task.write({'parent_id': self.task_no_collabo.id})
|
||||
|
||||
# Create/Update a forbidden task through child_ids
|
||||
with self.assertRaisesRegex(AccessError, "You cannot write on description"):
|
||||
task.write({'child_ids': [Command.create({'name': 'Foo', 'description': 'Foo'})]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task.write({'child_ids': [Command.update(self.task_no_collabo.id, {'name': 'Foo'})]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to delete 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task.write({'child_ids': [Command.delete(self.task_no_collabo.id)]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task.write({'child_ids': [Command.unlink(self.task_no_collabo.id)]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task.write({'child_ids': [Command.link(self.task_no_collabo.id)]})
|
||||
with self.assertRaisesRegex(AccessError, "not allowed to modify 'Task'"):
|
||||
with self.assertRaisesRegex(AccessError, "top-secret records"):
|
||||
task.write({'child_ids': [Command.set([self.task_no_collabo.id])]})
|
||||
|
||||
# Create/update a tag through tag_ids
|
||||
|
|
@ -332,6 +492,44 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
task.write({'tag_ids': [Command.set([self.task_tag.id])]})
|
||||
self.assertEqual(task.tag_ids, self.task_tag)
|
||||
|
||||
# 4.1) Restrict the collaborator access to edit with limited access, restricting the collaborator to edit task
|
||||
# on which he is in the followers only
|
||||
self.env['project.share.wizard'].create({
|
||||
'res_model': 'project.project',
|
||||
'res_id': self.project_cows.id,
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id, 'access_mode': 'edit_limited'}),
|
||||
],
|
||||
})
|
||||
self.assertTrue(self.project_cows.collaborator_ids.limited_access)
|
||||
|
||||
# Removing the collaborator from the followers prevents him to edit the task
|
||||
task.sudo().message_partner_ids -= self.user_portal.partner_id
|
||||
with self.assertRaises(AccessError):
|
||||
task.write({'name': 'foo'})
|
||||
|
||||
# Adding the collaborator back to the followers grants him to edit the task
|
||||
task.sudo().message_partner_ids += self.user_portal.partner_id
|
||||
task.write({'name': 'foo'})
|
||||
|
||||
# 4.2) Restrict the access to read and check he can no longer edit the tasks, even if he is within the followers
|
||||
self.env['project.share.wizard'].create({
|
||||
'res_model': 'project.project',
|
||||
'res_id': self.project_cows.id,
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id, 'access_mode': 'read'}),
|
||||
# Create a second collaborator with edit just so that the project sharing record rules
|
||||
# do not get automatically disabled when removing the last remaining edit collaborator
|
||||
Command.create({'partner_id': self.env['res.partner'].create({'name': 'Alain'}).id, 'access_mode': 'edit'}),
|
||||
],
|
||||
})
|
||||
# Sanity check: Assert the project sharing record rule is still active
|
||||
self.assertTrue(self.env.ref('project.project_task_rule_portal_project_sharing').active)
|
||||
|
||||
# Assert the collaborator can no longer write on the task despite he is still in the followers of the task
|
||||
self.assertIn(self.user_portal.partner_id, task.sudo().message_partner_ids)
|
||||
with self.assertRaises(AccessError):
|
||||
task.write({'name': 'foo'})
|
||||
|
||||
def test_portal_user_cannot_see_all_assignees(self):
|
||||
""" Test when the portal sees a task he cannot see all the assignees.
|
||||
|
|
@ -350,15 +548,15 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
self.assertEqual(len(self.task_cow.user_ids), 2, '2 users should be assigned in this task.')
|
||||
|
||||
project_share_wizard = self.env['project.share.wizard'].create({
|
||||
'access_mode': 'edit',
|
||||
'res_model': 'project.project',
|
||||
'res_id': self.project_cows.id,
|
||||
'partner_ids': [
|
||||
Command.link(self.user_portal.partner_id.id),
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id, 'access_mode': 'edit'}),
|
||||
],
|
||||
})
|
||||
project_share_wizard.action_send_mail()
|
||||
|
||||
# subscribe the portal user to give him read access to the task.
|
||||
self.task_cow.message_subscribe(partner_ids=self.user_portal.partner_id.ids)
|
||||
self.assertFalse(self.task_cow.with_user(self.user_portal).user_ids, 'the portal user should see no assigness in the task.')
|
||||
task_portal_read = self.task_cow.with_user(self.user_portal).read(['portal_user_names'])
|
||||
self.assertEqual(self.task_cow.portal_user_names, task_portal_read[0]['portal_user_names'], 'the portal user should see assignees name in the task via the `portal_user_names` field.')
|
||||
|
|
@ -370,65 +568,52 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
if a email template is set in `rating_template_id` field in the new stage.
|
||||
"""
|
||||
self.project_portal.write({
|
||||
'rating_active': True,
|
||||
'rating_status': 'stage',
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id}),
|
||||
],
|
||||
})
|
||||
self.task_portal.with_user(self.user_portal).write({'stage_id': self.project_portal.type_ids[-1].id})
|
||||
stage = self.project_portal.type_ids[-1]
|
||||
stage.write({
|
||||
'rating_active': True,
|
||||
'rating_status': 'stage',
|
||||
})
|
||||
self.task_portal.with_user(self.user_portal).write({'stage_id': stage.id})
|
||||
|
||||
def test_orm_method_with_true_false_domain(self):
|
||||
""" Test orm method overriden in project for project sharing works with TRUE_LEAF/FALSE_LEAF
|
||||
""" Test orm method overriden in project for project sharing works
|
||||
|
||||
Test Case
|
||||
=========
|
||||
1) Share a project in edit mode for portal user
|
||||
2) Search the portal task contained in the project shared by using a domain with TRUE_LEAF
|
||||
2) Search the portal task contained in the project shared by using a TRUE domain
|
||||
3) Check the task is found with the `search` method
|
||||
4) filter the task with `TRUE_DOMAIN` and check if the task is always returned by `filtered_domain` method
|
||||
5) filter the task with `FALSE_DOMAIN` and check if no task is returned by `filtered_domain` method
|
||||
6) Search the task with `FALSE_LEAF` and check no task is found with `search` method
|
||||
7) Call `read_group` method with `TRUE_LEAF` in the domain and check if the task is found
|
||||
8) Call `read_group` method with `FALSE_LEAF` in the domain and check if no task is found
|
||||
4) Search the task with `FALSE` and check no task is found with `search` method
|
||||
5) Call `read_group` method with `TRUE` in the domain and check if the task is found
|
||||
6) Call `read_group` method with `FALSE` in the domain and check if no task is found
|
||||
"""
|
||||
domain = [('id', '=', self.task_portal.id)]
|
||||
domain = Domain('id', '=', self.task_portal.id)
|
||||
self.project_portal.write({
|
||||
'collaborator_ids': [Command.create({
|
||||
'partner_id': self.user_portal.partner_id.id,
|
||||
})],
|
||||
})
|
||||
task = self.env['project.task'].with_user(self.user_portal).search(
|
||||
expression.AND([
|
||||
expression.TRUE_DOMAIN,
|
||||
domain,
|
||||
])
|
||||
)
|
||||
task = self.env['project.task'].with_user(self.user_portal).search(domain)
|
||||
self.assertTrue(task, 'The task should be found.')
|
||||
self.assertEqual(task, task.filtered_domain(expression.TRUE_DOMAIN), 'The task found should be kept since the domain is truly')
|
||||
self.assertFalse(task.filtered_domain(expression.FALSE_DOMAIN), 'The task should not be found since the domain is falsy')
|
||||
task = self.env['project.task'].with_user(self.user_portal).search(
|
||||
expression.AND([
|
||||
expression.FALSE_DOMAIN,
|
||||
domain,
|
||||
]),
|
||||
)
|
||||
task = self.env['project.task'].with_user(self.user_portal).search(Domain.FALSE)
|
||||
self.assertFalse(task, 'No task should be found since the domain contained a falsy tuple.')
|
||||
|
||||
task_read_group = self.env['project.task'].read_group(
|
||||
expression.AND([expression.TRUE_DOMAIN, domain]),
|
||||
['id'],
|
||||
[],
|
||||
task_read_group = self.env['project.task'].formatted_read_group(
|
||||
domain,
|
||||
aggregates=['id:min', '__count'],
|
||||
)
|
||||
self.assertEqual(task_read_group[0]['__count'], 1, 'The task should be found with the read_group method containing a truly tuple.')
|
||||
self.assertEqual(task_read_group[0]['id'], self.task_portal.id, 'The task should be found with the read_group method containing a truly tuple.')
|
||||
self.assertEqual(task_read_group[0]['__count'], 1, 'The task should be found with the formatted_read_group method containing a truly tuple.')
|
||||
self.assertEqual(task_read_group[0]['id:min'], self.task_portal.id, 'The task should be found with the formatted_read_group method containing a truly tuple.')
|
||||
|
||||
task_read_group = self.env['project.task'].read_group(
|
||||
expression.AND([expression.FALSE_DOMAIN, domain]),
|
||||
['id'],
|
||||
[],
|
||||
task_read_group = self.env['project.task'].formatted_read_group(
|
||||
Domain.FALSE,
|
||||
aggregates=['__count'],
|
||||
)
|
||||
self.assertFalse(task_read_group[0]['__count'], 'No result should found with the read_group since the domain is falsy.')
|
||||
self.assertFalse(task_read_group[0]['__count'], 'No result should found with the formatted_read_group since the domain is falsy.')
|
||||
|
||||
def test_milestone_read_access_right(self):
|
||||
""" This test ensures that a portal user has read access on the milestone of the project that was shared with him """
|
||||
|
|
@ -456,3 +641,99 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||
'name': 'Test Project new Milestone',
|
||||
'project_id': self.project_portal.id,
|
||||
})
|
||||
|
||||
def test_add_followers_from_share_edit_wizard(self):
|
||||
"""
|
||||
This test ensures that when a project is shared in edit mode, the partners are correctly set as follower in the project and their respective tasks.
|
||||
"""
|
||||
company_partner = self.env.company.partner_id
|
||||
partners = partner_a, partner_b, partner_d = self.env['res.partner'].create([
|
||||
{'name': "Solanum", 'parent_id': company_partner.id},
|
||||
{'name': "Zana", 'parent_id': company_partner.id},
|
||||
{'name': "Thresh"},
|
||||
])
|
||||
partners |= company_partner
|
||||
project_to_share = self.env['project.project'].create({'name': "project to share"})
|
||||
task_with_partner_1, task_with_partner_2, task_with_parent_partner, task_without_partner = self.env['project.task'].create([{
|
||||
'name': "Task with partner 1",
|
||||
'partner_id': partner_a.id,
|
||||
'project_id': project_to_share.id,
|
||||
}, {
|
||||
'name': "Task with partner 2",
|
||||
'partner_id': partner_b.id,
|
||||
'project_id': project_to_share.id,
|
||||
}, {
|
||||
'name': "Task with company",
|
||||
'partner_id': company_partner.id,
|
||||
'project_id': project_to_share.id,
|
||||
}, {
|
||||
'name': "Task with no partner",
|
||||
'project_id': project_to_share.id,
|
||||
}])
|
||||
project_to_share._add_followers(partners)
|
||||
|
||||
self.assertEqual(partners, project_to_share.message_partner_ids, "All the partner should be set as a new follower of the project")
|
||||
self.assertEqual(partner_a, task_with_partner_1.message_partner_ids, "Only the first partner should be set as a new follower for the task 1")
|
||||
self.assertEqual(partner_b, task_with_partner_2.message_partner_ids, "Only the second partner should be set as a new follower for the task 2")
|
||||
self.assertEqual(partners - partner_d, task_with_parent_partner.message_partner_ids,
|
||||
"The first, second, and the company partner should be set as new followers for the task 3 because the partner of this task is the parent of the other 2")
|
||||
self.assertFalse(task_without_partner.message_partner_ids, "Since this task has no partner, no follower should be added")
|
||||
|
||||
def test_project_manager_remains_follower_after_sharing(self):
|
||||
"""
|
||||
Test that the project manager remains a follower when collaborators are added
|
||||
"""
|
||||
project = self.env['project.project'].with_context({'mail_create_nolog': True}).create({
|
||||
'name': 'project',
|
||||
'privacy_visibility': 'followers',
|
||||
'user_id': self.user_projectmanager.id,
|
||||
})
|
||||
self.assertIn(self.user_projectmanager.partner_id, project.message_partner_ids, "Project manager should be a follower of the project")
|
||||
project_share_wizard = self.env['project.share.wizard'].create({
|
||||
'res_model': 'project.project',
|
||||
'res_id': project.id,
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id, 'access_mode': 'read'}),
|
||||
],
|
||||
})
|
||||
project_share_wizard.action_send_mail()
|
||||
self.assertIn(self.user_projectmanager.partner_id, project.message_partner_ids, "Project manager should still be a follower after sharing the project")
|
||||
self.assertEqual(len(project.message_follower_ids), 2, "number of followers should be 2")
|
||||
|
||||
def test_portal_user_with_edit_rights_can_close_recurring_task(self):
|
||||
"""
|
||||
Test that a portal user with edit rights can close a recurrent task.
|
||||
|
||||
Test Case:
|
||||
==========
|
||||
1) Create a project with a recurrent task.
|
||||
2) Create a portal user and give them edit rights on the project.
|
||||
3) Ensure the portal user can close the recurrent task.
|
||||
"""
|
||||
portal_user = self.env['res.users'].create({
|
||||
'name': 'Portal User',
|
||||
'login': 'portaluser',
|
||||
'email': 'portaluser@odoo.com',
|
||||
'group_ids': [(6, 0, [self.env.ref('base.group_portal').id])],
|
||||
})
|
||||
project = self.env['project.project'].create({
|
||||
'name': 'Project with Portal User',
|
||||
})
|
||||
project.task_ids = [Command.create({
|
||||
'name': 'Recurrent Task',
|
||||
'recurring_task': True,
|
||||
'repeat_type': 'forever',
|
||||
})]
|
||||
self.env['project.share.wizard'].create({
|
||||
'res_model': 'project.project',
|
||||
'res_id': project.id,
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': portal_user.partner_id.id, 'access_mode': 'edit'}),
|
||||
],
|
||||
})
|
||||
task = project.task_ids[0]
|
||||
self.env.invalidate_all()
|
||||
task.with_user(portal_user).write({'state': '1_done'})
|
||||
self.assertEqual(task.state, '1_done', "The portal user with edit rights should be able to mark the task as done.")
|
||||
next_task = task.recurrence_id.task_ids.filtered(lambda t: t != task)
|
||||
self.assertTrue(next_task, "The next occurrence of the recurrent task should be created.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue