diff --git a/arkindex/documents/api/elements.py b/arkindex/documents/api/elements.py index f08ae65497d605abcdba23bca2c3283615d5ffdd..2116376656dfff08d3af6814acdc982f37ecf321 100644 --- a/arkindex/documents/api/elements.py +++ b/arkindex/documents/api/elements.py @@ -573,14 +573,12 @@ class ElementsListBase(CorpusACLMixin, DestroyModelMixin, ListAPIView): @cached_property def selected_corpus(self): - # Retrieve the corpus and check user access rights only once + # Retrieve the corpus only once corpus_id = self.kwargs.get("corpus") if corpus_id is None: return - corpus = get_object_or_404(Corpus, id=corpus_id) - self.check_corpus_access(corpus) - return corpus + return get_object_or_404(Corpus, id=corpus_id) @property def folder_filter(self): @@ -1041,6 +1039,7 @@ class CorpusElements(ElementsListBase): def get_queryset(self): # Should not be possible due to the URL assert self.selected_corpus, "Missing corpus ID" + self.check_corpus_access(self.selected_corpus) return self.selected_corpus.elements.all() def get_filters(self) -> Q: diff --git a/arkindex/documents/api/export.py b/arkindex/documents/api/export.py index c2f6a52f9625714e95a8e6e21e1779fb5e7f42bd..9cec9a8351fa5079d0a623abd3b3394fb0db5e78 100644 --- a/arkindex/documents/api/export.py +++ b/arkindex/documents/api/export.py @@ -6,7 +6,7 @@ from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.functional import cached_property from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import permissions, serializers, status +from rest_framework import permissions, status from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.generics import ListCreateAPIView, RetrieveDestroyAPIView from rest_framework.response import Response @@ -78,7 +78,7 @@ class CorpusExportAPIView(ListCreateAPIView): Guest access is required on private corpora. """ ), - responses={302: serializers.Serializer}, + responses={302: None}, ), delete=extend_schema( operation_id="DestroyExport", diff --git a/arkindex/ponos/api.py b/arkindex/ponos/api.py index 368a44055b337a81a81b907d26b48c61e3df505d..a0a43c5ed8f59b9fe496d70a5f958ccbfe6acf04 100644 --- a/arkindex/ponos/api.py +++ b/arkindex/ponos/api.py @@ -4,7 +4,7 @@ from textwrap import dedent from django.db import transaction from django.shortcuts import get_object_or_404, redirect from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import serializers, status +from rest_framework import status from rest_framework.authentication import SessionAuthentication, TokenAuthentication from rest_framework.exceptions import NotFound, ValidationError from rest_framework.generics import CreateAPIView, ListCreateAPIView, RetrieveUpdateAPIView, UpdateAPIView @@ -200,12 +200,12 @@ class TaskUpdate(UpdateAPIView): The task must be in a final state to be restarted. """ ), + request=None, responses={201: TaskSerializer}, ), ) class TaskRestart(ProcessACLMixin, CreateAPIView): permission_classes = (IsVerified,) - serializer_class = serializers.Serializer def get_task(self): task = get_object_or_404( diff --git a/arkindex/process/api.py b/arkindex/process/api.py index 154f1273bac0cdfc23c2dbdad7d3f2261d8b847a..b291def10562450d1cf886f8a777f8e4778e92cc 100644 --- a/arkindex/process/api.py +++ b/arkindex/process/api.py @@ -1327,7 +1327,7 @@ class WorkerRunList(ProcessACLMixin, ListCreateAPIView): 201: WorkerRunSerializer, 400: OpenApiResponse( response=inline_serializer( - name="Error message with ID", + name="ErrorWithID", fields={ "detail": serializers.ListField(child=serializers.CharField(), required=False), "id": serializers.UUIDField(required=False, help_text="ID of an existing worker run") @@ -1557,6 +1557,7 @@ class WorkerRunDetails(ProcessACLMixin, RetrieveUpdateDestroyAPIView): False: ProcessElementLightSerializer, }, resource_type_field_name="with_image", + many=True, ), tags=["process"] )) @@ -2193,6 +2194,7 @@ class S3ImportCreate(CreateAPIView): post=extend_schema( operation_id="SelectProcessFailures", tags=["process"], + request=None, responses={204: None}, ), ) diff --git a/arkindex/process/tests/test_processes.py b/arkindex/process/tests/test_processes.py index 7140e8a3ef394c64800be592dcb5d675b18b3275..d0466ec05af91a109900aff3e1906427d8ae682e 100644 --- a/arkindex/process/tests/test_processes.py +++ b/arkindex/process/tests/test_processes.py @@ -3049,18 +3049,21 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.activity_state = ActivityState.Ready self.elts_process.save() self.client.force_login(self.user) + for state in unfinished_states: with self.subTest(state=state): self.elts_process.tasks.update(state=state) self.assertEqual(self.elts_process.state, state) + response = self.client.post( reverse("api:process-select-failures", kwargs={"pk": str(self.elts_process.id)}) ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual( - response.json(), - {"__all__": ["The process must be finished to select elements with failures."]}, - ) + + self.assertDictEqual( + response.json(), + {"__all__": ["The process must be finished to select elements with failures."]}, + ) def test_select_failed_elts_no_failure(self): self.elts_process.run() diff --git a/arkindex/project/openapi/schema.py b/arkindex/project/openapi/schema.py index b7737991377f34603fd112c528cf8fe000943fc7..4cac2e090c185c1700db48691a3910b858d83d26 100644 --- a/arkindex/project/openapi/schema.py +++ b/arkindex/project/openapi/schema.py @@ -32,5 +32,6 @@ class AutoSchema(BaseAutoSchema): Spectacular does not include any pagination parameters. """ operation = super().get_operation(*args, **kwargs) - operation["x-paginated"] = self._is_list_view() and self._get_paginator() is not None + if operation is not None: + operation["x-paginated"] = self._is_list_view() and self._get_paginator() is not None return operation diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py index 30feb9bf9877e2e140b40d43ed051572dc5c7826..fe463ed9634eb16e1013d40337585d13e485423c 100644 --- a/arkindex/project/settings.py +++ b/arkindex/project/settings.py @@ -219,6 +219,9 @@ SIMPLE_JWT = { SPECTACULAR_SETTINGS = { "CAMELIZE_NAMES": True, + # Remove the automatically generated `description` that lists the members of all enums, + # which duplicates the enum choices already displayed by ReDoc and does not add any documentation + "ENUM_GENERATE_CHOICE_DESCRIPTION": False, "SCHEMA_PATH_PREFIX": "/api/v1/", "TITLE": "Arkindex API", "CONTACT": { diff --git a/arkindex/training/api.py b/arkindex/training/api.py index 77289fab01822ca7819042ef6895be4696d7c4aa..20f10a203fb5418b4f8c2f94a6e609d555e0330b 100644 --- a/arkindex/training/api.py +++ b/arkindex/training/api.py @@ -522,9 +522,7 @@ class ModelCompatibleWorkerManage(CreateAPIView, DestroyAPIView): "Retrieve a download url for a model_version. A secret token is required to have access to the model version." "This endpoint does **not** require to be logged in." ), - responses={ - 302: serializers.Serializer, - }, + responses={302: None}, parameters=[ OpenApiParameter( "token", diff --git a/arkindex/training/serializers.py b/arkindex/training/serializers.py index 2f658f8b730a1bc6b6bd25c05350c0625ad0595e..48ec505718ece1ef8eede564a89da1ec14680311 100644 --- a/arkindex/training/serializers.py +++ b/arkindex/training/serializers.py @@ -171,7 +171,7 @@ class ModelVersionLightSerializer(serializers.ModelSerializer): model = ModelLightSerializer(default=_model_from_context) state = EnumField(ModelVersionState) - configuration = serializers.JSONField(style={"base_template": "textarea.html"}) + configuration = serializers.DictField(style={"base_template": "textarea.html"}) class Meta: model = ModelVersion @@ -190,7 +190,7 @@ class ModelVersionSerializer(serializers.ModelSerializer): allow_null=True, ) description = serializers.CharField(required=False, style={"base_template": "textarea.html"}) - configuration = serializers.JSONField(required=False, decoder=None, encoder=None, style={"base_template": "textarea.html"}) + configuration = serializers.DictField(required=False, style={"base_template": "textarea.html"}) tag = serializers.CharField(allow_null=True, max_length=50, required=False, default=None) state = EnumField(ModelVersionState, read_only=True) s3_url = serializers.SerializerMethodField(read_only=True, help_text="Only returned if the user has **contributor** access to the model, and the model is available.") diff --git a/arkindex/users/api.py b/arkindex/users/api.py index 5bc55e012c13abb97df750e9469ab14de34420a2..e4bfc44db7d8209ce6f8cd0b6e06770f3f4d0ef8 100644 --- a/arkindex/users/api.py +++ b/arkindex/users/api.py @@ -13,7 +13,7 @@ from django_rq.queues import get_queue from django_rq.settings import QUEUES from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view -from rest_framework import serializers, status +from rest_framework import status from rest_framework.exceptions import AuthenticationFailed, NotFound, PermissionDenied, ValidationError from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView from rest_framework.response import Response @@ -180,7 +180,7 @@ class UserEmailVerification(APIView): required=True, ) ], - responses={302: serializers.Serializer} + responses={302: None}, ) def get(self, *args, **kwargs): if not all(arg in self.request.GET for arg in ("email", "token")): diff --git a/arkindex/users/serializers.py b/arkindex/users/serializers.py index 8bf49eeea60a226a5c1c9bae0c2c449e7962c148..d30ba1c3691a73bc3148e7f68c55bd21d9c25859 100644 --- a/arkindex/users/serializers.py +++ b/arkindex/users/serializers.py @@ -161,9 +161,7 @@ class JobSerializer(serializers.Serializer): ended_at = serializers.DateTimeField(read_only=True, allow_null=True) def get_status(self, instance) -> str: - """ - Avoid causing more Redis queries to fetch a job's current status - Note that a job status is part of a JobStatus enum, - but the enum is just a plain object and not an Enum for Py2 compatibility. - """ + # Avoid causing more Redis queries to fetch a job's current status + # Note that a job status is part of a JobStatus enum, + # but the enum is just a plain object and not an Enum for Py2 compatibility. return instance.get_status(refresh=False) diff --git a/requirements.txt b/requirements.txt index dc936a8639ede1e2d7cb9c1d276b8c11fe45bb83..0c6424ce3eb36d753d34f21bf83554fa0ff068f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ django-rq==2.10.1 djangorestframework==3.13.1 djangorestframework-simplejwt==5.2.2 docker==7.0.0 -drf-spectacular==0.21.2 +drf-spectacular==0.27.2 python-magic==0.4.27 python-memcached==1.59 pytz==2023.3