(backend) implement thread and reactions API

In order to use comment we also have to implement a thread and reactions
API. A thread has multiple comments and comments can have multiple
reactions.
This commit is contained in:
Anthony LC
2025-09-12 15:28:25 +02:00
parent a2a63cd13e
commit b13571c6df
17 changed files with 2431 additions and 445 deletions

View File

@@ -1,4 +1,5 @@
"""Client serializers for the impress core app.""" """Client serializers for the impress core app."""
# pylint: disable=too-many-lines
import binascii import binascii
import mimetypes import mimetypes
@@ -893,45 +894,122 @@ class MoveDocumentSerializer(serializers.Serializer):
) )
class ReactionSerializer(serializers.ModelSerializer):
"""Serialize reactions."""
users = UserLightSerializer(many=True, read_only=True)
class Meta:
model = models.Reaction
fields = [
"id",
"emoji",
"created_at",
"users",
]
read_only_fields = ["id", "created_at", "users"]
class CommentSerializer(serializers.ModelSerializer): class CommentSerializer(serializers.ModelSerializer):
"""Serialize comments.""" """Serialize comments (nested under a thread) with reactions and abilities."""
user = UserLightSerializer(read_only=True) user = UserLightSerializer(read_only=True)
abilities = serializers.SerializerMethodField(read_only=True) abilities = serializers.SerializerMethodField()
reactions = ReactionSerializer(many=True, read_only=True)
class Meta: class Meta:
model = models.Comment model = models.Comment
fields = [ fields = [
"id", "id",
"content", "user",
"body",
"created_at", "created_at",
"updated_at", "updated_at",
"user", "reactions",
"document",
"abilities", "abilities",
] ]
read_only_fields = [
"id",
"user",
"created_at",
"updated_at",
"reactions",
"abilities",
]
def validate(self, attrs):
"""Validate comment data."""
request = self.context.get("request")
user = getattr(request, "user", None)
attrs["thread_id"] = self.context["thread_id"]
attrs["user_id"] = user.id if user else None
return attrs
def get_abilities(self, obj):
"""Return comment's abilities."""
request = self.context.get("request")
if request:
return obj.get_abilities(request.user)
return {}
class ThreadSerializer(serializers.ModelSerializer):
"""Serialize threads in a backward compatible shape for current frontend.
We expose a flatten representation where ``content`` maps to the first
comment's body. Creating a thread requires a ``content`` field which is
stored as the first comment.
"""
creator = UserLightSerializer(read_only=True)
abilities = serializers.SerializerMethodField(read_only=True)
body = serializers.JSONField(write_only=True, required=True)
comments = serializers.SerializerMethodField(read_only=True)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Thread
fields = [
"id",
"body",
"created_at",
"updated_at",
"creator",
"abilities",
"comments",
"resolved",
"resolved_at",
"resolved_by",
"metadata",
]
read_only_fields = [ read_only_fields = [
"id", "id",
"created_at", "created_at",
"updated_at", "updated_at",
"user", "creator",
"document",
"abilities", "abilities",
"comments",
"resolved",
"resolved_at",
"resolved_by",
"metadata",
] ]
def get_abilities(self, comment) -> dict:
"""Return abilities of the logged-in user on the instance."""
request = self.context.get("request")
if request:
return comment.get_abilities(request.user)
return {}
def validate(self, attrs): def validate(self, attrs):
"""Validate invitation data.""" """Validate thread data."""
request = self.context.get("request") request = self.context.get("request")
user = getattr(request, "user", None) user = getattr(request, "user", None)
attrs["document_id"] = self.context["resource_id"] attrs["document_id"] = self.context["resource_id"]
attrs["user_id"] = user.id if user else None attrs["creator_id"] = user.id if user else None
return attrs return attrs
def get_abilities(self, thread):
"""Return thread's abilities."""
request = self.context.get("request")
if request:
return thread.get_abilities(request.user)
return {}

View File

@@ -21,6 +21,7 @@ from django.db.models.expressions import RawSQL
from django.db.models.functions import Left, Length from django.db.models.functions import Left, Length
from django.http import Http404, StreamingHttpResponse from django.http import Http404, StreamingHttpResponse
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.text import capfirst, slugify from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -2152,15 +2153,9 @@ class ConfigView(drf.views.APIView):
return theme_customization return theme_customization
class CommentViewSet( class CommentViewSetMixin:
viewsets.ModelViewSet, """Comment ViewSet Mixin."""
):
"""API ViewSet for comments."""
permission_classes = [permissions.CommentPermission]
queryset = models.Comment.objects.select_related("user", "document").all()
serializer_class = serializers.CommentSerializer
pagination_class = Pagination
_document = None _document = None
def get_document_or_404(self): def get_document_or_404(self):
@@ -2174,12 +2169,114 @@ class CommentViewSet(
raise drf.exceptions.NotFound("Document not found.") from e raise drf.exceptions.NotFound("Document not found.") from e
return self._document return self._document
class ThreadViewSet(
ResourceAccessViewsetMixin,
CommentViewSetMixin,
drf.mixins.CreateModelMixin,
drf.mixins.ListModelMixin,
drf.mixins.RetrieveModelMixin,
drf.mixins.DestroyModelMixin,
viewsets.GenericViewSet,
):
"""Thread API: list/create threads and nested comment operations."""
permission_classes = [permissions.CommentPermission]
pagination_class = Pagination
serializer_class = serializers.ThreadSerializer
queryset = models.Thread.objects.select_related("creator", "document").filter(
resolved=False
)
resource_field_name = "document"
def perform_create(self, serializer):
"""Create the first comment of the thread."""
body = serializer.validated_data["body"]
del serializer.validated_data["body"]
thread = serializer.save()
models.Comment.objects.create(
thread=thread,
user=self.request.user if self.request.user.is_authenticated else None,
body=body,
)
@drf.decorators.action(detail=True, methods=["post"], url_path="resolve")
def resolve(self, request, *args, **kwargs):
"""Resolve a thread."""
thread = self.get_object()
if not thread.resolved:
thread.resolved = True
thread.resolved_at = timezone.now()
thread.resolved_by = request.user
thread.save(update_fields=["resolved", "resolved_at", "resolved_by"])
return drf.response.Response(status=status.HTTP_204_NO_CONTENT)
class CommentViewSet(
CommentViewSetMixin,
viewsets.ModelViewSet,
):
"""Comment API: list/create comments and nested reaction operations."""
permission_classes = [permissions.CommentPermission]
pagination_class = Pagination
serializer_class = serializers.CommentSerializer
queryset = models.Comment.objects.select_related("user").all()
def get_queryset(self):
"""Override to filter on related resource."""
return (
super()
.get_queryset()
.filter(
thread=self.kwargs["thread_id"],
thread__document=self.kwargs["resource_id"],
)
)
def get_serializer_context(self): def get_serializer_context(self):
"""Extra context provided to the serializer class.""" """Extra context provided to the serializer class."""
context = super().get_serializer_context() context = super().get_serializer_context()
context["resource_id"] = self.kwargs["resource_id"] context["document_id"] = self.kwargs["resource_id"]
context["thread_id"] = self.kwargs["thread_id"]
return context return context
def get_queryset(self): @drf.decorators.action(
"""Return the queryset according to the action.""" detail=True,
return super().get_queryset().filter(document=self.kwargs["resource_id"]) methods=["post", "delete"],
)
def reactions(self, request, *args, **kwargs):
"""POST: add reaction; DELETE: remove reaction.
Emoji is expected in request.data['emoji'] for both operations.
"""
comment = self.get_object()
serializer = serializers.ReactionSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if request.method == "POST":
reaction, created = models.Reaction.objects.get_or_create(
comment=comment,
emoji=serializer.validated_data["emoji"],
)
if not created and reaction.users.filter(id=request.user.id).exists():
return drf.response.Response(
{"user_already_reacted": True}, status=status.HTTP_400_BAD_REQUEST
)
reaction.users.add(request.user)
return drf.response.Response(status=status.HTTP_201_CREATED)
# DELETE
try:
reaction = models.Reaction.objects.get(
comment=comment,
emoji=serializer.validated_data["emoji"],
users__in=[request.user],
)
except models.Reaction.DoesNotExist as e:
raise drf.exceptions.NotFound("Reaction not found.") from e
reaction.users.remove(request.user)
if not reaction.users.exists():
reaction.delete()
return drf.response.Response(status=status.HTTP_204_NO_CONTENT)

View File

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

View File

@@ -258,12 +258,47 @@ class InvitationFactory(factory.django.DjangoModelFactory):
issuer = factory.SubFactory(UserFactory) issuer = factory.SubFactory(UserFactory)
class ThreadFactory(factory.django.DjangoModelFactory):
"""A factory to create threads for a document"""
class Meta:
model = models.Thread
document = factory.SubFactory(DocumentFactory)
creator = factory.SubFactory(UserFactory)
class CommentFactory(factory.django.DjangoModelFactory): class CommentFactory(factory.django.DjangoModelFactory):
"""A factory to create comments for a document""" """A factory to create comments for a thread"""
class Meta: class Meta:
model = models.Comment model = models.Comment
document = factory.SubFactory(DocumentFactory) thread = factory.SubFactory(ThreadFactory)
user = factory.SubFactory(UserFactory) user = factory.SubFactory(UserFactory)
content = factory.Faker("text") body = factory.Faker("text")
class ReactionFactory(factory.django.DjangoModelFactory):
"""A factory to create reactions for a comment"""
class Meta:
model = models.Reaction
comment = factory.SubFactory(CommentFactory)
emoji = "test"
@factory.post_generation
def users(self, create, extracted, **kwargs):
"""Add users to reaction from a given list of users or create one if not provided."""
if not create:
return
if not extracted:
# the factory is being created, but no users were provided
user = UserFactory()
self.users.add(user)
return
# Add the iterable of groups using bulk addition
self.users.add(*extracted)

View File

@@ -1,146 +0,0 @@
# Generated by Django 5.2.4 on 2025-08-26 08:11
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0024_add_is_masked_field_to_link_trace"),
]
operations = [
migrations.AlterField(
model_name="document",
name="link_role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commentator", "Commentator"),
("editor", "Editor"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="documentaccess",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commentator", "Commentator"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="documentaskforaccess",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commentator", "Commentator"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="invitation",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commentator", "Commentator"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="templateaccess",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commentator", "Commentator"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.CreateModel(
name="Comment",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
help_text="primary key for the record as UUID",
primary_key=True,
serialize=False,
verbose_name="id",
),
),
(
"created_at",
models.DateTimeField(
auto_now_add=True,
help_text="date and time at which a record was created",
verbose_name="created on",
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True,
help_text="date and time at which a record was last updated",
verbose_name="updated on",
),
),
("content", models.TextField()),
(
"document",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="core.document",
),
),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="comments",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Comment",
"verbose_name_plural": "Comments",
"db_table": "impress_comment",
"ordering": ("-created_at",),
},
),
]

View File

@@ -0,0 +1,275 @@
# Generated by Django 5.2.6 on 2025-09-16 08:59
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0025_alter_user_short_name"),
]
operations = [
migrations.AlterField(
model_name="document",
name="link_role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commenter", "Commenter"),
("editor", "Editor"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="documentaccess",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commenter", "Commenter"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="documentaskforaccess",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commenter", "Commenter"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="invitation",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commenter", "Commenter"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.AlterField(
model_name="templateaccess",
name="role",
field=models.CharField(
choices=[
("reader", "Reader"),
("commenter", "Commenter"),
("editor", "Editor"),
("administrator", "Administrator"),
("owner", "Owner"),
],
default="reader",
max_length=20,
),
),
migrations.CreateModel(
name="Thread",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
help_text="primary key for the record as UUID",
primary_key=True,
serialize=False,
verbose_name="id",
),
),
(
"created_at",
models.DateTimeField(
auto_now_add=True,
help_text="date and time at which a record was created",
verbose_name="created on",
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True,
help_text="date and time at which a record was last updated",
verbose_name="updated on",
),
),
("resolved", models.BooleanField(default=False)),
("resolved_at", models.DateTimeField(blank=True, null=True)),
("metadata", models.JSONField(blank=True, default=dict)),
(
"creator",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="threads",
to=settings.AUTH_USER_MODEL,
),
),
(
"document",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="threads",
to="core.document",
),
),
(
"resolved_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="resolved_threads",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Thread",
"verbose_name_plural": "Threads",
"db_table": "impress_thread",
"ordering": ("-created_at",),
},
),
migrations.CreateModel(
name="Comment",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
help_text="primary key for the record as UUID",
primary_key=True,
serialize=False,
verbose_name="id",
),
),
(
"created_at",
models.DateTimeField(
auto_now_add=True,
help_text="date and time at which a record was created",
verbose_name="created on",
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True,
help_text="date and time at which a record was last updated",
verbose_name="updated on",
),
),
("body", models.JSONField()),
("metadata", models.JSONField(blank=True, default=dict)),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="thread_comment",
to=settings.AUTH_USER_MODEL,
),
),
(
"thread",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="core.thread",
),
),
],
options={
"verbose_name": "Comment",
"verbose_name_plural": "Comments",
"db_table": "impress_comment",
"ordering": ("created_at",),
},
),
migrations.CreateModel(
name="Reaction",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
help_text="primary key for the record as UUID",
primary_key=True,
serialize=False,
verbose_name="id",
),
),
(
"created_at",
models.DateTimeField(
auto_now_add=True,
help_text="date and time at which a record was created",
verbose_name="created on",
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True,
help_text="date and time at which a record was last updated",
verbose_name="updated on",
),
),
("emoji", models.CharField(max_length=32)),
(
"comment",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="reactions",
to="core.comment",
),
),
(
"users",
models.ManyToManyField(
related_name="reactions", to=settings.AUTH_USER_MODEL
),
),
],
options={
"verbose_name": "Reaction",
"verbose_name_plural": "Reactions",
"db_table": "impress_comment_reaction",
"constraints": [
models.UniqueConstraint(
fields=("comment", "emoji"),
name="unique_comment_emoji",
violation_error_message="This emoji has already been reacted to this comment.",
)
],
},
),
]

View File

@@ -756,7 +756,7 @@ class Document(MP_Node, BaseModel):
can_update = ( can_update = (
is_owner_or_admin or role == RoleChoices.EDITOR is_owner_or_admin or role == RoleChoices.EDITOR
) and not is_deleted ) and not is_deleted
can_comment = (can_update or role == RoleChoices.COMMENTATOR) and not is_deleted can_comment = (can_update or role == RoleChoices.COMMENTER) and not is_deleted
can_create_children = can_update and user.is_authenticated can_create_children = can_update and user.is_authenticated
can_destroy = ( can_destroy = (
is_owner is_owner
@@ -1150,7 +1150,7 @@ class DocumentAccess(BaseAccess):
set_role_to.extend( set_role_to.extend(
[ [
RoleChoices.READER, RoleChoices.READER,
RoleChoices.COMMENTATOR, RoleChoices.COMMENTER,
RoleChoices.EDITOR, RoleChoices.EDITOR,
RoleChoices.ADMIN, RoleChoices.ADMIN,
] ]
@@ -1277,48 +1277,153 @@ class DocumentAskForAccess(BaseModel):
self.document.send_email(subject, [email], context, language) self.document.send_email(subject, [email], context, language)
class Comment(BaseModel): class Thread(BaseModel):
"""User comment on a document.""" """Discussion thread attached to a document.
A thread groups one or many comments. For backward compatibility with the
existing frontend (useComments hook) we still expose a flattened serializer
that returns a "content" field representing the first comment's body.
"""
document = models.ForeignKey( document = models.ForeignKey(
Document, Document,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="threads",
)
creator = models.ForeignKey(
User,
on_delete=models.SET_NULL,
related_name="threads",
null=True,
blank=True,
)
resolved = models.BooleanField(default=False)
resolved_at = models.DateTimeField(null=True, blank=True)
resolved_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
related_name="resolved_threads",
null=True,
blank=True,
)
metadata = models.JSONField(default=dict, blank=True)
class Meta:
db_table = "impress_thread"
ordering = ("-created_at",)
verbose_name = _("Thread")
verbose_name_plural = _("Threads")
def __str__(self):
author = self.creator or _("Anonymous")
return f"Thread by {author!s} on {self.document!s}"
def get_abilities(self, user):
"""Compute and return abilities for a given user (mirrors comment logic)."""
role = self.document.get_role(user)
doc_abilities = self.document.get_abilities(user)
read_access = doc_abilities.get("comment", False)
write_access = self.creator == user or role in [
RoleChoices.OWNER,
RoleChoices.ADMIN,
]
return {
"destroy": write_access,
"update": write_access,
"partial_update": write_access,
"resolve": write_access,
"retrieve": read_access,
}
@property
def first_comment(self):
"""Return the first createdcomment of the thread."""
return self.comments.order_by("created_at").first()
class Comment(BaseModel):
"""A comment belonging to a thread."""
thread = models.ForeignKey(
Thread,
on_delete=models.CASCADE,
related_name="comments", related_name="comments",
) )
user = models.ForeignKey( user = models.ForeignKey(
User, User,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name="comments", related_name="thread_comment",
null=True, null=True,
blank=True, blank=True,
) )
content = models.TextField() body = models.JSONField()
metadata = models.JSONField(default=dict, blank=True)
class Meta: class Meta:
db_table = "impress_comment" db_table = "impress_comment"
ordering = ("-created_at",) ordering = ("created_at",)
verbose_name = _("Comment") verbose_name = _("Comment")
verbose_name_plural = _("Comments") verbose_name_plural = _("Comments")
def __str__(self): def __str__(self):
"""Return the string representation of the comment."""
author = self.user or _("Anonymous") author = self.user or _("Anonymous")
return f"{author!s} on {self.document!s}" return f"Comment by {author!s} on thread {self.thread_id}"
def get_abilities(self, user): def get_abilities(self, user):
"""Compute and return abilities for a given user.""" """Return the abilities of the comment."""
role = self.document.get_role(user) role = self.thread.document.get_role(user)
can_comment = self.document.get_abilities(user)["comment"] doc_abilities = self.thread.document.get_abilities(user)
read_access = doc_abilities.get("comment", False)
can_react = read_access and user.is_authenticated
write_access = self.user == user or role in [
RoleChoices.OWNER,
RoleChoices.ADMIN,
]
return { return {
"destroy": self.user == user "destroy": write_access,
or role in [RoleChoices.OWNER, RoleChoices.ADMIN], "update": write_access,
"update": self.user == user "partial_update": write_access,
or role in [RoleChoices.OWNER, RoleChoices.ADMIN], "reactions": can_react,
"partial_update": self.user == user "retrieve": read_access,
or role in [RoleChoices.OWNER, RoleChoices.ADMIN],
"retrieve": can_comment,
} }
class Reaction(BaseModel):
"""Aggregated reactions for a given emoji on a comment.
We store one row per (comment, emoji) and maintain the list of user IDs who
reacted with that emoji. This matches the frontend interface where a
reaction exposes: emoji, createdAt (first reaction date) and userIds.
"""
comment = models.ForeignKey(
Comment,
on_delete=models.CASCADE,
related_name="reactions",
)
emoji = models.CharField(max_length=32)
users = models.ManyToManyField(User, related_name="reactions")
class Meta:
db_table = "impress_comment_reaction"
constraints = [
models.UniqueConstraint(
fields=["comment", "emoji"],
name="unique_comment_emoji",
violation_error_message=_(
"This emoji has already been reacted to this comment."
),
),
]
verbose_name = _("Reaction")
verbose_name_plural = _("Reactions")
def __str__(self):
"""Return the string representation of the reaction."""
return f"Reaction {self.emoji} on comment {self.comment.id}"
class Template(BaseModel): class Template(BaseModel):
"""HTML and CSS code used for formatting the print around the MarkDown body.""" """HTML and CSS code used for formatting the print around the MarkDown body."""

View File

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

View File

@@ -360,6 +360,7 @@ def test_api_documents_ask_for_access_list_owner_or_admin(role):
expected_set_role_to = [ expected_set_role_to = [
RoleChoices.READER, RoleChoices.READER,
RoleChoices.COMMENTER,
RoleChoices.EDITOR, RoleChoices.EDITOR,
RoleChoices.ADMIN, RoleChoices.ADMIN,
] ]
@@ -480,6 +481,7 @@ def test_api_documents_ask_for_access_retrieve_owner_or_admin(role):
assert response.status_code == 200 assert response.status_code == 200
expected_set_role_to = [ expected_set_role_to = [
RoleChoices.READER, RoleChoices.READER,
RoleChoices.COMMENTER,
RoleChoices.EDITOR, RoleChoices.EDITOR,
RoleChoices.ADMIN, RoleChoices.ADMIN,
] ]

View File

@@ -17,42 +17,45 @@ pytestmark = pytest.mark.django_db
def test_list_comments_anonymous_user_public_document(): def test_list_comments_anonymous_user_public_document():
"""Anonymous users should be allowed to list comments on a public document.""" """Anonymous users should be allowed to list comments on a public document."""
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.COMMENTATOR link_reach="public", link_role=models.LinkRoleChoices.COMMENTER
) )
comment1, comment2 = factories.CommentFactory.create_batch(2, document=document) thread = factories.ThreadFactory(document=document)
comment1, comment2 = factories.CommentFactory.create_batch(2, thread=thread)
# other comments not linked to the document # other comments not linked to the document
factories.CommentFactory.create_batch(2) factories.CommentFactory.create_batch(2)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/comments/") response = APIClient().get(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/"
)
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == { assert response.json() == {
"count": 2, "count": 2,
"next": None, "next": None,
"previous": None, "previous": None,
"results": [ "results": [
{
"id": str(comment2.id),
"content": comment2.content,
"created_at": comment2.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment2.updated_at.isoformat().replace("+00:00", "Z"),
"user": {
"full_name": comment2.user.full_name,
"short_name": comment2.user.short_name,
},
"document": str(comment2.document.id),
"abilities": comment2.get_abilities(AnonymousUser()),
},
{ {
"id": str(comment1.id), "id": str(comment1.id),
"content": comment1.content, "body": comment1.body,
"created_at": comment1.created_at.isoformat().replace("+00:00", "Z"), "created_at": comment1.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment1.updated_at.isoformat().replace("+00:00", "Z"), "updated_at": comment1.updated_at.isoformat().replace("+00:00", "Z"),
"user": { "user": {
"full_name": comment1.user.full_name, "full_name": comment1.user.full_name,
"short_name": comment1.user.short_name, "short_name": comment1.user.short_name,
}, },
"document": str(comment1.document.id),
"abilities": comment1.get_abilities(AnonymousUser()), "abilities": comment1.get_abilities(AnonymousUser()),
"reactions": [],
},
{
"id": str(comment2.id),
"body": comment2.body,
"created_at": comment2.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment2.updated_at.isoformat().replace("+00:00", "Z"),
"user": {
"full_name": comment2.user.full_name,
"short_name": comment2.user.short_name,
},
"abilities": comment2.get_abilities(AnonymousUser()),
"reactions": [],
}, },
], ],
} }
@@ -62,13 +65,16 @@ def test_list_comments_anonymous_user_public_document():
def test_list_comments_anonymous_user_non_public_document(link_reach): def test_list_comments_anonymous_user_non_public_document(link_reach):
"""Anonymous users should not be allowed to list comments on a non-public document.""" """Anonymous users should not be allowed to list comments on a non-public document."""
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach=link_reach, link_role=models.LinkRoleChoices.COMMENTATOR link_reach=link_reach, link_role=models.LinkRoleChoices.COMMENTER
) )
factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
factories.CommentFactory(thread=thread)
# other comments not linked to the document # other comments not linked to the document
factories.CommentFactory.create_batch(2) factories.CommentFactory.create_batch(2)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/comments/") response = APIClient().get(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/"
)
assert response.status_code == 401 assert response.status_code == 401
@@ -76,46 +82,49 @@ def test_list_comments_authenticated_user_accessible_document():
"""Authenticated users should be allowed to list comments on an accessible document.""" """Authenticated users should be allowed to list comments on an accessible document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTATOR)] link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTER)]
) )
comment1 = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment2 = factories.CommentFactory(document=document, user=user) comment1 = factories.CommentFactory(thread=thread)
comment2 = factories.CommentFactory(thread=thread, user=user)
# other comments not linked to the document # other comments not linked to the document
factories.CommentFactory.create_batch(2) factories.CommentFactory.create_batch(2)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get(f"/api/v1.0/documents/{document.id!s}/comments/") response = client.get(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/"
)
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == { assert response.json() == {
"count": 2, "count": 2,
"next": None, "next": None,
"previous": None, "previous": None,
"results": [ "results": [
{
"id": str(comment2.id),
"content": comment2.content,
"created_at": comment2.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment2.updated_at.isoformat().replace("+00:00", "Z"),
"user": {
"full_name": comment2.user.full_name,
"short_name": comment2.user.short_name,
},
"document": str(comment2.document.id),
"abilities": comment2.get_abilities(user),
},
{ {
"id": str(comment1.id), "id": str(comment1.id),
"content": comment1.content, "body": comment1.body,
"created_at": comment1.created_at.isoformat().replace("+00:00", "Z"), "created_at": comment1.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment1.updated_at.isoformat().replace("+00:00", "Z"), "updated_at": comment1.updated_at.isoformat().replace("+00:00", "Z"),
"user": { "user": {
"full_name": comment1.user.full_name, "full_name": comment1.user.full_name,
"short_name": comment1.user.short_name, "short_name": comment1.user.short_name,
}, },
"document": str(comment1.document.id),
"abilities": comment1.get_abilities(user), "abilities": comment1.get_abilities(user),
"reactions": [],
},
{
"id": str(comment2.id),
"body": comment2.body,
"created_at": comment2.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment2.updated_at.isoformat().replace("+00:00", "Z"),
"user": {
"full_name": comment2.user.full_name,
"short_name": comment2.user.short_name,
},
"abilities": comment2.get_abilities(user),
"reactions": [],
}, },
], ],
} }
@@ -125,14 +134,17 @@ def test_list_comments_authenticated_user_non_accessible_document():
"""Authenticated users should not be allowed to list comments on a non-accessible document.""" """Authenticated users should not be allowed to list comments on a non-accessible document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(link_reach="restricted") document = factories.DocumentFactory(link_reach="restricted")
factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
factories.CommentFactory(thread=thread)
# other comments not linked to the document # other comments not linked to the document
factories.CommentFactory.create_batch(2) factories.CommentFactory.create_batch(2)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get(f"/api/v1.0/documents/{document.id!s}/comments/") response = client.get(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/"
)
assert response.status_code == 403 assert response.status_code == 403
@@ -145,14 +157,17 @@ def test_list_comments_authenticated_user_not_enough_access():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)] link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)]
) )
factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
factories.CommentFactory(thread=thread)
# other comments not linked to the document # other comments not linked to the document
factories.CommentFactory.create_batch(2) factories.CommentFactory.create_batch(2)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get(f"/api/v1.0/documents/{document.id!s}/comments/") response = client.get(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/"
)
assert response.status_code == 403 assert response.status_code == 403
@@ -160,30 +175,35 @@ def test_list_comments_authenticated_user_not_enough_access():
def test_create_comment_anonymous_user_public_document(): def test_create_comment_anonymous_user_public_document():
"""Anonymous users should not be allowed to create comments on a public document.""" """
Anonymous users should be allowed to create comments on a public document
with commenter link_role.
"""
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.COMMENTATOR link_reach="public", link_role=models.LinkRoleChoices.COMMENTER
) )
thread = factories.ThreadFactory(document=document)
client = APIClient() client = APIClient()
response = client.post( response = client.post(
f"/api/v1.0/documents/{document.id!s}/comments/", {"content": "test"} f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/",
{"body": "test"},
) )
assert response.status_code == 201 assert response.status_code == 201
assert response.json() == { assert response.json() == {
"id": str(response.json()["id"]), "id": str(response.json()["id"]),
"content": "test", "body": "test",
"created_at": response.json()["created_at"], "created_at": response.json()["created_at"],
"updated_at": response.json()["updated_at"], "updated_at": response.json()["updated_at"],
"user": None, "user": None,
"document": str(document.id),
"abilities": { "abilities": {
"destroy": False, "destroy": False,
"update": False, "update": False,
"partial_update": False, "partial_update": False,
"reactions": False,
"retrieve": True, "retrieve": True,
}, },
"reactions": [],
} }
@@ -192,9 +212,11 @@ def test_create_comment_anonymous_user_non_accessible_document():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.READER link_reach="public", link_role=models.LinkRoleChoices.READER
) )
thread = factories.ThreadFactory(document=document)
client = APIClient() client = APIClient()
response = client.post( response = client.post(
f"/api/v1.0/documents/{document.id!s}/comments/", {"content": "test"} f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/",
{"body": "test"},
) )
assert response.status_code == 401 assert response.status_code == 401
@@ -204,31 +226,34 @@ def test_create_comment_authenticated_user_accessible_document():
"""Authenticated users should be allowed to create comments on an accessible document.""" """Authenticated users should be allowed to create comments on an accessible document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTATOR)] link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTER)]
) )
thread = factories.ThreadFactory(document=document)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.post( response = client.post(
f"/api/v1.0/documents/{document.id!s}/comments/", {"content": "test"} f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/",
{"body": "test"},
) )
assert response.status_code == 201 assert response.status_code == 201
assert response.json() == { assert response.json() == {
"id": str(response.json()["id"]), "id": str(response.json()["id"]),
"content": "test", "body": "test",
"created_at": response.json()["created_at"], "created_at": response.json()["created_at"],
"updated_at": response.json()["updated_at"], "updated_at": response.json()["updated_at"],
"user": { "user": {
"full_name": user.full_name, "full_name": user.full_name,
"short_name": user.short_name, "short_name": user.short_name,
}, },
"document": str(document.id),
"abilities": { "abilities": {
"destroy": True, "destroy": True,
"update": True, "update": True,
"partial_update": True, "partial_update": True,
"reactions": True,
"retrieve": True, "retrieve": True,
}, },
"reactions": [],
} }
@@ -241,10 +266,12 @@ def test_create_comment_authenticated_user_not_enough_access():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)] link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)]
) )
thread = factories.ThreadFactory(document=document)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.post( response = client.post(
f"/api/v1.0/documents/{document.id!s}/comments/", {"content": "test"} f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/",
{"body": "test"},
) )
assert response.status_code == 403 assert response.status_code == 403
@@ -255,24 +282,25 @@ def test_create_comment_authenticated_user_not_enough_access():
def test_retrieve_comment_anonymous_user_public_document(): def test_retrieve_comment_anonymous_user_public_document():
"""Anonymous users should be allowed to retrieve comments on a public document.""" """Anonymous users should be allowed to retrieve comments on a public document."""
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.COMMENTATOR link_reach="public", link_role=models.LinkRoleChoices.COMMENTER
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
response = client.get( response = client.get(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == { assert response.json() == {
"id": str(comment.id), "id": str(comment.id),
"content": comment.content, "body": comment.body,
"created_at": comment.created_at.isoformat().replace("+00:00", "Z"), "created_at": comment.created_at.isoformat().replace("+00:00", "Z"),
"updated_at": comment.updated_at.isoformat().replace("+00:00", "Z"), "updated_at": comment.updated_at.isoformat().replace("+00:00", "Z"),
"user": { "user": {
"full_name": comment.user.full_name, "full_name": comment.user.full_name,
"short_name": comment.user.short_name, "short_name": comment.user.short_name,
}, },
"document": str(comment.document.id), "reactions": [],
"abilities": comment.get_abilities(AnonymousUser()), "abilities": comment.get_abilities(AnonymousUser()),
} }
@@ -282,10 +310,11 @@ def test_retrieve_comment_anonymous_user_non_accessible_document():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.READER link_reach="public", link_role=models.LinkRoleChoices.READER
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
response = client.get( response = client.get(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 401 assert response.status_code == 401
@@ -294,13 +323,14 @@ def test_retrieve_comment_authenticated_user_accessible_document():
"""Authenticated users should be allowed to retrieve comments on an accessible document.""" """Authenticated users should be allowed to retrieve comments on an accessible document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTATOR)] link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTER)]
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get( response = client.get(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 200 assert response.status_code == 200
@@ -314,11 +344,12 @@ def test_retrieve_comment_authenticated_user_not_enough_access():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)] link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)]
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get( response = client.get(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 403 assert response.status_code == 403
@@ -329,13 +360,14 @@ def test_retrieve_comment_authenticated_user_not_enough_access():
def test_update_comment_anonymous_user_public_document(): def test_update_comment_anonymous_user_public_document():
"""Anonymous users should not be allowed to update comments on a public document.""" """Anonymous users should not be allowed to update comments on a public document."""
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.COMMENTATOR link_reach="public", link_role=models.LinkRoleChoices.COMMENTER
) )
comment = factories.CommentFactory(document=document, content="test") thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test")
client = APIClient() client = APIClient()
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 401 assert response.status_code == 401
@@ -345,11 +377,12 @@ def test_update_comment_anonymous_user_non_accessible_document():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.READER link_reach="public", link_role=models.LinkRoleChoices.READER
) )
comment = factories.CommentFactory(document=document, content="test") thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test")
client = APIClient() client = APIClient()
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 401 assert response.status_code == 401
@@ -363,17 +396,18 @@ def test_update_comment_authenticated_user_accessible_document():
( (
user, user,
random.choice( random.choice(
[models.LinkRoleChoices.COMMENTATOR, models.LinkRoleChoices.EDITOR] [models.LinkRoleChoices.COMMENTER, models.LinkRoleChoices.EDITOR]
), ),
) )
], ],
) )
comment = factories.CommentFactory(document=document, content="test") thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test")
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 403 assert response.status_code == 403
@@ -387,22 +421,23 @@ def test_update_comment_authenticated_user_own_comment():
( (
user, user,
random.choice( random.choice(
[models.LinkRoleChoices.COMMENTATOR, models.LinkRoleChoices.EDITOR] [models.LinkRoleChoices.COMMENTER, models.LinkRoleChoices.EDITOR]
), ),
) )
], ],
) )
comment = factories.CommentFactory(document=document, content="test", user=user) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test", user=user)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 200 assert response.status_code == 200
comment.refresh_from_db() comment.refresh_from_db()
assert comment.content == "other content" assert comment.body == "other content"
def test_update_comment_authenticated_user_not_enough_access(): def test_update_comment_authenticated_user_not_enough_access():
@@ -414,12 +449,13 @@ def test_update_comment_authenticated_user_not_enough_access():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)] link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)]
) )
comment = factories.CommentFactory(document=document, content="test") thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test")
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 403 assert response.status_code == 403
@@ -431,12 +467,13 @@ def test_update_comment_authenticated_no_access():
""" """
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(link_reach="restricted") document = factories.DocumentFactory(link_reach="restricted")
comment = factories.CommentFactory(document=document, content="test") thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test")
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 403 assert response.status_code == 403
@@ -448,18 +485,19 @@ def test_update_comment_authenticated_admin_or_owner_can_update_any_comment(role
""" """
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, role)]) document = factories.DocumentFactory(users=[(user, role)])
comment = factories.CommentFactory(document=document, content="test") thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test")
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 200 assert response.status_code == 200
comment.refresh_from_db() comment.refresh_from_db()
assert comment.content == "other content" assert comment.body == "other content"
@pytest.mark.parametrize("role", [models.RoleChoices.ADMIN, models.RoleChoices.OWNER]) @pytest.mark.parametrize("role", [models.RoleChoices.ADMIN, models.RoleChoices.OWNER])
@@ -469,18 +507,19 @@ def test_update_comment_authenticated_admin_or_owner_can_update_own_comment(role
""" """
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, role)]) document = factories.DocumentFactory(users=[(user, role)])
comment = factories.CommentFactory(document=document, content="test", user=user) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, body="test", user=user)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.put( response = client.put(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/", f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/",
{"content": "other content"}, {"body": "other content"},
) )
assert response.status_code == 200 assert response.status_code == 200
comment.refresh_from_db() comment.refresh_from_db()
assert comment.content == "other content" assert comment.body == "other content"
# Delete comment # Delete comment
@@ -489,12 +528,13 @@ def test_update_comment_authenticated_admin_or_owner_can_update_own_comment(role
def test_delete_comment_anonymous_user_public_document(): def test_delete_comment_anonymous_user_public_document():
"""Anonymous users should not be allowed to delete comments on a public document.""" """Anonymous users should not be allowed to delete comments on a public document."""
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.COMMENTATOR link_reach="public", link_role=models.LinkRoleChoices.COMMENTER
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 401 assert response.status_code == 401
@@ -504,10 +544,11 @@ def test_delete_comment_anonymous_user_non_accessible_document():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.READER link_reach="public", link_role=models.LinkRoleChoices.READER
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 401 assert response.status_code == 401
@@ -516,13 +557,14 @@ def test_delete_comment_authenticated_user_accessible_document_own_comment():
"""Authenticated users should be able to delete comments on an accessible document.""" """Authenticated users should be able to delete comments on an accessible document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTATOR)] link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTER)]
) )
comment = factories.CommentFactory(document=document, user=user) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, user=user)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 204 assert response.status_code == 204
@@ -531,13 +573,14 @@ def test_delete_comment_authenticated_user_accessible_document_not_own_comment()
"""Authenticated users should not be able to delete comments on an accessible document.""" """Authenticated users should not be able to delete comments on an accessible document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTATOR)] link_reach="restricted", users=[(user, models.LinkRoleChoices.COMMENTER)]
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 403 assert response.status_code == 403
@@ -547,11 +590,12 @@ def test_delete_comment_authenticated_user_admin_or_owner_can_delete_any_comment
"""Authenticated users should be able to delete comments on a document they have access to.""" """Authenticated users should be able to delete comments on a document they have access to."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, role)]) document = factories.DocumentFactory(users=[(user, role)])
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 204 assert response.status_code == 204
@@ -561,11 +605,12 @@ def test_delete_comment_authenticated_user_admin_or_owner_can_delete_own_comment
"""Authenticated users should be able to delete comments on a document they have access to.""" """Authenticated users should be able to delete comments on a document they have access to."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, role)]) document = factories.DocumentFactory(users=[(user, role)])
comment = factories.CommentFactory(document=document, user=user) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread, user=user)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 204 assert response.status_code == 204
@@ -579,10 +624,255 @@ def test_delete_comment_authenticated_user_not_enough_access():
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)] link_reach="restricted", users=[(user, models.LinkRoleChoices.READER)]
) )
comment = factories.CommentFactory(document=document) thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.delete( response = client.delete(
f"/api/v1.0/documents/{document.id!s}/comments/{comment.id!s}/" f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/comments/{comment.id!s}/"
) )
assert response.status_code == 403 assert response.status_code == 403
# Create reaction
@pytest.mark.parametrize("link_role", models.LinkRoleChoices.values)
def test_create_reaction_anonymous_user_public_document(link_role):
"""No matter the link_role, an anonymous user can not react to a comment."""
document = factories.DocumentFactory(link_reach="public", link_role=link_role)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 401
def test_create_reaction_authenticated_user_public_document():
"""
Authenticated users should not be able to reaction to a comment on a public document with
link_role reader.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.READER
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 403
def test_create_reaction_authenticated_user_accessible_public_document():
"""
Authenticated users should be able to react to a comment on a public document.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="public", link_role=models.LinkRoleChoices.COMMENTER
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 201
assert models.Reaction.objects.filter(
comment=comment, emoji="test", users__in=[user]
).exists()
def test_create_reaction_authenticated_user_connected_document_link_role_reader():
"""
Authenticated users should not be able to react to a comment on a connected document
with link_role reader.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="authenticated", link_role=models.LinkRoleChoices.READER
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 403
@pytest.mark.parametrize(
"link_role",
[
role
for role in models.LinkRoleChoices.values
if role != models.LinkRoleChoices.READER
],
)
def test_create_reaction_authenticated_user_connected_document(link_role):
"""
Authenticated users should be able to react to a comment on a connected document.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="authenticated", link_role=link_role
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 201
assert models.Reaction.objects.filter(
comment=comment, emoji="test", users__in=[user]
).exists()
def test_create_reaction_authenticated_user_restricted_accessible_document():
"""
Authenticated users should not be able to react to a comment on a restricted accessible document
they don't have access to.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(link_reach="restricted")
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 403
def test_create_reaction_authenticated_user_restricted_accessible_document_role_reader():
"""
Authenticated users should not be able to react to a comment on a restricted accessible
document with role reader.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="restricted", link_role=models.LinkRoleChoices.READER
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 403
@pytest.mark.parametrize(
"role",
[role for role in models.RoleChoices.values if role != models.RoleChoices.READER],
)
def test_create_reaction_authenticated_user_restricted_accessible_document_role_commenter(
role,
):
"""
Authenticated users should be able to react to a comment on a restricted accessible document
with role commenter.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(link_reach="restricted", users=[(user, role)])
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 201
assert models.Reaction.objects.filter(
comment=comment, emoji="test", users__in=[user]
).exists()
response = client.post(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": "test"},
)
assert response.status_code == 400
assert response.json() == {"user_already_reacted": True}
# Delete reaction
def test_delete_reaction_not_owned_by_the_current_user():
"""
Users should not be able to delete reactions not owned by the current user.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.RoleChoices.ADMIN)]
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
reaction = factories.ReactionFactory(comment=comment)
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": reaction.emoji},
)
assert response.status_code == 404
def test_delete_reaction_owned_by_the_current_user():
"""
Users should not be able to delete reactions not owned by the current user.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach="restricted", users=[(user, models.RoleChoices.ADMIN)]
)
thread = factories.ThreadFactory(document=document)
comment = factories.CommentFactory(thread=thread)
reaction = factories.ReactionFactory(comment=comment)
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
f"comments/{comment.id!s}/reactions/",
{"emoji": reaction.emoji},
)
assert response.status_code == 404
reaction.refresh_from_db()
assert reaction.users.exists()

View File

@@ -36,7 +36,7 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"children_create": False, "children_create": False,
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": document.link_role in ["commentator", "editor"], "comment": document.link_role in ["commenter", "editor"],
"cors_proxy": True, "cors_proxy": True,
"content": True, "content": True,
"descendants": True, "descendants": True,
@@ -47,8 +47,8 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": False, "mask": False,
@@ -114,7 +114,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
"children_create": False, "children_create": False,
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": grand_parent.link_role in ["commentator", "editor"], "comment": grand_parent.link_role in ["commenter", "editor"],
"descendants": True, "descendants": True,
"cors_proxy": True, "cors_proxy": True,
"content": True, "content": True,
@@ -222,7 +222,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"children_create": document.link_role == "editor", "children_create": document.link_role == "editor",
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": document.link_role in ["commentator", "editor"], "comment": document.link_role in ["commenter", "editor"],
"descendants": True, "descendants": True,
"cors_proxy": True, "cors_proxy": True,
"content": True, "content": True,
@@ -232,8 +232,8 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -307,7 +307,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
"children_create": grand_parent.link_role == "editor", "children_create": grand_parent.link_role == "editor",
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": grand_parent.link_role in ["commentator", "editor"], "comment": grand_parent.link_role in ["commenter", "editor"],
"descendants": True, "descendants": True,
"cors_proxy": True, "cors_proxy": True,
"content": True, "content": True,
@@ -498,11 +498,11 @@ def test_api_documents_retrieve_authenticated_related_parent():
"abilities": { "abilities": {
"accesses_manage": access.role in ["administrator", "owner"], "accesses_manage": access.role in ["administrator", "owner"],
"accesses_view": True, "accesses_view": True,
"ai_transform": access.role != "reader", "ai_transform": access.role not in ["reader", "commenter"],
"ai_translate": access.role != "reader", "ai_translate": access.role not in ["reader", "commenter"],
"attachment_upload": access.role != "reader", "attachment_upload": access.role not in ["reader", "commenter"],
"can_edit": access.role not in ["reader", "commentator"], "can_edit": access.role not in ["reader", "commenter"],
"children_create": access.role != "reader", "children_create": access.role not in ["reader", "commenter"],
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": access.role != "reader", "comment": access.role != "reader",
@@ -521,11 +521,11 @@ def test_api_documents_retrieve_authenticated_related_parent():
"media_auth": True, "media_auth": True,
"media_check": True, "media_check": True,
"move": access.role in ["administrator", "owner"], "move": access.role in ["administrator", "owner"],
"partial_update": access.role != "reader", "partial_update": access.role not in ["reader", "commenter"],
"restore": access.role == "owner", "restore": access.role == "owner",
"retrieve": True, "retrieve": True,
"tree": True, "tree": True,
"update": access.role != "reader", "update": access.role not in ["reader", "commenter"],
"versions_destroy": access.role in ["administrator", "owner"], "versions_destroy": access.role in ["administrator", "owner"],
"versions_list": True, "versions_list": True,
"versions_retrieve": True, "versions_retrieve": True,

File diff suppressed because it is too large Load Diff

View File

@@ -89,8 +89,8 @@ def test_api_documents_trashbin_format():
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": False, "mask": False,

View File

@@ -16,7 +16,7 @@ pytestmark = pytest.mark.django_db
"role,can_comment", "role,can_comment",
[ [
(LinkRoleChoices.READER, False), (LinkRoleChoices.READER, False),
(LinkRoleChoices.COMMENTATOR, True), (LinkRoleChoices.COMMENTER, True),
(LinkRoleChoices.EDITOR, True), (LinkRoleChoices.EDITOR, True),
], ],
) )
@@ -25,13 +25,14 @@ def test_comment_get_abilities_anonymous_user_public_document(role, can_comment)
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_role=role, link_reach=LinkReachChoices.PUBLIC link_role=role, link_reach=LinkReachChoices.PUBLIC
) )
comment = factories.CommentFactory(document=document) comment = factories.CommentFactory(thread__document=document)
user = AnonymousUser() user = AnonymousUser()
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": False, "destroy": False,
"update": False, "update": False,
"partial_update": False, "partial_update": False,
"reactions": False,
"retrieve": can_comment, "retrieve": can_comment,
} }
@@ -42,13 +43,14 @@ def test_comment_get_abilities_anonymous_user_public_document(role, can_comment)
def test_comment_get_abilities_anonymous_user_restricted_document(link_reach): def test_comment_get_abilities_anonymous_user_restricted_document(link_reach):
"""Anonymous users cannot comment on a restricted document.""" """Anonymous users cannot comment on a restricted document."""
document = factories.DocumentFactory(link_reach=link_reach) document = factories.DocumentFactory(link_reach=link_reach)
comment = factories.CommentFactory(document=document) comment = factories.CommentFactory(thread__document=document)
user = AnonymousUser() user = AnonymousUser()
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": False, "destroy": False,
"update": False, "update": False,
"partial_update": False, "partial_update": False,
"reactions": False,
"retrieve": False, "retrieve": False,
} }
@@ -57,13 +59,13 @@ def test_comment_get_abilities_anonymous_user_restricted_document(link_reach):
"link_role,link_reach,can_comment", "link_role,link_reach,can_comment",
[ [
(LinkRoleChoices.READER, LinkReachChoices.PUBLIC, False), (LinkRoleChoices.READER, LinkReachChoices.PUBLIC, False),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.PUBLIC, True), (LinkRoleChoices.COMMENTER, LinkReachChoices.PUBLIC, True),
(LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC, True), (LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC, True),
(LinkRoleChoices.READER, LinkReachChoices.RESTRICTED, False), (LinkRoleChoices.READER, LinkReachChoices.RESTRICTED, False),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.RESTRICTED, False), (LinkRoleChoices.COMMENTER, LinkReachChoices.RESTRICTED, False),
(LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED, False), (LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED, False),
(LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED, False), (LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED, False),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.AUTHENTICATED, True), (LinkRoleChoices.COMMENTER, LinkReachChoices.AUTHENTICATED, True),
(LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED, True), (LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED, True),
], ],
) )
@@ -73,12 +75,13 @@ def test_comment_get_abilities_user_reader(link_role, link_reach, can_comment):
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.READER)] link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.READER)]
) )
comment = factories.CommentFactory(document=document) comment = factories.CommentFactory(thread__document=document)
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": False, "destroy": False,
"update": False, "update": False,
"partial_update": False, "partial_update": False,
"reactions": can_comment,
"retrieve": can_comment, "retrieve": can_comment,
} }
@@ -87,13 +90,13 @@ def test_comment_get_abilities_user_reader(link_role, link_reach, can_comment):
"link_role,link_reach,can_comment", "link_role,link_reach,can_comment",
[ [
(LinkRoleChoices.READER, LinkReachChoices.PUBLIC, False), (LinkRoleChoices.READER, LinkReachChoices.PUBLIC, False),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.PUBLIC, True), (LinkRoleChoices.COMMENTER, LinkReachChoices.PUBLIC, True),
(LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC, True), (LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC, True),
(LinkRoleChoices.READER, LinkReachChoices.RESTRICTED, False), (LinkRoleChoices.READER, LinkReachChoices.RESTRICTED, False),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.RESTRICTED, False), (LinkRoleChoices.COMMENTER, LinkReachChoices.RESTRICTED, False),
(LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED, False), (LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED, False),
(LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED, False), (LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED, False),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.AUTHENTICATED, True), (LinkRoleChoices.COMMENTER, LinkReachChoices.AUTHENTICATED, True),
(LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED, True), (LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED, True),
], ],
) )
@@ -106,13 +109,14 @@ def test_comment_get_abilities_user_reader_own_comment(
link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.READER)] link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.READER)]
) )
comment = factories.CommentFactory( comment = factories.CommentFactory(
document=document, user=user if can_comment else None thread__document=document, user=user if can_comment else None
) )
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": can_comment, "destroy": can_comment,
"update": can_comment, "update": can_comment,
"partial_update": can_comment, "partial_update": can_comment,
"reactions": can_comment,
"retrieve": can_comment, "retrieve": can_comment,
} }
@@ -121,30 +125,31 @@ def test_comment_get_abilities_user_reader_own_comment(
"link_role,link_reach", "link_role,link_reach",
[ [
(LinkRoleChoices.READER, LinkReachChoices.PUBLIC), (LinkRoleChoices.READER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.COMMENTER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC),
(LinkRoleChoices.READER, LinkReachChoices.RESTRICTED), (LinkRoleChoices.READER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.COMMENTER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.COMMENTER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED),
], ],
) )
def test_comment_get_abilities_user_commentator(link_role, link_reach): def test_comment_get_abilities_user_commenter(link_role, link_reach):
"""Commentators can comment on a document.""" """Commenters can comment on a document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_role=link_role, link_role=link_role,
link_reach=link_reach, link_reach=link_reach,
users=[(user, RoleChoices.COMMENTATOR)], users=[(user, RoleChoices.COMMENTER)],
) )
comment = factories.CommentFactory(document=document) comment = factories.CommentFactory(thread__document=document)
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": False, "destroy": False,
"update": False, "update": False,
"partial_update": False, "partial_update": False,
"reactions": True,
"retrieve": True, "retrieve": True,
} }
@@ -153,30 +158,31 @@ def test_comment_get_abilities_user_commentator(link_role, link_reach):
"link_role,link_reach", "link_role,link_reach",
[ [
(LinkRoleChoices.READER, LinkReachChoices.PUBLIC), (LinkRoleChoices.READER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.COMMENTER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC),
(LinkRoleChoices.READER, LinkReachChoices.RESTRICTED), (LinkRoleChoices.READER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.COMMENTER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.COMMENTER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED),
], ],
) )
def test_comment_get_abilities_user_commentator_own_comment(link_role, link_reach): def test_comment_get_abilities_user_commenter_own_comment(link_role, link_reach):
"""Commentators have all accesses to its own comment.""" """Commenters have all accesses to its own comment."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_role=link_role, link_role=link_role,
link_reach=link_reach, link_reach=link_reach,
users=[(user, RoleChoices.COMMENTATOR)], users=[(user, RoleChoices.COMMENTER)],
) )
comment = factories.CommentFactory(document=document, user=user) comment = factories.CommentFactory(thread__document=document, user=user)
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": True, "destroy": True,
"update": True, "update": True,
"partial_update": True, "partial_update": True,
"reactions": True,
"retrieve": True, "retrieve": True,
} }
@@ -185,13 +191,13 @@ def test_comment_get_abilities_user_commentator_own_comment(link_role, link_reac
"link_role,link_reach", "link_role,link_reach",
[ [
(LinkRoleChoices.READER, LinkReachChoices.PUBLIC), (LinkRoleChoices.READER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.COMMENTER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC),
(LinkRoleChoices.READER, LinkReachChoices.RESTRICTED), (LinkRoleChoices.READER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.COMMENTER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.COMMENTER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED),
], ],
) )
@@ -201,12 +207,13 @@ def test_comment_get_abilities_user_editor(link_role, link_reach):
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.EDITOR)] link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.EDITOR)]
) )
comment = factories.CommentFactory(document=document) comment = factories.CommentFactory(thread__document=document)
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": False, "destroy": False,
"update": False, "update": False,
"partial_update": False, "partial_update": False,
"reactions": True,
"retrieve": True, "retrieve": True,
} }
@@ -215,13 +222,13 @@ def test_comment_get_abilities_user_editor(link_role, link_reach):
"link_role,link_reach", "link_role,link_reach",
[ [
(LinkRoleChoices.READER, LinkReachChoices.PUBLIC), (LinkRoleChoices.READER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.COMMENTER, LinkReachChoices.PUBLIC),
(LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC), (LinkRoleChoices.EDITOR, LinkReachChoices.PUBLIC),
(LinkRoleChoices.READER, LinkReachChoices.RESTRICTED), (LinkRoleChoices.READER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.COMMENTER, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED), (LinkRoleChoices.EDITOR, LinkReachChoices.RESTRICTED),
(LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.READER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.COMMENTATOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.COMMENTER, LinkReachChoices.AUTHENTICATED),
(LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED), (LinkRoleChoices.EDITOR, LinkReachChoices.AUTHENTICATED),
], ],
) )
@@ -231,12 +238,13 @@ def test_comment_get_abilities_user_editor_own_comment(link_role, link_reach):
document = factories.DocumentFactory( document = factories.DocumentFactory(
link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.EDITOR)] link_role=link_role, link_reach=link_reach, users=[(user, RoleChoices.EDITOR)]
) )
comment = factories.CommentFactory(document=document, user=user) comment = factories.CommentFactory(thread__document=document, user=user)
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": True, "destroy": True,
"update": True, "update": True,
"partial_update": True, "partial_update": True,
"reactions": True,
"retrieve": True, "retrieve": True,
} }
@@ -246,13 +254,14 @@ def test_comment_get_abilities_user_admin():
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, RoleChoices.ADMIN)]) document = factories.DocumentFactory(users=[(user, RoleChoices.ADMIN)])
comment = factories.CommentFactory( comment = factories.CommentFactory(
document=document, user=random.choice([user, None]) thread__document=document, user=random.choice([user, None])
) )
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": True, "destroy": True,
"update": True, "update": True,
"partial_update": True, "partial_update": True,
"reactions": True,
"retrieve": True, "retrieve": True,
} }
@@ -262,12 +271,13 @@ def test_comment_get_abilities_user_owner():
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, RoleChoices.OWNER)]) document = factories.DocumentFactory(users=[(user, RoleChoices.OWNER)])
comment = factories.CommentFactory( comment = factories.CommentFactory(
document=document, user=random.choice([user, None]) thread__document=document, user=random.choice([user, None])
) )
assert comment.get_abilities(user) == { assert comment.get_abilities(user) == {
"destroy": True, "destroy": True,
"update": True, "update": True,
"partial_update": True, "partial_update": True,
"reactions": True,
"retrieve": True, "retrieve": True,
} }

View File

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

View File

@@ -134,13 +134,13 @@ def test_models_documents_soft_delete(depth):
[ [
(True, "restricted", "reader"), (True, "restricted", "reader"),
(True, "restricted", "editor"), (True, "restricted", "editor"),
(True, "restricted", "commentator"), (True, "restricted", "commenter"),
(False, "restricted", "reader"), (False, "restricted", "reader"),
(False, "restricted", "editor"), (False, "restricted", "editor"),
(False, "restricted", "commentator"), (False, "restricted", "commenter"),
(False, "authenticated", "reader"), (False, "authenticated", "reader"),
(False, "authenticated", "editor"), (False, "authenticated", "editor"),
(False, "authenticated", "commentator"), (False, "authenticated", "commenter"),
], ],
) )
def test_models_documents_get_abilities_forbidden( def test_models_documents_get_abilities_forbidden(
@@ -176,8 +176,8 @@ def test_models_documents_get_abilities_forbidden(
"move": False, "move": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"partial_update": False, "partial_update": False,
@@ -237,8 +237,8 @@ def test_models_documents_get_abilities_reader(
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": is_authenticated, "mask": is_authenticated,
@@ -278,14 +278,14 @@ def test_models_documents_get_abilities_reader(
(True, "authenticated"), (True, "authenticated"),
], ],
) )
def test_models_documents_get_abilities_commentator( def test_models_documents_get_abilities_commenter(
is_authenticated, reach, django_assert_num_queries is_authenticated, reach, django_assert_num_queries
): ):
""" """
Check abilities returned for a document giving commentator role to link holders Check abilities returned for a document giving commenter role to link holders
i.e anonymous users or authenticated users who have no specific role on the document. i.e anonymous users or authenticated users who have no specific role on the document.
""" """
document = factories.DocumentFactory(link_reach=reach, link_role="commentator") document = factories.DocumentFactory(link_reach=reach, link_role="commenter")
user = factories.UserFactory() if is_authenticated else AnonymousUser() user = factories.UserFactory() if is_authenticated else AnonymousUser()
expected_abilities = { expected_abilities = {
"accesses_manage": False, "accesses_manage": False,
@@ -298,6 +298,7 @@ def test_models_documents_get_abilities_commentator(
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": True, "comment": True,
"content": True,
"descendants": True, "descendants": True,
"cors_proxy": True, "cors_proxy": True,
"destroy": False, "destroy": False,
@@ -306,8 +307,8 @@ def test_models_documents_get_abilities_commentator(
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": is_authenticated, "mask": is_authenticated,
@@ -373,8 +374,8 @@ def test_models_documents_get_abilities_editor(
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": is_authenticated, "mask": is_authenticated,
@@ -429,8 +430,8 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"invite_owner": True, "invite_owner": True,
"link_configuration": True, "link_configuration": True,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -461,6 +462,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"children_create": False, "children_create": False,
"children_list": False, "children_list": False,
"collaboration_auth": False, "collaboration_auth": False,
"comment": False,
"descendants": False, "descendants": False,
"cors_proxy": False, "cors_proxy": False,
"content": False, "content": False,
@@ -470,8 +472,8 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": False, "mask": False,
@@ -516,8 +518,8 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
"invite_owner": False, "invite_owner": False,
"link_configuration": True, "link_configuration": True,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -572,8 +574,8 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -626,7 +628,7 @@ def test_models_documents_get_abilities_reader_user(
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": document.link_reach != "restricted" "comment": document.link_reach != "restricted"
and document.link_role in ["commentator", "editor"], and document.link_role in ["commenter", "editor"],
"descendants": True, "descendants": True,
"cors_proxy": True, "cors_proxy": True,
"content": True, "content": True,
@@ -636,8 +638,8 @@ def test_models_documents_get_abilities_reader_user(
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -668,12 +670,12 @@ def test_models_documents_get_abilities_reader_user(
@pytest.mark.parametrize("ai_access_setting", ["public", "authenticated", "restricted"]) @pytest.mark.parametrize("ai_access_setting", ["public", "authenticated", "restricted"])
def test_models_documents_get_abilities_commentator_user( def test_models_documents_get_abilities_commenter_user(
ai_access_setting, django_assert_num_queries ai_access_setting, django_assert_num_queries
): ):
"""Check abilities returned for the commentator of a document.""" """Check abilities returned for the commenter of a document."""
user = factories.UserFactory() user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, "commentator")]) document = factories.DocumentFactory(users=[(user, "commenter")])
access_from_link = ( access_from_link = (
document.link_reach != "restricted" and document.link_role == "editor" document.link_reach != "restricted" and document.link_role == "editor"
@@ -692,6 +694,7 @@ def test_models_documents_get_abilities_commentator_user(
"children_list": True, "children_list": True,
"collaboration_auth": True, "collaboration_auth": True,
"comment": True, "comment": True,
"content": True,
"descendants": True, "descendants": True,
"cors_proxy": True, "cors_proxy": True,
"destroy": False, "destroy": False,
@@ -700,8 +703,8 @@ def test_models_documents_get_abilities_commentator_user(
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -761,8 +764,8 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
"invite_owner": False, "invite_owner": False,
"link_configuration": False, "link_configuration": False,
"link_select_options": { "link_select_options": {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
"mask": True, "mask": True,
@@ -1465,14 +1468,14 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"public", "public",
"reader", "reader",
{ {
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
}, },
), ),
( (
"public", "public",
"commentator", "commenter",
{ {
"public": ["commentator", "editor"], "public": ["commenter", "editor"],
}, },
), ),
("public", "editor", {"public": ["editor"]}), ("public", "editor", {"public": ["editor"]}),
@@ -1480,16 +1483,16 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"authenticated", "authenticated",
"reader", "reader",
{ {
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
}, },
), ),
( (
"authenticated", "authenticated",
"commentator", "commenter",
{ {
"authenticated": ["commentator", "editor"], "authenticated": ["commenter", "editor"],
"public": ["commentator", "editor"], "public": ["commenter", "editor"],
}, },
), ),
( (
@@ -1502,17 +1505,17 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"reader", "reader",
{ {
"restricted": None, "restricted": None,
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
}, },
), ),
( (
"restricted", "restricted",
"commentator", "commenter",
{ {
"restricted": None, "restricted": None,
"authenticated": ["commentator", "editor"], "authenticated": ["commenter", "editor"],
"public": ["commentator", "editor"], "public": ["commenter", "editor"],
}, },
), ),
( (
@@ -1529,15 +1532,15 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
"public", "public",
None, None,
{ {
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
}, },
), ),
( (
None, None,
"reader", "reader",
{ {
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
), ),
@@ -1545,8 +1548,8 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
None, None,
None, None,
{ {
"public": ["reader", "commentator", "editor"], "public": ["reader", "commenter", "editor"],
"authenticated": ["reader", "commentator", "editor"], "authenticated": ["reader", "commenter", "editor"],
"restricted": None, "restricted": None,
}, },
), ),

View File

@@ -27,9 +27,9 @@ document_related_router.register(
basename="invitations", basename="invitations",
) )
document_related_router.register( document_related_router.register(
"comments", "threads",
viewsets.CommentViewSet, viewsets.ThreadViewSet,
basename="comments", basename="threads",
) )
document_related_router.register( document_related_router.register(
"ask-for-access", "ask-for-access",
@@ -37,6 +37,13 @@ document_related_router.register(
basename="ask_for_access", basename="ask_for_access",
) )
thread_related_router = DefaultRouter()
thread_related_router.register(
"comments",
viewsets.CommentViewSet,
basename="comments",
)
urlpatterns = [ urlpatterns = [
path( path(
@@ -49,6 +56,10 @@ urlpatterns = [
r"^documents/(?P<resource_id>[0-9a-z-]*)/", r"^documents/(?P<resource_id>[0-9a-z-]*)/",
include(document_related_router.urls), include(document_related_router.urls),
), ),
re_path(
r"^documents/(?P<resource_id>[0-9a-z-]*)/threads/(?P<thread_id>[0-9a-z-]*)/",
include(thread_related_router.urls),
),
] ]
), ),
), ),