From 2545ffef15b8a3e9d75dfda41718ce5f8c4d3c07 Mon Sep 17 00:00:00 2001 From: Hironsan Date: Mon, 7 Jun 2021 10:49:33 +0900 Subject: [PATCH] Add ExampleState API --- backend/api/tests/api/test_example_state.py | 67 +++++++++++++++++++++ backend/api/tests/api/utils.py | 4 ++ backend/api/urls.py | 5 ++ backend/api/views/__init__.py | 1 + backend/api/views/example_state.py | 31 ++++++++++ 5 files changed, 108 insertions(+) create mode 100644 backend/api/tests/api/test_example_state.py create mode 100644 backend/api/views/example_state.py diff --git a/backend/api/tests/api/test_example_state.py b/backend/api/tests/api/test_example_state.py new file mode 100644 index 00000000..9d799e1c --- /dev/null +++ b/backend/api/tests/api/test_example_state.py @@ -0,0 +1,67 @@ +from rest_framework import status +from rest_framework.reverse import reverse + +from .utils import (CRUDMixin, make_doc, make_example_state, make_user, + prepare_project) + + +class TestExampleStateList(CRUDMixin): + + @classmethod + def setUpTestData(cls): + cls.non_member = make_user() + cls.project = prepare_project() + cls.example = make_doc(cls.project.item) + for user in cls.project.users: + make_example_state(cls.example, user) + cls.url = reverse(viewname='example_state_list', args=[cls.project.item.id, cls.example.id]) + + def test_returns_example_state_to_project_member(self): + for member in self.project.users: + response = self.assert_fetch(member, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + + def test_does_not_return_example_state_to_non_project_member(self): + self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) + + def test_does_not_return_example_state_to_unauthenticated_user(self): + self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) + + +class TestExampleStateConfirm(CRUDMixin): + + def setUp(self): + self.project = prepare_project() + self.example = make_doc(self.project.item) + self.url = reverse(viewname='example_state_list', args=[self.project.item.id, self.example.id]) + + def test_allows_member_to_confirm_example(self): + for member in self.project.users: + response = self.assert_fetch(member, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 0) + self.assert_create(member, status.HTTP_201_CREATED) # confirm + response = self.assert_fetch(member, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + self.assert_create(member, status.HTTP_201_CREATED) # toggle confirm + response = self.assert_fetch(member, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 0) + + +class TestExampleStateConfirmCollaborative(CRUDMixin): + + def setUp(self): + self.project = prepare_project(collaborative_annotation=True) + self.example = make_doc(self.project.item) + self.url = reverse(viewname='example_state_list', args=[self.project.item.id, self.example.id]) + + def test_can_share_example_state(self): + member1 = self.project.users[0] + member2 = self.project.users[1] + response = self.assert_fetch(member1, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 0) + self.assert_create(member1, status.HTTP_201_CREATED) # confirm + response = self.assert_fetch(member1, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + self.assert_create(member2, status.HTTP_201_CREATED) # toggle confirm + response = self.assert_fetch(member1, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 0) diff --git a/backend/api/tests/api/utils.py b/backend/api/tests/api/utils.py index 2229b3f7..147b5bd3 100644 --- a/backend/api/tests/api/utils.py +++ b/backend/api/tests/api/utils.py @@ -100,6 +100,10 @@ def make_comment(doc, user): return mommy.make('Comment', example=doc, user=user) +def make_example_state(example, user): + return mommy.make('ExampleState', example=example, confirmed_by=user) + + def make_annotation(task, doc, user): annotation_model = { DOCUMENT_CLASSIFICATION: 'Category', diff --git a/backend/api/urls.py b/backend/api/urls.py index f2108f67..f3ddd21c 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -134,6 +134,11 @@ urlpatterns_project = [ view=views.CommentDetail.as_view(), name='comment_detail' ), + path( + route='examples//states', + view=views.ExampleStateList.as_view(), + name='example_state_list' + ), path( route='roles', view=views.RoleMappingList.as_view(), diff --git a/backend/api/views/__init__.py b/backend/api/views/__init__.py index 7b3c32fc..260cc724 100644 --- a/backend/api/views/__init__.py +++ b/backend/api/views/__init__.py @@ -3,6 +3,7 @@ from .annotation_relations import * from .auto_labeling import * from .comment import * from .example import * +from .example_state import * from .export_dataset import * from .health import * from .import_dataset import * diff --git a/backend/api/views/example_state.py b/backend/api/views/example_state.py new file mode 100644 index 00000000..38a82217 --- /dev/null +++ b/backend/api/views/example_state.py @@ -0,0 +1,31 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import generics +from rest_framework.permissions import IsAuthenticated + +from ..models import Example, ExampleState, Project +from ..permissions import IsInProjectOrAdmin +from ..serializers import ExampleStateSerializer + + +class ExampleStateList(generics.ListCreateAPIView): + serializer_class = ExampleStateSerializer + permission_classes = [IsAuthenticated & IsInProjectOrAdmin] + + @property + def can_confirm_per_user(self): + project = get_object_or_404(Project, pk=self.kwargs['project_id']) + return not project.collaborative_annotation + + def get_queryset(self): + queryset = ExampleState.objects.filter(example=self.kwargs['example_id']) + if self.can_confirm_per_user: + queryset = queryset.filter(confirmed_by=self.request.user) + return queryset + + def perform_create(self, serializer): + queryset = self.get_queryset() + if queryset.exists(): + queryset.delete() + else: + example = get_object_or_404(Example, pk=self.kwargs['example_id']) + serializer.save(example=example, confirmed_by=self.request.user)