Browse Source

Add entity api

pull/110/head
Hironsan 6 years ago
parent
commit
0e3f97154e
2 changed files with 141 additions and 68 deletions
  1. 23
      app/server/api.py
  2. 186
      app/server/tests/test_api.py

23
app/server/api.py

@ -3,15 +3,16 @@ from itertools import chain
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets, generics, filters
from rest_framework.decorators import action
from rest_framework import generics, filters
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Project, Label, Document
from .models import SequenceAnnotation
from .permissions import IsAdminUserAndWriteOnly, IsProjectUser, IsOwnAnnotation
from .serializers import ProjectSerializer, LabelSerializer, DocumentSerializer
from .serializers import SequenceAnnotationSerializer
class ProjectList(generics.ListCreateAPIView):
@ -105,11 +106,25 @@ class DocumentDetail(generics.RetrieveUpdateDestroyAPIView):
class EntityList(generics.ListCreateAPIView):
pass
queryset = SequenceAnnotation.objects.all()
serializer_class = SequenceAnnotationSerializer
pagination_class = None
permission_classes = (IsAuthenticated, IsProjectUser)
def get_queryset(self):
queryset = self.queryset.filter(document=self.kwargs['doc_id'])
return queryset
def perform_create(self, serializer):
doc = get_object_or_404(Document, pk=self.kwargs['doc_id'])
serializer.save(document=doc, user=self.request.user)
class EntityDetail(generics.RetrieveUpdateDestroyAPIView):
pass
queryset = SequenceAnnotation.objects.all()
serializer_class = SequenceAnnotationSerializer
lookup_url_kwarg = 'entity_id'
permission_classes = (IsAuthenticated, IsProjectUser)
class AnnotationList(generics.ListCreateAPIView):

186
app/server/tests/test_api.py

@ -2,7 +2,7 @@ from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from mixer.backend.django import mixer
from ..models import User, Project
from ..models import User, SequenceAnnotation
class TestProjectListAPI(APITestCase):
@ -365,69 +365,127 @@ class TestDocumentDetailAPI(APITestCase):
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class TestAnnotationAPI(APITestCase):
class TestEntityListAPI(APITestCase):
@classmethod
def setUpTestData(cls):
cls.username = 'user'
cls.password = 'pass'
cls.user1 = User.objects.create_user(username=cls.username, password=cls.password)
cls.user2 = User.objects.create_user(username='user2', password='pass2')
cls.project1 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.user1, cls.user2])
cls.project2 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.user2])
cls.doc1 = mixer.blend('server.Document', project=cls.project1)
cls.doc2 = mixer.blend('server.Document', project=cls.project1)
cls.label = mixer.blend('server.Label', project=cls.project1)
cls.annotation1 = mixer.blend('server.DocumentAnnotation', document=cls.doc1, user=cls.user1)
cls.annotation2 = mixer.blend('server.DocumentAnnotation', document=cls.doc1, user=cls.user2)
def setUp(self):
self.client.login(username=self.username, password=self.password)
def test_fetch_own_annotation(self):
"""
Ensure user can fetch only own annotation.
"""
url = reverse('annotations', args=[self.project1.id, self.doc1.id])
r = self.client.get(url, format='json')
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(len(r.data), 1)
self.assertEqual(r.data[0]['id'], self.annotation1.id)
def test_fetch_other_projects_annotation(self):
"""
Ensure user cannot fetch other project's annotation.
"""
url = reverse('annotations', args=[self.project2.id, self.doc1.id])
r = self.client.get(url, format='json')
self.assertEqual(r.status_code, status.HTTP_403_FORBIDDEN)
def test_annotate_doc(self):
"""
Ensure user can annotate a document.
"""
# Try to annotate a empty document(doc2).
data = {'label': self.label.id}
url = reverse('annotations', args=[self.project1.id, self.doc2.id])
r = self.client.post(url, data, format='json')
self.assertEqual(r.status_code, status.HTTP_201_CREATED)
self.assertEqual(len(self.doc2.doc_annotations.all()), 1)
def test_delete_annotation(self):
"""
Ensure user can delete only own annotation.
"""
self.assertEqual(len(self.doc1.doc_annotations.all()), 2)
# Try to delete own annotation.
url = reverse('ann', args=[self.project1.id, self.doc1.id, self.annotation1.id])
r = self.client.delete(url, format='json')
self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(len(self.doc1.doc_annotations.all()), 1)
# Try to delete other's annotation.
url = reverse('ann', args=[self.project1.id, self.doc1.id, self.annotation2.id])
r = self.client.delete(url, format='json')
self.assertEqual(r.status_code, status.HTTP_403_FORBIDDEN)
cls.project_member_name = 'project_member_name'
cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass'
project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name,
password=cls.non_project_member_pass)
label = mixer.blend('server.Label')
cls.main_project = mixer.blend('server.Project')
cls.main_project.users.add(project_member)
cls.main_project.labels.add(label)
main_project_doc = mixer.blend('server.Document', project=cls.main_project)
mixer.blend('server.SequenceAnnotation', document=main_project_doc)
sub_project = mixer.blend('server.Project')
sub_project_doc = mixer.blend('server.Document', project=sub_project)
mixer.blend('server.SequenceAnnotation', document=sub_project_doc)
cls.url = reverse(viewname='entity_list', args=[cls.main_project.id, main_project_doc.id])
cls.post_data = {'start_offset': 0, 'end_offset': 1, 'label': label.id}
cls.count = SequenceAnnotation.objects.filter(document=main_project_doc).count()
def test_returns_entities_to_project_member(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_do_not_return_entities_to_non_project_member(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_do_not_return_entities_of_other_projects(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(len(response.data), self.count)
def test_allows_project_member_to_create_entity(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.post(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_disallows_non_project_member_to_create_entity(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.post(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class TestEntityDetailAPI(APITestCase):
@classmethod
def setUpTestData(cls):
cls.project_member_name = 'project_member_name'
cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass'
project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name,
password=cls.non_project_member_pass)
label = mixer.blend('server.Label')
cls.main_project = mixer.blend('server.Project')
cls.main_project.users.add(project_member)
cls.main_project.labels.add(label)
main_project_doc = mixer.blend('server.Document', project=cls.main_project)
main_project_entity = mixer.blend('server.SequenceAnnotation', document=main_project_doc)
sub_project = mixer.blend('server.Project')
sub_project_doc = mixer.blend('server.Document', project=sub_project)
mixer.blend('server.SequenceAnnotation', document=sub_project_doc)
cls.url = reverse(viewname='entity_detail', args=[cls.main_project.id,
main_project_doc.id,
main_project_entity.id])
cls.post_data = {'start_offset': 0, 'end_offset': 10}
cls.count = SequenceAnnotation.objects.filter(document=main_project_doc).count()
def test_returns_entity_to_project_member(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_do_not_return_entity_to_non_project_member(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_allows_project_member_to_update_entity(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.patch(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_disallows_non_project_member_to_update_entity(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.put(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_allows_project_member_to_delete_entity(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.delete(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
def test_disallows_project_member_to_delete_entity(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.delete(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
Loading…
Cancel
Save