From 6ac434cc207c84823bd1f59b4f9b4048bd8c120a Mon Sep 17 00:00:00 2001 From: Simurgan Date: Fri, 22 Dec 2023 15:24:15 +0300 Subject: [PATCH 1/3] implement semantic tag post api --- project/backend/api/urls.py | 2 ++ project/backend/api/views.py | 11 +++++++++++ project/backend/database/models.py | 2 +- project/backend/database/serializers.py | 20 ++++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/project/backend/api/urls.py b/project/backend/api/urls.py index 65a35046..115537ff 100644 --- a/project/backend/api/urls.py +++ b/project/backend/api/urls.py @@ -46,4 +46,6 @@ path('promote_contributor/', promote_contributor, name='promote_contributor'), path('demote_reviewer/', demote_reviewer, name='demote_reviewer'), path('add_user_semantic_tag/', AddUserSemanticTag.as_view(), name='add_user_semantic_tag'), + path('add_semantic_tag/', SemanticTagAPIView.as_view(), name='add_semantic_tag'), + ] diff --git a/project/backend/api/views.py b/project/backend/api/views.py index 5a39db29..1897e3c7 100644 --- a/project/backend/api/views.py +++ b/project/backend/api/views.py @@ -132,7 +132,18 @@ def post(self, request): serializer.errors, status=400 ) +class SemanticTagAPIView(APIView): + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAuthenticated, IsContributorAndWorkspace) + def post(self, request): + serializer = SemanticTagSerializer(data=request.data, context={'request': request}) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response( + serializer.errors, status=400 + ) def search(request): diff --git a/project/backend/database/models.py b/project/backend/database/models.py index 7a92268a..98c6da61 100644 --- a/project/backend/database/models.py +++ b/project/backend/database/models.py @@ -10,7 +10,7 @@ class SemanticTag(models.Model): created_at = models.DateTimeField(auto_now_add=True) wid = models.CharField(max_length=20) - label = models.CharField(max_length=30, unique=True) + label = models.CharField(max_length=30) @property def nodes(self): diff --git a/project/backend/database/serializers.py b/project/backend/database/serializers.py index 9ab2ba95..0b243857 100644 --- a/project/backend/database/serializers.py +++ b/project/backend/database/serializers.py @@ -68,6 +68,26 @@ def create(self, validated_data): return workspace +class SemanticTagSerializer(serializers.ModelSerializer): + wid = serializers.CharField(required=True) + label = serializers.CharField(required=True) + + class Meta: + model = SemanticTag + fields = "__all__" + + def create(self, validated_data): + wid = validated_data.get('wid', None) + label = validated_data.get('label', None) + workspace_id = self.context['request'].POST.get('workspace_id', None) + + tag = SemanticTag.objects.create(wid=wid, label=label) + + if workspace_id is not None: + workspace = Workspace.objects.get(workspace_id=workspace_id) + workspace.semantic_tags.add(tag) + + return tag # Serializer to change password class ChangePasswordSerializer(serializers.ModelSerializer): From 720dc2e3f20f397cade036fbbdb4c58c1991adcc Mon Sep 17 00:00:00 2001 From: Simurgan Date: Fri, 22 Dec 2023 18:12:10 +0300 Subject: [PATCH 2/3] implement remove workspace tag put api --- project/backend/api/urls.py | 1 + project/backend/api/views.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/project/backend/api/urls.py b/project/backend/api/urls.py index 115537ff..4d06203c 100644 --- a/project/backend/api/urls.py +++ b/project/backend/api/urls.py @@ -47,5 +47,6 @@ path('demote_reviewer/', demote_reviewer, name='demote_reviewer'), path('add_user_semantic_tag/', AddUserSemanticTag.as_view(), name='add_user_semantic_tag'), path('add_semantic_tag/', SemanticTagAPIView.as_view(), name='add_semantic_tag'), + path('remove_workspace_tag/', remove_workspace_tag, name='remove_workspace_tag'), ] diff --git a/project/backend/api/views.py b/project/backend/api/views.py index 1897e3c7..1f1b67e3 100644 --- a/project/backend/api/views.py +++ b/project/backend/api/views.py @@ -145,6 +145,42 @@ def post(self, request): serializer.errors, status=400 ) +def is_workspace_contributor(request): + workspace_id = request.data.get('workspace_id') + if not request.user.is_authenticated: + return False + if not Contributor.objects.filter(pk=request.user.basicuser.pk).exists(): + return False + if workspace_id is not None: + return request.user.basicuser.contributor.workspaces.filter(workspace_id=workspace_id).exists() + return True + +@api_view(['PUT']) +def remove_workspace_tag(request): + workspace_id = request.data.get("workspace_id") + tag_id = request.data.get("tag_id") + + if workspace_id == None or workspace_id == '': + return JsonResponse({'message': 'workspace_id field can not be empty'}, status=400) + try: + workspace_id = int(workspace_id) + except: + return JsonResponse({'message': 'workspace_id field has to be an integer'}, status=400) + + if tag_id == None or tag_id == '': + return JsonResponse({'message': 'tag_id field can not be empty'}, status=400) + try: + tag_id = int(tag_id) + except: + return JsonResponse({'message': 'tag_id field has to be an integer'}, status=400) + + if is_workspace_contributor(request): + workspace = Workspace.objects.get(workspace_id=workspace_id) + workspace.semantic_tags.remove(tag_id) + return JsonResponse({'message': 'Tag is successfully removed from workspace.'}, status=200) + else: + return JsonResponse({'message': "You don't have permission to do this!"}, status=403) + def search(request): search = request.GET.get("query") From 48e5953a51e22b29d9fd902970346b098c98c200 Mon Sep 17 00:00:00 2001 From: Simurgan Date: Sat, 23 Dec 2023 15:18:43 +0300 Subject: [PATCH 3/3] implement tests --- project/backend/api/tests.py | 165 +++++++++++++++++++++++- project/backend/database/serializers.py | 2 +- 2 files changed, 165 insertions(+), 2 deletions(-) diff --git a/project/backend/api/tests.py b/project/backend/api/tests.py index b566bd81..a5fa7272 100644 --- a/project/backend/api/tests.py +++ b/project/backend/api/tests.py @@ -10,6 +10,169 @@ # Create your tests here for each class or API call. +class SemanticTagAPITestCase(TestCase): + def setUp(self): + self.client = APIClient() + self.url_add = reverse("add_semantic_tag") + self.url_remove = reverse("remove_workspace_tag") + + self.wid = "Q486598" + self.label1 = "Test Semantic Tag Label 1" + self.label2 = "Test Semantic Tag Label 2" + self.label3 = "Test Semantic Tag Label 3" + self.user_for_contributor = User.objects.create_user(id=2, email= 'cont1@example.com', username='cont1@example.com', first_name='Contributor User 2', last_name='Test2') + self.user_for_contributor2 = User.objects.create_user(id=3, email= 'cont2@example.com', username='cont2@example.com', first_name='Contributor User 3', last_name='Test3') + self.contributor = Contributor.objects.create(user=self.user_for_contributor, bio="I am the contributor") + self.contributor2 = Contributor.objects.create(user=self.user_for_contributor2, bio="I am the second contributor") + + self.contributor_token = Token.objects.create(user=self.user_for_contributor) + self.contributor2_token = Token.objects.create(user=self.user_for_contributor2) + + self.workspace = Workspace.objects.create() + self.contributor.workspaces.add(self.workspace) + + def tearDown(self): + Workspace.objects.all().delete() + Contributor.objects.all().delete() + BasicUser.objects.all().delete() + User.objects.all().delete() + SemanticTag.objects.all().delete() + print("All tests for the Semantic Tag API are completed!") + + def test_add_semantic_tag(self): + data_complete = { + "wid": self.wid, + "label": self.label2, + "workspace_id": self.workspace.workspace_id + } + response = self.client.post(self.url_add, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response.data["detail"], "Authentication credentials were not provided.") + + self.client.credentials(HTTP_AUTHORIZATION=f"Token {Token.objects.get(user=self.user_for_contributor).key[0:-1]}") + response = self.client.post(self.url_add, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response.data["detail"], "Invalid token.") + + self.client.credentials(HTTP_AUTHORIZATION=f"Token {Token.objects.get(user=self.user_for_contributor2).key}") + response = self.client.post(self.url_add, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.data["detail"], "You do not have permission to perform this action.") + + self.client.credentials(HTTP_AUTHORIZATION=f"Token {Token.objects.get(user=self.user_for_contributor).key}") + + data_missing_wid = { + "label": self.label1, + "workspace_id": self.workspace.workspace_id + } + + response = self.client.post(self.url_add, data_missing_wid, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["wid"][0], "This field is required.") + + data_missing_label = { + "wid": self.wid, + "workspace_id": self.workspace.workspace_id + } + + response = self.client.post(self.url_add, data_missing_label, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["label"][0], "This field is required.") + + data_missing_workspace_id = { + "wid": self.wid, + "label": self.label1 + } + + response = self.client.post(self.url_add, data_missing_workspace_id, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + tags = SemanticTag.objects.filter(wid=self.wid, label=self.label1) + self.assertEqual(tags.count(), 1) + self.assertEqual(tags[0].wid, self.wid) + self.assertEqual(tags[0].wid, response.data["wid"]) + self.assertEqual(tags[0].label, self.label1) + self.assertEqual(tags[0].label, response.data["label"]) + + data_complete = { + "wid": self.wid, + "label": self.label2, + "workspace_id": str(self.workspace.workspace_id) + } + + SemanticTag.objects.all().delete() + + response = self.client.post(self.url_add, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + tags = SemanticTag.objects.filter(wid=self.wid, label=self.label2) + self.assertEqual(tags.count(), 1) + self.assertEqual(tags[0].wid, self.wid) + self.assertEqual(tags[0].wid, response.data["wid"]) + self.assertEqual(tags[0].label, self.label2) + self.assertEqual(tags[0].label, response.data["label"]) + + tags = self.workspace.semantic_tags.all() + self.assertEqual(tags.count(), 1) + tag = tags[0] + self.assertEqual(tag.wid, self.wid) + self.assertEqual(tag.wid, response.data["wid"]) + self.assertEqual(tag.label, self.label2) + self.assertEqual(tag.label, response.data["label"]) + + def test_remove_workspace_tag(self): + tag = SemanticTag.objects.create(wid=self.wid, label=self.label3) + self.workspace.semantic_tags.add(tag) + + data_complete = { + "workspace_id": self.workspace.workspace_id, + "tag_id": tag.id + } + response = self.client.put(self.url_remove, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + content = json.loads(response.content) + self.assertEqual(content["message"], "You don't have permission to do this!") + + self.client.credentials(HTTP_AUTHORIZATION=f"Token {Token.objects.get(user=self.user_for_contributor).key[0:-1]}") + response = self.client.put(self.url_remove, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + content = json.loads(response.content) + self.assertEqual(content["detail"], "Invalid token.") + + self.client.credentials(HTTP_AUTHORIZATION=f"Token {Token.objects.get(user=self.user_for_contributor2).key}") + response = self.client.put(self.url_remove, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + content = json.loads(response.content) + self.assertEqual(content["message"], "You don't have permission to do this!") + + self.client.credentials(HTTP_AUTHORIZATION=f"Token {Token.objects.get(user=self.user_for_contributor).key}") + data_missing_workspace_id = { + "tag_id": tag.id + } + response = self.client.put(self.url_remove, data_missing_workspace_id, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + content = json.loads(response.content) + self.assertEqual(content["message"], "workspace_id field can not be empty") + + data_missing_tag_id = { + "workspace_id": self.workspace.workspace_id + } + response = self.client.put(self.url_remove, data_missing_tag_id, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + content = json.loads(response.content) + self.assertEqual(content["message"], "tag_id field can not be empty") + + data_complete = { + "workspace_id": self.workspace.workspace_id, + "tag_id": tag.id + } + response = self.client.put(self.url_remove, data_complete, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + content = json.loads(response.content) + self.assertEqual(content["message"], "Tag is successfully removed from workspace.") + + tags = self.workspace.semantic_tags.filter(wid=self.wid, label=self.label3) + self.assertEqual(tags.count(), 0) + + class WorkspacePOSTAPITestCase(TestCase): def setUp(self): self.client = APIClient() @@ -1013,4 +1176,4 @@ def test_add_user_semantic_tag(self): 'sm_tag_id': self.sm_tag.pk, } response = self.client.post(url, payload, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) \ No newline at end of file + self.assertEqual(response.status_code, status.HTTP_201_CREATED) diff --git a/project/backend/database/serializers.py b/project/backend/database/serializers.py index 0b243857..57564df8 100644 --- a/project/backend/database/serializers.py +++ b/project/backend/database/serializers.py @@ -79,7 +79,7 @@ class Meta: def create(self, validated_data): wid = validated_data.get('wid', None) label = validated_data.get('label', None) - workspace_id = self.context['request'].POST.get('workspace_id', None) + workspace_id = self.context['request'].data.get('workspace_id', None) tag = SemanticTag.objects.create(wid=wid, label=label)