Repository: WeblateOrg/weblate

Issue:
Extend screenshots API
**Is your feature request related to a problem? Please describe.**
The API for screenshots is still missing some bits:

* Delete screenshots, DELETE /api/screenshots/(int: id)/
* Remove unit association from screenshot, DELETE /api/screenshots/(int: id)/units/(int: unitid)/
* Edit screenshot, PUT/PATCH /api/screenshots/(int: id)/

**Describe the solution you'd like**
Complete CRUD for screenshots.

**Additional context**
Followup for https://github.com/WeblateOrg/weblate/issues/3076

<bountysource-plugin>

---
Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/91349010-extend-screenshots-api?utm_campaign=plugin&utm_content=tracker%2F253393&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F253393&utm_medium=issues&utm_source=github).
</bountysource-plugin>


Discussion:

Resolving patch:
diff --git a/weblate/api/views.py b/weblate/api/views.py
--- a/weblate/api/views.py
+++ b/weblate/api/views.py
@@ -1090,7 +1090,7 @@ def get_queryset(self):
         return Unit.objects.filter_access(self.request.user).order_by("id")
 
 
-class ScreenshotViewSet(DownloadViewSet, CreateModelMixin):
+class ScreenshotViewSet(DownloadViewSet, viewsets.ModelViewSet):
     """Screenshots API."""
 
     queryset = Screenshot.objects.none()
@@ -1154,6 +1154,24 @@ def units(self, request, **kwargs):
 
         return Response(serializer.data, status=status.HTTP_200_OK,)
 
+    @action(detail=True, methods=["delete"], url_path="units/(?P<unit_id>[^/.]+)")
+    def delete_units(self, request, pk, unit_id):
+        obj = self.get_object()
+        if not request.user.has_perm("screenshot.edit", obj.component):
+            raise PermissionDenied()
+
+        try:
+            source_string = obj.component.source_translation.unit_set.get(
+                pk=int(unit_id)
+            )
+        except (Unit.DoesNotExist, ValueError) as error:
+            return Response(
+                data={"result": "Unsuccessful", "detail": force_str(error)},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+        obj.units.remove(source_string)
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
     def create(self, request, *args, **kwargs):
         required_params = ["name", "image", "project_slug", "component_slug"]
         for param in required_params:
@@ -1186,6 +1204,18 @@ def create(self, request, *args, **kwargs):
             )
             return Response(serializer.data, status=status.HTTP_201_CREATED,)
 
+    def update(self, request, *args, **kwargs):
+        instance = self.get_object()
+        if not request.user.has_perm("screenshot.edit", instance.component):
+            self.permission_denied(request, message="Can not edit screenshot.")
+        return super().update(request, *args, **kwargs)
+
+    def destroy(self, request, *args, **kwargs):
+        instance = self.get_object()
+        if not request.user.has_perm("screenshot.delete", instance.component):
+            self.permission_denied(request, message="Can not delete screenshot.")
+        return super().destroy(request, *args, **kwargs)
+
 
 class ChangeFilter(filters.FilterSet):
     timestamp = filters.IsoDateTimeFromToRangeFilter()


Test patch:
diff --git a/weblate/api/tests.py b/weblate/api/tests.py
--- a/weblate/api/tests.py
+++ b/weblate/api/tests.py
@@ -1906,6 +1906,63 @@ def test_create(self):
             )
         self.assertEqual(Screenshot.objects.count(), 2)
 
+    def test_patch_screenshot(self):
+        self.do_request(
+            "api:screenshot-detail",
+            kwargs={"pk": Screenshot.objects.get().pk},
+            method="patch",
+            code=403,
+            request={"name": "Test New screenshot"},
+        )
+        self.do_request(
+            "api:screenshot-detail",
+            kwargs={"pk": Screenshot.objects.get().pk},
+            method="patch",
+            code=200,
+            superuser=True,
+            request={"name": "Test New screenshot"},
+        )
+        self.assertEqual(Screenshot.objects.get().name, "Test New screenshot")
+
+    def test_put_screenshot(self):
+        response = self.client.get(
+            reverse("api:screenshot-detail", kwargs={"pk": Screenshot.objects.get().pk})
+        )
+        request = response.data
+        request["name"] = "Test new screenshot"
+        self.do_request(
+            "api:screenshot-detail",
+            kwargs={"pk": Screenshot.objects.get().pk},
+            method="put",
+            code=403,
+            request=request,
+        )
+        self.do_request(
+            "api:screenshot-detail",
+            kwargs={"pk": Screenshot.objects.get().pk},
+            method="put",
+            code=200,
+            superuser=True,
+            request=request,
+        )
+        self.assertEqual(Screenshot.objects.get().name, "Test new screenshot")
+
+    def test_delete_screenshot(self):
+        self.do_request(
+            "api:screenshot-detail",
+            kwargs={"pk": Screenshot.objects.get().pk},
+            method="delete",
+            code=403,
+        )
+        self.do_request(
+            "api:screenshot-detail",
+            kwargs={"pk": Screenshot.objects.get().pk},
+            method="delete",
+            code=204,
+            superuser=True,
+        )
+        self.assertEqual(Screenshot.objects.count(), 0)
+
     def test_units_denied(self):
         unit = self.component.source_translation.unit_set.all()[0]
         response = self.client.post(
@@ -1932,6 +1989,29 @@ def test_units(self):
         self.assertEqual(response.status_code, 200)
         self.assertIn(str(unit.pk), response.data["units"][0])
 
+    def test_units_delete(self):
+        self.authenticate(True)
+        unit = self.component.source_translation.unit_set.all()[0]
+        self.client.post(
+            reverse("api:screenshot-units", kwargs={"pk": Screenshot.objects.get().pk}),
+            {"unit_id": unit.pk},
+        )
+        response = self.client.delete(
+            reverse(
+                "api:screenshot-delete-units",
+                kwargs={"pk": Screenshot.objects.get().pk, "unit_id": -1},
+            ),
+        )
+        self.assertEqual(response.status_code, 400)
+        response = self.client.delete(
+            reverse(
+                "api:screenshot-delete-units",
+                kwargs={"pk": Screenshot.objects.get().pk, "unit_id": unit.pk},
+            ),
+        )
+        self.assertEqual(response.status_code, 204)
+        self.assertEqual(len(Screenshot.objects.get().units.all()), 0)
+
 
 class ChangeAPITest(APIBaseTest):
     def test_list_changes(self):