✨(back) document as for access CRUD
We introduce a new model for user wanted to access a document or upgrade their role if they already have access. The viewsets does not implement PUT and PATCH, we don't need it for now.
This commit is contained in:
@@ -665,6 +665,37 @@ class InvitationSerializer(serializers.ModelSerializer):
|
|||||||
return role
|
return role
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentAskForAccessCreateSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for creating a document ask for access."""
|
||||||
|
|
||||||
|
role = serializers.ChoiceField(choices=models.RoleChoices.choices, required=False, default=models.RoleChoices.READER)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentAskForAccessSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for document ask for access model"""
|
||||||
|
|
||||||
|
abilities = serializers.SerializerMethodField(read_only=True)
|
||||||
|
user = UserSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.DocumentAskForAccess
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"document",
|
||||||
|
"user",
|
||||||
|
"role",
|
||||||
|
"created_at",
|
||||||
|
"abilities",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "document", "user", "role", "created_at", "abilities"]
|
||||||
|
|
||||||
|
def get_abilities(self, invitation) -> dict:
|
||||||
|
"""Return abilities of the logged-in user on the instance."""
|
||||||
|
request = self.context.get("request")
|
||||||
|
if request:
|
||||||
|
return invitation.get_abilities(request.user)
|
||||||
|
return {}
|
||||||
|
|
||||||
class VersionFilterSerializer(serializers.Serializer):
|
class VersionFilterSerializer(serializers.Serializer):
|
||||||
"""Validate version filters applied to the list endpoint."""
|
"""Validate version filters applied to the list endpoint."""
|
||||||
|
|
||||||
|
|||||||
@@ -1774,6 +1774,70 @@ class InvitationViewset(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentAskForAccessViewSet(
|
||||||
|
drf.mixins.ListModelMixin,
|
||||||
|
drf.mixins.RetrieveModelMixin,
|
||||||
|
drf.mixins.DestroyModelMixin,
|
||||||
|
viewsets.GenericViewSet,
|
||||||
|
):
|
||||||
|
"""API ViewSet for asking for access to a document."""
|
||||||
|
|
||||||
|
lookup_field = "id"
|
||||||
|
pagination_class = Pagination
|
||||||
|
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
|
||||||
|
queryset = models.DocumentAskForAccess.objects.all()
|
||||||
|
serializer_class = serializers.DocumentAskForAccessSerializer
|
||||||
|
_document = None
|
||||||
|
|
||||||
|
def get_document_or_404(self):
|
||||||
|
"""Get the document related to the viewset or raise a 404 error."""
|
||||||
|
if self._document is None:
|
||||||
|
try:
|
||||||
|
self._document = models.Document.objects.get(
|
||||||
|
pk=self.kwargs["resource_id"]
|
||||||
|
)
|
||||||
|
except models.Document.DoesNotExist as e:
|
||||||
|
raise drf.exceptions.NotFound("Document not found.") from e
|
||||||
|
return self._document
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Return the queryset according to the action."""
|
||||||
|
document = self.get_document_or_404()
|
||||||
|
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
queryset = queryset.filter(document=document)
|
||||||
|
|
||||||
|
roles = set(document.get_roles(self.request.user))
|
||||||
|
is_owner_or_admin = bool(roles.intersection(set(models.PRIVILEGED_ROLES)))
|
||||||
|
if not is_owner_or_admin:
|
||||||
|
queryset = queryset.filter(user=self.request.user)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
"""Create a document ask for access resource."""
|
||||||
|
document = self.get_document_or_404()
|
||||||
|
|
||||||
|
serializer = serializers.DocumentAskForAccessCreateSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
|
||||||
|
if queryset.filter(user=request.user).exists():
|
||||||
|
return drf.response.Response(
|
||||||
|
{"detail": "You already ask to access to this document."},
|
||||||
|
status=drf.status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
models.DocumentAskForAccess.objects.create(
|
||||||
|
document=document,
|
||||||
|
user=request.user,
|
||||||
|
role=serializer.validated_data["role"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return drf.response.Response(status=drf.status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
class ConfigView(drf.views.APIView):
|
class ConfigView(drf.views.APIView):
|
||||||
"""API ViewSet for sharing some public settings."""
|
"""API ViewSet for sharing some public settings."""
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,17 @@ class TeamDocumentAccessFactory(factory.django.DjangoModelFactory):
|
|||||||
role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices])
|
role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices])
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentAskForAccessFactory(factory.django.DjangoModelFactory):
|
||||||
|
"""Create fake document ask for access for testing."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.DocumentAskForAccess
|
||||||
|
|
||||||
|
document = factory.SubFactory(DocumentFactory)
|
||||||
|
user = factory.SubFactory(UserFactory)
|
||||||
|
role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices])
|
||||||
|
|
||||||
|
|
||||||
class TemplateFactory(factory.django.DjangoModelFactory):
|
class TemplateFactory(factory.django.DjangoModelFactory):
|
||||||
"""A factory to create templates"""
|
"""A factory to create templates"""
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Generated by Django 5.2.3 on 2025-06-18 10:02
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("core", "0021_activate_unaccent_extension"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DocumentAskForAccess",
|
||||||
|
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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"role",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("reader", "Reader"),
|
||||||
|
("editor", "Editor"),
|
||||||
|
("administrator", "Administrator"),
|
||||||
|
("owner", "Owner"),
|
||||||
|
],
|
||||||
|
default="reader",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"document",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="ask_for_accesses",
|
||||||
|
to="core.document",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="ask_for_accesses",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Document ask for access",
|
||||||
|
"verbose_name_plural": "Document ask for accesses",
|
||||||
|
"db_table": "impress_document_ask_for_access",
|
||||||
|
"constraints": [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=("user", "document"),
|
||||||
|
name="unique_document_ask_for_access_user",
|
||||||
|
violation_error_message="This user has already asked for access to this document.",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1149,6 +1149,65 @@ class DocumentAccess(BaseAccess):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentAskForAccess(BaseModel):
|
||||||
|
"""Relation model to ask for access to a document."""
|
||||||
|
|
||||||
|
document = models.ForeignKey(
|
||||||
|
Document, on_delete=models.CASCADE, related_name="ask_for_accesses"
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE, related_name="ask_for_accesses"
|
||||||
|
)
|
||||||
|
|
||||||
|
role = models.CharField(
|
||||||
|
max_length=20, choices=RoleChoices.choices, default=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "impress_document_ask_for_access"
|
||||||
|
verbose_name = _("Document ask for access")
|
||||||
|
verbose_name_plural = _("Document ask for accesses")
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["user", "document"],
|
||||||
|
name="unique_document_ask_for_access_user",
|
||||||
|
violation_error_message=_(
|
||||||
|
"This user has already asked for access to this document."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user!s} asked for access to document {self.document!s}"
|
||||||
|
|
||||||
|
def get_abilities(self, user):
|
||||||
|
"""Compute and return abilities for a given user."""
|
||||||
|
roles = []
|
||||||
|
|
||||||
|
if user.is_authenticated:
|
||||||
|
teams = user.teams
|
||||||
|
try:
|
||||||
|
roles = self.user_roles or []
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
roles = self.document.accesses.filter(
|
||||||
|
models.Q(user=user) | models.Q(team__in=teams),
|
||||||
|
).values_list("role", flat=True)
|
||||||
|
except (self._meta.model.DoesNotExist, IndexError):
|
||||||
|
roles = []
|
||||||
|
|
||||||
|
is_admin_or_owner = bool(
|
||||||
|
set(roles).intersection({RoleChoices.OWNER, RoleChoices.ADMIN})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"destroy": is_admin_or_owner,
|
||||||
|
"update": is_admin_or_owner,
|
||||||
|
"partial_update": is_admin_or_owner,
|
||||||
|
"retrieve": is_admin_or_owner,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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."""
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,445 @@
|
|||||||
|
"""Test API for document ask for access."""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.api.serializers import UserSerializer
|
||||||
|
from core.factories import (
|
||||||
|
DocumentAskForAccessFactory,
|
||||||
|
DocumentFactory,
|
||||||
|
UserDocumentAccessFactory,
|
||||||
|
UserFactory,
|
||||||
|
)
|
||||||
|
from core.models import DocumentAccess, DocumentAskForAccess, RoleChoices
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
## Create
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_create_anonymous():
|
||||||
|
"""Anonymous users should not be able to create a document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
response = client.post(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_create_invalid_document_id():
|
||||||
|
"""Invalid document ID should return a 404 error."""
|
||||||
|
user = UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
response = client.post(f"/api/v1.0/documents/{uuid.uuid4()}/ask-for-access/")
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_create_authenticated():
|
||||||
|
"""Authenticated users should be able to create a document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
user = UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.post(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
assert DocumentAskForAccess.objects.filter(
|
||||||
|
document=document,
|
||||||
|
user=user,
|
||||||
|
role=RoleChoices.READER,
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_create_authenticated_specific_role():
|
||||||
|
"""
|
||||||
|
Authenticated users should be able to create a document ask for access with a specific role.
|
||||||
|
"""
|
||||||
|
document = DocumentFactory()
|
||||||
|
user = UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/",
|
||||||
|
data={"role": RoleChoices.EDITOR},
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
assert DocumentAskForAccess.objects.filter(
|
||||||
|
document=document,
|
||||||
|
user=user,
|
||||||
|
role=RoleChoices.EDITOR,
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_create_authenticated_already_has_access():
|
||||||
|
"""Authenticated users with existing access can ask for access with a different role."""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, RoleChoices.READER)])
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/",
|
||||||
|
data={"role": RoleChoices.EDITOR},
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
assert DocumentAskForAccess.objects.filter(
|
||||||
|
document=document,
|
||||||
|
user=user,
|
||||||
|
role=RoleChoices.EDITOR,
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_create_authenticated_already_has_ask_for_access():
|
||||||
|
"""
|
||||||
|
Authenticated users with existing ask for access can not ask for a new access on this document.
|
||||||
|
"""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, RoleChoices.READER)])
|
||||||
|
DocumentAskForAccessFactory(document=document, user=user, role=RoleChoices.READER)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/",
|
||||||
|
data={"role": RoleChoices.EDITOR},
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response.json() == {"detail": "You already ask to access to this document."}
|
||||||
|
|
||||||
|
|
||||||
|
## List
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_list_anonymous():
|
||||||
|
"""Anonymous users should not be able to list document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_list_authenticated():
|
||||||
|
"""Authenticated users should be able to list document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(UserFactory())
|
||||||
|
|
||||||
|
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"count": 0,
|
||||||
|
"next": None,
|
||||||
|
"previous": None,
|
||||||
|
"results": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_list_authenticated_own_request():
|
||||||
|
"""Authenticated users should be able to list their own document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
user = UserFactory()
|
||||||
|
user_data = UserSerializer(instance=user).data
|
||||||
|
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, user=user, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"count": 1,
|
||||||
|
"next": None,
|
||||||
|
"previous": None,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": str(document_ask_for_access.id),
|
||||||
|
"document": str(document.id),
|
||||||
|
"user": user_data,
|
||||||
|
"role": RoleChoices.READER,
|
||||||
|
"created_at": document_ask_for_access.created_at.isoformat().replace(
|
||||||
|
"+00:00", "Z"
|
||||||
|
),
|
||||||
|
"abilities": {
|
||||||
|
"destroy": False,
|
||||||
|
"update": False,
|
||||||
|
"partial_update": False,
|
||||||
|
"retrieve": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_list_authenticated_other_document():
|
||||||
|
"""Authenticated users should not be able to list document ask for access of other documents."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(UserFactory())
|
||||||
|
|
||||||
|
other_document = DocumentFactory()
|
||||||
|
DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=other_document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get(f"/api/v1.0/documents/{other_document.id}/ask-for-access/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"count": 0,
|
||||||
|
"next": None,
|
||||||
|
"previous": None,
|
||||||
|
"results": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role", [RoleChoices.READER, RoleChoices.EDITOR])
|
||||||
|
def test_api_documents_ask_for_access_list_non_owner_or_admin(role):
|
||||||
|
"""Non owner or admin users should not be able to list document ask for access."""
|
||||||
|
|
||||||
|
user = UserFactory()
|
||||||
|
|
||||||
|
document = DocumentFactory(users=[(user, role)])
|
||||||
|
DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"count": 0,
|
||||||
|
"next": None,
|
||||||
|
"previous": None,
|
||||||
|
"results": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role", [RoleChoices.OWNER])
|
||||||
|
def test_api_documents_ask_for_access_list_owner_or_admin(role):
|
||||||
|
"""Owner or admin users should be able to list document ask for access."""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, role)])
|
||||||
|
document_ask_for_accesses = DocumentAskForAccessFactory.create_batch(
|
||||||
|
3, document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"count": 3,
|
||||||
|
"next": None,
|
||||||
|
"previous": None,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": str(document_ask_for_access.id),
|
||||||
|
"document": str(document.id),
|
||||||
|
"user": UserSerializer(instance=document_ask_for_access.user).data,
|
||||||
|
"role": RoleChoices.READER,
|
||||||
|
"created_at": document_ask_for_access.created_at.isoformat().replace(
|
||||||
|
"+00:00", "Z"
|
||||||
|
),
|
||||||
|
"abilities": {
|
||||||
|
"destroy": True,
|
||||||
|
"update": True,
|
||||||
|
"partial_update": True,
|
||||||
|
"retrieve": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for document_ask_for_access in document_ask_for_accesses
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Retrieve
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_retrieve_anonymous():
|
||||||
|
"""Anonymous users should not be able to retrieve document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
response = client.get(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_retrieve_authenticated():
|
||||||
|
"""Authenticated users should not be able to retrieve document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(UserFactory())
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role", [RoleChoices.READER, RoleChoices.EDITOR])
|
||||||
|
def test_api_documents_ask_for_access_retrieve_authenticated_non_owner_or_admin(role):
|
||||||
|
"""Non owner or admin users should not be able to retrieve document ask for access."""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, role)])
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||||
|
def test_api_documents_ask_for_access_retrieve_owner_or_admin(role):
|
||||||
|
"""Owner or admin users should be able to retrieve document ask for access."""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, role)])
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
user_data = UserSerializer(instance=document_ask_for_access.user).data
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"id": str(document_ask_for_access.id),
|
||||||
|
"document": str(document.id),
|
||||||
|
"user": user_data,
|
||||||
|
"role": RoleChoices.READER,
|
||||||
|
"created_at": document_ask_for_access.created_at.isoformat().replace(
|
||||||
|
"+00:00", "Z"
|
||||||
|
),
|
||||||
|
"abilities": {
|
||||||
|
"destroy": True,
|
||||||
|
"update": True,
|
||||||
|
"partial_update": True,
|
||||||
|
"retrieve": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Delete
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_delete_anonymous():
|
||||||
|
"""Anonymous users should not be able to delete document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_documents_ask_for_access_delete_authenticated():
|
||||||
|
"""Authenticated users should not be able to delete document ask for access."""
|
||||||
|
document = DocumentFactory()
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(UserFactory())
|
||||||
|
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role", [RoleChoices.READER, RoleChoices.EDITOR])
|
||||||
|
def test_api_documents_ask_for_access_delete_authenticated_non_owner_or_admin(role):
|
||||||
|
"""Non owner or admin users should not be able to delete document ask for access."""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, role)])
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||||
|
def test_api_documents_ask_for_access_delete_owner_or_admin(role):
|
||||||
|
"""Owner or admin users should be able to delete document ask for access."""
|
||||||
|
user = UserFactory()
|
||||||
|
document = DocumentFactory(users=[(user, role)])
|
||||||
|
document_ask_for_access = DocumentAskForAccessFactory(
|
||||||
|
document=document, role=RoleChoices.READER
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/documents/{document.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||||
|
)
|
||||||
|
assert response.status_code == 204
|
||||||
|
assert not DocumentAskForAccess.objects.filter(
|
||||||
|
id=document_ask_for_access.id
|
||||||
|
).exists()
|
||||||
@@ -27,6 +27,12 @@ document_related_router.register(
|
|||||||
basename="invitations",
|
basename="invitations",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
document_related_router.register(
|
||||||
|
"ask-for-access",
|
||||||
|
viewsets.DocumentAskForAccessViewSet,
|
||||||
|
basename="ask_for_access",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# - Routes nested under a template
|
# - Routes nested under a template
|
||||||
template_related_router = DefaultRouter()
|
template_related_router = DefaultRouter()
|
||||||
|
|||||||
Reference in New Issue
Block a user