diff --git a/arkindex/documents/api/elements.py b/arkindex/documents/api/elements.py index 7024a304eee36fd9216191c439caf98623c24761..83cb626b504ee295e31ce650ec5588540345d1ca 100644 --- a/arkindex/documents/api/elements.py +++ b/arkindex/documents/api/elements.py @@ -28,7 +28,6 @@ from rest_framework.generics import ( ListAPIView, ListCreateAPIView, RetrieveUpdateDestroyAPIView, - UpdateAPIView, ) from rest_framework.mixins import DestroyModelMixin from rest_framework.response import Response @@ -1445,20 +1444,60 @@ class ElementTypeCreate(CreateAPIView): @extend_schema_view( - put=extend_schema(operation_id='UpdateElementType', tags=['elements']), - patch=extend_schema(operation_id='PartialUpdateElementType', tags=['elements']), -) -class ElementTypeUpdate(UpdateAPIView): - """ - Update an existing element type. + get=extend_schema( + operation_id='RetrieveElementType', + description=dedent(""" + Retrieve an existing element type. - This requires admin access to the element type's corpus. - """ + This requires read access to the element type's corpus. + """), + ), + put=extend_schema( + operation_id='UpdateElementType', + description=dedent(""" + Update an existing element type. + + This requires admin access to the element type's corpus. + """), + ), + patch=extend_schema( + operation_id='PartialUpdateElementType', + description=dedent(""" + Update parts of an existing element type. + + This requires admin access to the element type's corpus. + """), + ), + delete=extend_schema( + operation_id='DestroyElementType', + description=dedent(""" + Delete an existing element type if it has no associated elements. + + This requires admin access to the element's type corpus. + """) + ) +) +@extend_schema(tags=['elements']) +class ElementTypeUpdate(RetrieveUpdateDestroyAPIView): serializer_class = ElementTypeLightSerializer permission_classes = (IsVerifiedOrReadOnly, ) def get_queryset(self): - return ElementType.objects.filter(corpus__in=Corpus.objects.admin(self.request.user)) + if self.request.method in permissions.SAFE_METHODS: + corpora = Corpus.objects.readable(self.request.user) + else: + corpora = Corpus.objects.admin(self.request.user) + + return ElementType.objects.filter(corpus__in=corpora) + + def perform_destroy(self, instance): + if instance.elements.exists(): + raise ValidationError({'detail': ['Some elements are using this element type.']}) + + # Update DataImport element type foreign keys manually because letting Django do that can fill up the RAM + instance.folder_imports.update(folder_type=None) + instance.element_imports.update(element_type=None) + super().perform_destroy(instance) @extend_schema_view( diff --git a/arkindex/documents/tests/test_element_type.py b/arkindex/documents/tests/test_element_type.py index 8b3e7e1105dd2249b9d3d9472fd17d93d739be34..be7589fe305c2cf21963fe44139e6ca7d9160b65 100644 --- a/arkindex/documents/tests/test_element_type.py +++ b/arkindex/documents/tests/test_element_type.py @@ -201,3 +201,30 @@ class TestElementType(FixtureAPITestCase): 'folder': self.element_type.folder } ) + + def test_delete_requires_login(self): + response = self.client.delete(reverse('api:element-type', kwargs={'pk': self.element_type.id})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_requires_admin(self): + private_element_type = ElementType.objects.create(corpus=self.private_corpus, slug='element', display_name='Element') + self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) + self.client.force_login(self.user) + response = self.client.delete(reverse('api:element-type', kwargs={'pk': private_element_type.id})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete(self): + self.client.force_login(self.superuser) + with self.assertNumQueries(11): + response = self.client.delete(reverse('api:element-type', kwargs={'pk': self.element_type.id})) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + with self.assertRaises(ElementType.DoesNotExist): + self.element_type.refresh_from_db() + + def test_delete_has_elements(self): + self.corpus.elements.create(name="can't touch this", type=self.element_type) + self.client.force_login(self.superuser) + with self.assertNumQueries(4): + response = self.client.delete(reverse('api:element-type', kwargs={'pk': self.element_type.id})) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), {'detail': ['Some elements are using this element type.']})