(backend) add commentator role

To allow a user to comment a document we added a new role: commentator.
Commentator is higher than reader but lower than editor.
This commit is contained in:
Manuel Raynaud
2025-08-26 17:55:53 +02:00
committed by Anthony LC
parent 10a319881d
commit 0caee61d86
7 changed files with 248 additions and 61 deletions

View File

@@ -33,6 +33,7 @@ class LinkRoleChoices(PriorityTextChoices):
"""Defines the possible roles a link can offer on a document."""
READER = "reader", _("Reader") # Can read
COMMENTATOR = "commentator", _("Commentator") # Can read and comment
EDITOR = "editor", _("Editor") # Can read and edit
@@ -40,6 +41,7 @@ class RoleChoices(PriorityTextChoices):
"""Defines the possible roles a user can have in a resource."""
READER = "reader", _("Reader") # Can read
COMMENTATOR = "commentator", _("Commentator") # Can read and comment
EDITOR = "editor", _("Editor") # Can read and edit
ADMIN = "administrator", _("Administrator") # Can read, edit, delete and share
OWNER = "owner", _("Owner")

View File

@@ -756,6 +756,7 @@ class Document(MP_Node, BaseModel):
can_update = (
is_owner_or_admin or role == RoleChoices.EDITOR
) and not is_deleted
can_comment = (can_update or role == RoleChoices.COMMENTATOR) and not is_deleted
can_create_children = can_update and user.is_authenticated
can_destroy = (
is_owner
@@ -786,6 +787,7 @@ class Document(MP_Node, BaseModel):
"children_list": can_get,
"children_create": can_create_children,
"collaboration_auth": can_get,
"comment": can_comment,
"content": can_get,
"cors_proxy": can_get,
"descendants": can_get,
@@ -1146,7 +1148,12 @@ class DocumentAccess(BaseAccess):
set_role_to = []
if is_owner_or_admin:
set_role_to.extend(
[RoleChoices.READER, RoleChoices.EDITOR, RoleChoices.ADMIN]
[
RoleChoices.READER,
RoleChoices.COMMENTATOR,
RoleChoices.EDITOR,
RoleChoices.ADMIN,
]
)
if role == RoleChoices.OWNER:
set_role_to.append(RoleChoices.OWNER)

View File

@@ -293,6 +293,7 @@ def test_api_document_accesses_retrieve_set_role_to_child():
}
assert result_dict[str(document_access_other_user.id)] == [
"reader",
"commentator",
"editor",
"administrator",
"owner",
@@ -301,7 +302,7 @@ def test_api_document_accesses_retrieve_set_role_to_child():
# Add an access for the other user on the parent
parent_access_other_user = factories.UserDocumentAccessFactory(
document=parent, user=other_user, role="editor"
document=parent, user=other_user, role="commentator"
)
response = client.get(f"/api/v1.0/documents/{document.id!s}/accesses/")
@@ -314,6 +315,7 @@ def test_api_document_accesses_retrieve_set_role_to_child():
result["id"]: result["abilities"]["set_role_to"] for result in content
}
assert result_dict[str(document_access_other_user.id)] == [
"commentator",
"editor",
"administrator",
"owner",
@@ -321,6 +323,7 @@ def test_api_document_accesses_retrieve_set_role_to_child():
assert result_dict[str(parent_access.id)] == []
assert result_dict[str(parent_access_other_user.id)] == [
"reader",
"commentator",
"editor",
"administrator",
"owner",
@@ -333,28 +336,28 @@ def test_api_document_accesses_retrieve_set_role_to_child():
[
["administrator", "reader", "reader", "reader"],
[
["reader", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
[],
[],
["reader", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
],
],
[
["owner", "reader", "reader", "reader"],
[
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
[],
[],
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
],
],
[
["owner", "reader", "reader", "owner"],
[
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
[],
[],
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
],
],
],
@@ -415,44 +418,44 @@ def test_api_document_accesses_list_authenticated_related_same_user(roles, resul
[
["administrator", "reader", "reader", "reader"],
[
["reader", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
[],
[],
["reader", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
],
],
[
["owner", "reader", "reader", "reader"],
[
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
[],
[],
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
],
],
[
["owner", "reader", "reader", "owner"],
[
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
[],
[],
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
],
],
[
["reader", "reader", "reader", "owner"],
[
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
[],
[],
["reader", "editor", "administrator", "owner"],
["reader", "commentator", "editor", "administrator", "owner"],
],
],
[
["reader", "administrator", "reader", "editor"],
[
["reader", "editor", "administrator"],
["reader", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
[],
[],
],
@@ -460,7 +463,7 @@ def test_api_document_accesses_list_authenticated_related_same_user(roles, resul
[
["editor", "editor", "administrator", "editor"],
[
["reader", "editor", "administrator"],
["reader", "commentator", "editor", "administrator"],
[],
["editor", "administrator"],
[],

View File

@@ -36,6 +36,7 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"children_create": False,
"children_list": True,
"collaboration_auth": True,
"comment": document.link_role in ["commentator", "editor"],
"cors_proxy": True,
"content": True,
"descendants": True,
@@ -46,8 +47,8 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": False,
@@ -113,6 +114,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
"children_create": False,
"children_list": True,
"collaboration_auth": True,
"comment": grand_parent.link_role in ["commentator", "editor"],
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -220,6 +222,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"children_create": document.link_role == "editor",
"children_list": True,
"collaboration_auth": True,
"comment": document.link_role in ["commentator", "editor"],
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -229,8 +232,8 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
@@ -304,6 +307,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
"children_create": grand_parent.link_role == "editor",
"children_list": True,
"collaboration_auth": True,
"comment": grand_parent.link_role in ["commentator", "editor"],
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -497,10 +501,11 @@ def test_api_documents_retrieve_authenticated_related_parent():
"ai_transform": access.role != "reader",
"ai_translate": access.role != "reader",
"attachment_upload": access.role != "reader",
"can_edit": access.role != "reader",
"can_edit": access.role not in ["reader", "commentator"],
"children_create": access.role != "reader",
"children_list": True,
"collaboration_auth": True,
"comment": access.role != "reader",
"descendants": True,
"cors_proxy": True,
"content": True,

View File

@@ -81,6 +81,7 @@ def test_api_documents_trashbin_format():
"collaboration_auth": False,
"descendants": False,
"cors_proxy": False,
"comment": False,
"content": False,
"destroy": False,
"duplicate": False,
@@ -88,8 +89,8 @@ def test_api_documents_trashbin_format():
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": False,

View File

@@ -123,7 +123,7 @@ def test_models_document_access_get_abilities_for_owner_of_self_allowed():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator", "owner"],
"set_role_to": ["reader", "commentator", "editor", "administrator", "owner"],
}
@@ -166,7 +166,7 @@ def test_models_document_access_get_abilities_for_owner_of_self_last_on_child(
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator", "owner"],
"set_role_to": ["reader", "commentator", "editor", "administrator", "owner"],
}
@@ -183,7 +183,7 @@ def test_models_document_access_get_abilities_for_owner_of_owner():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator", "owner"],
"set_role_to": ["reader", "commentator", "editor", "administrator", "owner"],
}
@@ -200,7 +200,7 @@ def test_models_document_access_get_abilities_for_owner_of_administrator():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator", "owner"],
"set_role_to": ["reader", "commentator", "editor", "administrator", "owner"],
}
@@ -217,7 +217,7 @@ def test_models_document_access_get_abilities_for_owner_of_editor():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator", "owner"],
"set_role_to": ["reader", "commentator", "editor", "administrator", "owner"],
}
@@ -234,7 +234,7 @@ def test_models_document_access_get_abilities_for_owner_of_reader():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator", "owner"],
"set_role_to": ["reader", "commentator", "editor", "administrator", "owner"],
}
@@ -271,7 +271,7 @@ def test_models_document_access_get_abilities_for_administrator_of_administrator
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator"],
"set_role_to": ["reader", "commentator", "editor", "administrator"],
}
@@ -288,7 +288,7 @@ def test_models_document_access_get_abilities_for_administrator_of_editor():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator"],
"set_role_to": ["reader", "commentator", "editor", "administrator"],
}
@@ -305,7 +305,7 @@ def test_models_document_access_get_abilities_for_administrator_of_reader():
"retrieve": True,
"update": True,
"partial_update": True,
"set_role_to": ["reader", "editor", "administrator"],
"set_role_to": ["reader", "commentator", "editor", "administrator"],
}

View File

@@ -134,10 +134,13 @@ def test_models_documents_soft_delete(depth):
[
(True, "restricted", "reader"),
(True, "restricted", "editor"),
(True, "restricted", "commentator"),
(False, "restricted", "reader"),
(False, "restricted", "editor"),
(False, "restricted", "commentator"),
(False, "authenticated", "reader"),
(False, "authenticated", "editor"),
(False, "authenticated", "commentator"),
],
)
def test_models_documents_get_abilities_forbidden(
@@ -165,6 +168,7 @@ def test_models_documents_get_abilities_forbidden(
"destroy": False,
"duplicate": False,
"favorite": False,
"comment": False,
"invite_owner": False,
"mask": False,
"media_auth": False,
@@ -172,8 +176,8 @@ def test_models_documents_get_abilities_forbidden(
"move": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"partial_update": False,
@@ -223,6 +227,7 @@ def test_models_documents_get_abilities_reader(
"children_create": False,
"children_list": True,
"collaboration_auth": True,
"comment": False,
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -232,8 +237,77 @@ def test_models_documents_get_abilities_reader(
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": is_authenticated,
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": False,
"restore": False,
"retrieve": True,
"tree": True,
"update": False,
"versions_destroy": False,
"versions_list": False,
"versions_retrieve": False,
}
nb_queries = 1 if is_authenticated else 0
with django_assert_num_queries(nb_queries):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key not in ["link_select_options", "ancestors_links_definition"]
)
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
@pytest.mark.parametrize(
"is_authenticated,reach",
[
(True, "public"),
(False, "public"),
(True, "authenticated"),
],
)
def test_models_documents_get_abilities_commentator(
is_authenticated, reach, django_assert_num_queries
):
"""
Check abilities returned for a document giving commentator role to link holders
i.e anonymous users or authenticated users who have no specific role on the document.
"""
document = factories.DocumentFactory(link_reach=reach, link_role="commentator")
user = factories.UserFactory() if is_authenticated else AnonymousUser()
expected_abilities = {
"accesses_manage": False,
"accesses_view": False,
"ai_transform": False,
"ai_translate": False,
"attachment_upload": False,
"can_edit": False,
"children_create": False,
"children_list": True,
"collaboration_auth": True,
"comment": True,
"descendants": True,
"cors_proxy": True,
"destroy": False,
"duplicate": is_authenticated,
"favorite": is_authenticated,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": is_authenticated,
@@ -289,6 +363,7 @@ def test_models_documents_get_abilities_editor(
"children_create": is_authenticated,
"children_list": True,
"collaboration_auth": True,
"comment": True,
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -298,8 +373,8 @@ def test_models_documents_get_abilities_editor(
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": is_authenticated,
@@ -344,6 +419,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"children_create": True,
"children_list": True,
"collaboration_auth": True,
"comment": True,
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -353,8 +429,8 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"invite_owner": True,
"link_configuration": True,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
@@ -430,6 +506,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
"children_create": True,
"children_list": True,
"collaboration_auth": True,
"comment": True,
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -439,8 +516,8 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
"invite_owner": False,
"link_configuration": True,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
@@ -485,6 +562,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"children_create": True,
"children_list": True,
"collaboration_auth": True,
"comment": True,
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -494,8 +572,8 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
@@ -547,6 +625,8 @@ def test_models_documents_get_abilities_reader_user(
"children_create": access_from_link,
"children_list": True,
"collaboration_auth": True,
"comment": document.link_reach != "restricted"
and document.link_role in ["commentator", "editor"],
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -556,8 +636,72 @@ def test_models_documents_get_abilities_reader_user(
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": access_from_link,
"restore": False,
"retrieve": True,
"tree": True,
"update": access_from_link,
"versions_destroy": False,
"versions_list": True,
"versions_retrieve": True,
}
with override_settings(AI_ALLOW_REACH_FROM=ai_access_setting):
with django_assert_num_queries(1):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key not in ["link_select_options", "ancestors_links_definition"]
)
@pytest.mark.parametrize("ai_access_setting", ["public", "authenticated", "restricted"])
def test_models_documents_get_abilities_commentator_user(
ai_access_setting, django_assert_num_queries
):
"""Check abilities returned for the commentator of a document."""
user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, "commentator")])
access_from_link = (
document.link_reach != "restricted" and document.link_role == "editor"
)
expected_abilities = {
"accesses_manage": False,
"accesses_view": True,
# If you get your editor rights from the link role and not your access role
# You should not access AI if it's restricted to users with specific access
"ai_transform": access_from_link and ai_access_setting != "restricted",
"ai_translate": access_from_link and ai_access_setting != "restricted",
"attachment_upload": access_from_link,
"can_edit": access_from_link,
"children_create": access_from_link,
"children_list": True,
"collaboration_auth": True,
"comment": True,
"descendants": True,
"cors_proxy": True,
"destroy": False,
"duplicate": True,
"favorite": True,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
@@ -607,6 +751,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
"children_create": False,
"children_list": True,
"collaboration_auth": True,
"comment": False,
"descendants": True,
"cors_proxy": True,
"content": True,
@@ -616,8 +761,8 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
"restricted": None,
},
"mask": True,
@@ -1320,7 +1465,14 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"public",
"reader",
{
"public": ["reader", "editor"],
"public": ["reader", "commentator", "editor"],
},
),
(
"public",
"commentator",
{
"public": ["commentator", "editor"],
},
),
("public", "editor", {"public": ["editor"]}),
@@ -1328,8 +1480,16 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"authenticated",
"reader",
{
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
},
),
(
"authenticated",
"commentator",
{
"authenticated": ["commentator", "editor"],
"public": ["commentator", "editor"],
},
),
(
@@ -1342,8 +1502,17 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"reader",
{
"restricted": None,
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"public": ["reader", "commentator", "editor"],
},
),
(
"restricted",
"commentator",
{
"restricted": None,
"authenticated": ["commentator", "editor"],
"public": ["commentator", "editor"],
},
),
(
@@ -1360,15 +1529,15 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"public",
None,
{
"public": ["reader", "editor"],
"public": ["reader", "commentator", "editor"],
},
),
(
None,
"reader",
{
"public": ["reader", "editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "commentator", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"restricted": None,
},
),
@@ -1376,8 +1545,8 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
None,
None,
{
"public": ["reader", "editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "commentator", "editor"],
"authenticated": ["reader", "commentator", "editor"],
"restricted": None,
},
),