From 8da650c3a433d20f15d46dd27cedf72c05df7061 Mon Sep 17 00:00:00 2001
From: Bram Wiepjes <bramw@protonmail.com>
Date: Sun, 22 Sep 2019 15:13:22 +0200
Subject: [PATCH] made it possible to update and delete existing applications

---
 .../src/baserow/api/v0/applications/errors.py |  1 +
 .../api/v0/applications/serializers.py        |  7 ++
 .../src/baserow/api/v0/applications/urls.py   |  5 +-
 .../src/baserow/api/v0/applications/views.py  | 43 ++++++++++-
 .../v0/applications/test_application_views.py | 71 +++++++++++++++++++
 5 files changed, 123 insertions(+), 4 deletions(-)
 create mode 100644 backend/src/baserow/api/v0/applications/errors.py

diff --git a/backend/src/baserow/api/v0/applications/errors.py b/backend/src/baserow/api/v0/applications/errors.py
new file mode 100644
index 000000000..a6d2b0e6d
--- /dev/null
+++ b/backend/src/baserow/api/v0/applications/errors.py
@@ -0,0 +1 @@
+ERROR_USER_NOT_IN_GROUP = 'ERROR_USER_NOT_IN_GROUP'
diff --git a/backend/src/baserow/api/v0/applications/serializers.py b/backend/src/baserow/api/v0/applications/serializers.py
index 0159757c2..8d87124a0 100644
--- a/backend/src/baserow/api/v0/applications/serializers.py
+++ b/backend/src/baserow/api/v0/applications/serializers.py
@@ -27,3 +27,10 @@ class ApplicationCreateSerializer(serializers.ModelSerializer):
     class Meta:
         model = Application
         fields = ('name', 'type')
+
+
+
+class ApplicationUpdateSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Application
+        fields = ('name',)
diff --git a/backend/src/baserow/api/v0/applications/urls.py b/backend/src/baserow/api/v0/applications/urls.py
index aaaec1f02..bdb9f24b1 100644
--- a/backend/src/baserow/api/v0/applications/urls.py
+++ b/backend/src/baserow/api/v0/applications/urls.py
@@ -1,10 +1,11 @@
 from django.conf.urls import url
 
-from .views import ApplicationsView
+from .views import ApplicationsView, ApplicationView
 
 
 app_name = 'baserow.api.v0.group'
 
 urlpatterns = [
-    url(r'(?P<group_id>[0-9]+)/$', ApplicationsView.as_view(), name='list')
+    url(r'group/(?P<group_id>[0-9]+)/$', ApplicationsView.as_view(), name='list'),
+    url(r'(?P<application_id>[0-9]+)/$', ApplicationView.as_view(), name='item'),
 ]
diff --git a/backend/src/baserow/api/v0/applications/views.py b/backend/src/baserow/api/v0/applications/views.py
index 36cef62c9..e35a0282e 100644
--- a/backend/src/baserow/api/v0/applications/views.py
+++ b/backend/src/baserow/api/v0/applications/views.py
@@ -5,11 +5,15 @@ from rest_framework.views import APIView
 from rest_framework.response import Response
 from rest_framework.permissions import IsAuthenticated
 
-from baserow.api.v0.decorators import validate_body
+from baserow.api.v0.decorators import validate_body, map_exceptions
 from baserow.core.models import GroupUser, Application
 from baserow.core.handler import CoreHandler
+from baserow.core.exceptions import UserNotIngroupError
 
-from .serializers import ApplicationSerializer, ApplicationCreateSerializer
+from .serializers import (
+    ApplicationSerializer, ApplicationCreateSerializer, ApplicationUpdateSerializer
+)
+from .errors import ERROR_USER_NOT_IN_GROUP
 
 
 class ApplicationsView(APIView):
@@ -45,3 +49,38 @@ class ApplicationsView(APIView):
             request.user, group_user.group, data['type'], name=data['name'])
 
         return Response(ApplicationSerializer(application).data)
+
+
+class ApplicationView(APIView):
+    permission_classes = (IsAuthenticated,)
+    core_handler = CoreHandler()
+
+    @transaction.atomic
+    @validate_body(ApplicationUpdateSerializer)
+    @map_exceptions({
+        UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
+    })
+    def patch(self, request, data, application_id):
+        """Updates the application if it belongs to a user."""
+        application = get_object_or_404(
+            Application.objects.select_related('group').select_for_update(),
+            pk=application_id
+        )
+        application = self.core_handler.update_application(
+            request.user, application, name=data['name'])
+
+        return Response(ApplicationSerializer(application).data)
+
+    @transaction.atomic
+    @map_exceptions({
+        UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
+    })
+    def delete(self, request, application_id):
+        """Deletes an existing application if the user belongs to the group."""
+        application = get_object_or_404(
+            Application.objects.select_related('group'),
+            pk=application_id
+        )
+        self.core_handler.delete_application(request.user, application)
+
+        return Response(status=204)
diff --git a/backend/tests/baserow/api/v0/applications/test_application_views.py b/backend/tests/baserow/api/v0/applications/test_application_views.py
index 1819f601e..7f0a8272d 100644
--- a/backend/tests/baserow/api/v0/applications/test_application_views.py
+++ b/backend/tests/baserow/api/v0/applications/test_application_views.py
@@ -92,3 +92,74 @@ def test_create_application(api_client, data_fixture):
     assert response_json['id'] == database.id
     assert response_json['name'] == database.name
     assert response_json['order'] == database.order
+
+
+@pytest.mark.django_db
+def test_update_application(api_client, data_fixture):
+    user, token = data_fixture.create_user_and_token()
+    user_2, token_2 = data_fixture.create_user_and_token()
+    group = data_fixture.create_group(user=user)
+    group_2 = data_fixture.create_group(user=user_2)
+    application = data_fixture.create_database_application(group=group)
+    application_2 = data_fixture.create_database_application(group=group_2)
+
+    url = reverse('api_v0:applications:item',
+                  kwargs={'application_id': application_2.id})
+    response = api_client.patch(
+        url,
+        {'name': 'Test 1'},
+        format='json',
+        HTTP_AUTHORIZATION=f'JWT {token}'
+    )
+    response_json = response.json()
+    assert response.status_code == 400
+    assert response_json['error'] == 'ERROR_USER_NOT_IN_GROUP'
+
+    url = reverse('api_v0:applications:item', kwargs={'application_id': application.id})
+    response = api_client.patch(
+        url,
+        {'UNKNOWN_FIELD': 'Test 1'},
+        format='json',
+        HTTP_AUTHORIZATION=f'JWT {token}'
+    )
+    response_json = response.json()
+    assert response.status_code == 400
+    assert response_json['error'] == 'ERROR_REQUEST_BODY_VALIDATION'
+
+    url = reverse('api_v0:applications:item', kwargs={'application_id': application.id})
+    response = api_client.patch(
+        url,
+        {'name': 'Test 1'},
+        format='json',
+        HTTP_AUTHORIZATION=f'JWT {token}'
+    )
+    response_json = response.json()
+    assert response.status_code == 200
+    assert response_json['id'] == application.id
+    assert response_json['name'] == 'Test 1'
+
+    application.refresh_from_db()
+    assert application.name == 'Test 1'
+
+
+@pytest.mark.django_db
+def test_delete_application(api_client, data_fixture):
+    user, token = data_fixture.create_user_and_token()
+    user_2, token_2 = data_fixture.create_user_and_token()
+    group = data_fixture.create_group(user=user)
+    group_2 = data_fixture.create_group(user=user_2)
+    application = data_fixture.create_database_application(group=group)
+    application_2 = data_fixture.create_database_application(group=group_2)
+
+    url = reverse('api_v0:applications:item',
+                  kwargs={'application_id': application_2.id})
+    response = api_client.delete(url, HTTP_AUTHORIZATION=f'JWT {token}')
+    response_json = response.json()
+    assert response.status_code == 400
+    assert response_json['error'] == 'ERROR_USER_NOT_IN_GROUP'
+
+    url = reverse('api_v0:applications:item', kwargs={'application_id': application.id})
+    response = api_client.delete(url, HTTP_AUTHORIZATION=f'JWT {token}')
+    assert response.status_code == 204
+
+    assert Database.objects.all().count() == 1