Repository: WeblateOrg/weblate

Issue:
Allow updating components via the REST API.
**Is your feature request related to a problem? Please describe.**

I would like to be able to update some component information using the REST API

**Describe the solution you'd like**

Enable a PUT/PATCH request to `api/components/<project-name>/<component-name>/`.

For example, with the below payload I can change the slug name.

```
update_payload = {"slug": "super-cool"}
```

**Describe alternatives you've considered**

I have checked the REST API docs and there does not seem to be a valid way to do this.



<bountysource-plugin>

---
Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/91421567-allow-updating-components-via-the-rest-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/serializers.py b/weblate/api/serializers.py
--- a/weblate/api/serializers.py
+++ b/weblate/api/serializers.py
@@ -337,7 +337,7 @@ class ComponentSerializer(RemovableSerializer):
 
     repo = RepoField(max_length=REPO_LENGTH)
 
-    push = RepoField(required=False, max_length=REPO_LENGTH)
+    push = RepoField(required=False, allow_blank=True, max_length=REPO_LENGTH)
 
     serializer_url_field = MultiFieldHyperlinkedIdentityField
 
diff --git a/weblate/api/views.py b/weblate/api/views.py
--- a/weblate/api/views.py
+++ b/weblate/api/views.py
@@ -31,7 +31,7 @@
 from rest_framework import parsers, status, viewsets
 from rest_framework.decorators import action
 from rest_framework.exceptions import ParseError
-from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
+from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, UpdateModelMixin
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
@@ -658,7 +658,9 @@ def destroy(self, request, *args, **kwargs):
         return Response(status=status.HTTP_204_NO_CONTENT)
 
 
-class ComponentViewSet(MultipleFieldMixin, WeblateViewSet, DestroyModelMixin):
+class ComponentViewSet(
+    MultipleFieldMixin, WeblateViewSet, UpdateModelMixin, DestroyModelMixin
+):
     """Translation components API."""
 
     queryset = Component.objects.none()
@@ -779,6 +781,12 @@ def screenshots(self, request, **kwargs):
 
         return self.get_paginated_response(serializer.data)
 
+    def update(self, request, *args, **kwargs):
+        instance = self.get_object()
+        if not request.user.has_perm("component.edit", instance):
+            self.permission_denied(request, message="Can not edit component")
+        return super().update(request, *args, **kwargs)
+
     def destroy(self, request, *args, **kwargs):
         instance = self.get_object()
         if not request.user.has_perm("component.edit", instance):


Test patch:
diff --git a/weblate/api/tests.py b/weblate/api/tests.py
--- a/weblate/api/tests.py
+++ b/weblate/api/tests.py
@@ -1060,6 +1060,40 @@ def test_screenshots(self):
         self.assertEqual(request.data["count"], 1)
         self.assertEqual(request.data["results"][0]["name"], "Obrazek")
 
+    def test_patch(self):
+        self.do_request(
+            "api:component-detail", self.component_kwargs, method="patch", code=403
+        )
+        response = self.do_request(
+            "api:component-detail",
+            self.component_kwargs,
+            method="patch",
+            superuser=True,
+            code=200,
+            format="json",
+            request={"name": "New Name"},
+        )
+        self.assertEqual(response.data["name"], "New Name")
+
+    def test_put(self):
+        self.do_request(
+            "api:component-detail", self.component_kwargs, method="put", code=403
+        )
+        component = self.client.get(
+            reverse("api:component-detail", kwargs=self.component_kwargs)
+        ).data
+        component["name"] = "New Name"
+        response = self.do_request(
+            "api:component-detail",
+            self.component_kwargs,
+            method="put",
+            superuser=True,
+            code=200,
+            format="json",
+            request=component,
+        )
+        self.assertEqual(response.data["name"], "New Name")
+
     def test_delete(self):
         self.do_request(
             "api:component-detail", self.component_kwargs, method="delete", code=403