diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst index db16dce11..747de7819 100644 --- a/docs/CHANGELOG.rst +++ b/docs/CHANGELOG.rst @@ -18,6 +18,8 @@ Added endpoint - Allow staff users to create new ``VariantExperiment`` objects through the API endpoint +- Allow staff users to delet ``Variant`` and ``VariantExperiment`` via API +- Allow users with permissions to delete ``VariantCall`` objects via the API Changed ------- diff --git a/resolwe_bio/variants/tests/test_variant.py b/resolwe_bio/variants/tests/test_variant.py index 43a228a81..419c21ec9 100644 --- a/resolwe_bio/variants/tests/test_variant.py +++ b/resolwe_bio/variants/tests/test_variant.py @@ -562,11 +562,13 @@ def test_add_variant_annotations(self): class VariantTest(PrepareDataMixin, TestCase): def setUp(self) -> None: - self.view = VariantViewSet.as_view({"get": "list", "post": "create"}) + self.view = VariantViewSet.as_view( + {"get": "list", "post": "create", "delete": "destroy"} + ) return super().setUp() - def test_create(self): - """Test the Variant creation. + def test_create_destroy(self): + """Test the Variant create and destroy. Only users with staff status are allowed to create Variant objects. """ @@ -606,7 +608,45 @@ def test_create(self): response = self.view(request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # Check that the created object exists. - Variant.objects.get(**response.data) + created = Variant.objects.get(**response.data) + + # Delete as anonymous user. + request = APIRequestFactory().delete( + "/variant", {"pk": created.pk}, format="json" + ) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "Authentication credentials were not provided.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete as non-staff user. + self.contributor.is_staff = False + self.contributor.save(update_fields=["is_staff"]) + request = APIRequestFactory().delete( + "/variant", {"pk": created.pk}, format="json" + ) + force_authenticate(request, self.contributor) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "You do not have permission to perform this action.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete as staff user. + self.contributor.is_staff = True + self.contributor.save(update_fields=["is_staff"]) + request = APIRequestFactory().delete( + "/variant", {"pk": created.pk}, format="json" + ) + force_authenticate(request, self.contributor) + response = self.view(request, pk=created.pk) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + with self.assertRaises(Variant.DoesNotExist): + created.refresh_from_db() + self.contributor.is_staff = False self.contributor.save(update_fields=["is_staff"]) @@ -1177,10 +1217,12 @@ def test_ordering(self): class VariantCallTest(PrepareDataMixin, TestCase): def setUp(self) -> None: - self.view = VariantCallViewSet.as_view({"get": "list", "post": "create"}) + self.view = VariantCallViewSet.as_view( + {"get": "list", "post": "create", "delete": "destroy"} + ) return super().setUp() - def test_create(self): + def test_create_destroy(self): """Test the VariantCall creation.""" variant_call_data = { "sample": self.sample.pk, @@ -1273,6 +1315,57 @@ def test_create(self): }, ) + # Delete as anonymous user. + request = APIRequestFactory().delete( + "/variantcall", {"pk": created.pk}, format="json" + ) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "Authentication credentials were not provided.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete without required permission to data and sample. + self.sample.set_permission(Permission.VIEW, user) + data.set_permission(Permission.VIEW, user) + request = APIRequestFactory().delete( + "/variantcall", {"pk": created.pk}, format="json" + ) + force_authenticate(request, user) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "You do not have permission to perform this action.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete without required permission to data. + self.sample.set_permission(Permission.EDIT, user) + data.set_permission(Permission.VIEW, user) + request = APIRequestFactory().delete( + "/variantcall", {"pk": created.pk}, format="json" + ) + force_authenticate(request, user) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "You do not have permission to perform this action.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete with required permission to data. + self.sample.set_permission(Permission.EDIT, user) + data.set_permission(Permission.EDIT, user) + request = APIRequestFactory().delete( + "/variantcall", {"pk": created.pk}, format="json" + ) + force_authenticate(request, user) + response = self.view(request, pk=created.pk) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + with self.assertRaises(VariantCall.DoesNotExist): + created.refresh_from_db() + def test_filter(self): # No filter no permission for public. request = APIRequestFactory().get("/variantcall") @@ -1497,11 +1590,13 @@ def test_ordering(self): class VariantExperimentTest(PrepareDataMixin, TestCase): def setUp(self) -> None: - self.view = VariantExperimentViewSet.as_view({"get": "list", "post": "create"}) + self.view = VariantExperimentViewSet.as_view( + {"get": "list", "post": "create", "delete": "destroy"} + ) return super().setUp() - def test_create(self): - """Test the VariantExperiment creation. + def test_create_destroy(self): + """Test the VariantExperiment creation and deletion. Only users with staff status are allowed to create VariantExperiment objects. """ @@ -1543,7 +1638,45 @@ def test_create(self): response = self.view(request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # Check that the created object exists. - VariantExperiment.objects.get(**response.data) + created = VariantExperiment.objects.get(**response.data) + self.contributor.is_staff = False + self.contributor.save(update_fields=["is_staff"]) + + # Delete as anonymous user. + request = APIRequestFactory().delete( + "/variantexperiment", {"pk": created.pk}, format="json" + ) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "Authentication credentials were not provided.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete as non-staff user. + request = APIRequestFactory().delete( + "/variantexperiment", {"pk": created.pk}, format="json" + ) + force_authenticate(request, self.contributor) + response = self.view(request, pk=created.pk) + self.assertContains( + response, + "You do not have permission to perform this action.", + status_code=status.HTTP_403_FORBIDDEN, + ) + + # Delete as staff user. + self.contributor.is_staff = True + self.contributor.save(update_fields=["is_staff"]) + request = APIRequestFactory().delete( + "/variantexperiment", {"pk": created.pk}, format="json" + ) + force_authenticate(request, self.contributor) + response = self.view(request, pk=created.pk) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + with self.assertRaises(VariantExperiment.DoesNotExist): + created.refresh_from_db() + self.contributor.is_staff = False self.contributor.save(update_fields=["is_staff"]) diff --git a/resolwe_bio/variants/views.py b/resolwe_bio/variants/views.py index 259a72322..c04851607 100644 --- a/resolwe_bio/variants/views.py +++ b/resolwe_bio/variants/views.py @@ -35,7 +35,10 @@ class VariantViewSet( - mixins.ListModelMixin, ResolweCreateModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + ResolweCreateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, ): """Variant endpoint.""" @@ -65,7 +68,10 @@ class VariantAnnotationViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class VariantCallViewSet( - mixins.ListModelMixin, ResolweCreateModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + ResolweCreateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, ): """VariantCall endpoint. @@ -94,9 +100,22 @@ def perform_create(self, serializer: serializers.BaseSerializer) -> None: return super().perform_create(serializer) + def perform_destroy(self, instance: VariantCall) -> None: + """Check if user has EDIT permission on the sample.""" + if not instance.sample.has_permission(Permission.EDIT, self.request.user): + raise exceptions.PermissionDenied() + if data := instance.data: + if not data.has_permission(Permission.EDIT, self.request.user): + raise exceptions.PermissionDenied() + + return super().perform_destroy(instance) + class VariantExperimentViewSet( - mixins.ListModelMixin, ResolweCreateModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + ResolweCreateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, ): """VariantExperiment endpoint."""