Browse Source

Add Category API

pull/1592/head
Hironsan 3 years ago
parent
commit
e354378545
5 changed files with 139 additions and 16 deletions
  1. 69
      backend/api/tests/api/test_annotation.py
  2. 11
      backend/api/urls.py
  3. 0
      backend/api/views/tasks/__init__.py
  4. 57
      backend/api/views/tasks/base.py
  5. 18
      backend/api/views/tasks/category.py

69
backend/api/tests/api/test_annotation.py

@ -7,15 +7,17 @@ from .utils import (CRUDMixin, make_annotation, make_doc, make_label,
class TestAnnotationList(CRUDMixin):
task = DOCUMENT_CLASSIFICATION
view_name = 'annotation_list'
@classmethod
def setUpTestData(cls):
cls.project = prepare_project(task=DOCUMENT_CLASSIFICATION)
cls.project = prepare_project(task=cls.task)
cls.non_member = make_user()
doc = make_doc(cls.project.item)
for member in cls.project.users:
make_annotation(task=DOCUMENT_CLASSIFICATION, doc=doc, user=member)
cls.url = reverse(viewname='annotation_list', args=[cls.project.item.id, doc.id])
make_annotation(task=cls.task, doc=doc, user=member)
cls.url = reverse(viewname=cls.view_name, args=[cls.project.item.id, doc.id])
def test_allows_project_member_to_fetch_annotation(self):
for member in self.project.users:
@ -34,15 +36,21 @@ class TestAnnotationList(CRUDMixin):
self.assertEqual(count, 2) # delete only own annotation
class TestCategoryList(TestAnnotationList):
view_name = 'category_list'
class TestSharedAnnotationList(CRUDMixin):
task = DOCUMENT_CLASSIFICATION
view_name = 'annotation_list'
@classmethod
def setUpTestData(cls):
cls.project = prepare_project(task=DOCUMENT_CLASSIFICATION, collaborative_annotation=True)
cls.project = prepare_project(task=cls.task, collaborative_annotation=True)
doc = make_doc(cls.project.item)
for member in cls.project.users:
make_annotation(task=DOCUMENT_CLASSIFICATION, doc=doc, user=member)
cls.url = reverse(viewname='annotation_list', args=[cls.project.item.id, doc.id])
make_annotation(task=cls.task, doc=doc, user=member)
cls.url = reverse(viewname=cls.view_name, args=[cls.project.item.id, doc.id])
def test_allows_project_member_to_fetch_all_annotation(self):
for member in self.project.users:
@ -55,15 +63,21 @@ class TestSharedAnnotationList(CRUDMixin):
self.assertEqual(count, 0) # delete all annotation in the doc
class TestSharedCategoryList(TestSharedAnnotationList):
view_name = 'category_list'
class TestAnnotationCreation(CRUDMixin):
task = DOCUMENT_CLASSIFICATION
view_name = 'annotation_list'
def setUp(self):
self.project = prepare_project(task=DOCUMENT_CLASSIFICATION)
self.project = prepare_project(task=self.task)
self.non_member = make_user()
doc = make_doc(self.project.item)
label = make_label(self.project.item)
self.data = {'label': label.id}
self.url = reverse(viewname='annotation_list', args=[self.project.item.id, doc.id])
self.url = reverse(viewname=self.view_name, args=[self.project.item.id, doc.id])
def test_allows_project_member_to_annotate(self):
for member in self.project.users:
@ -76,22 +90,31 @@ class TestAnnotationCreation(CRUDMixin):
self.assert_create(expected=status.HTTP_403_FORBIDDEN)
class TestCategoryCreation(TestAnnotationCreation):
view_name = 'category_list'
class TestAnnotationDetail(CRUDMixin):
task = SEQUENCE_LABELING
view_name = 'annotation_detail'
def setUp(self):
self.project = prepare_project(task=SEQUENCE_LABELING)
self.project = prepare_project(task=self.task)
self.non_member = make_user()
doc = make_doc(self.project.item)
label = make_label(self.project.item)
annotation = make_annotation(
task=SEQUENCE_LABELING,
annotation = self.create_annotation_data(doc=doc)
self.data = {'label': label.id}
self.url = reverse(viewname=self.view_name, args=[self.project.item.id, doc.id, annotation.id])
def create_annotation_data(self, doc):
return make_annotation(
task=self.task,
doc=doc,
user=self.project.users[0],
start_offset=0,
end_offset=1
)
self.data = {'label': label.id}
self.url = reverse(viewname='annotation_detail', args=[self.project.item.id, doc.id, annotation.id])
def test_allows_owner_to_get_annotation(self):
self.assert_fetch(self.project.users[0], status.HTTP_200_OK)
@ -127,15 +150,25 @@ class TestAnnotationDetail(CRUDMixin):
self.assert_delete(self.non_member, status.HTTP_403_FORBIDDEN)
class TestCategoryDetail(TestAnnotationDetail):
task = DOCUMENT_CLASSIFICATION
view_name = 'category_detail'
def create_annotation_data(self, doc):
return make_annotation(task=self.task, doc=doc, user=self.project.users[0])
class TestSharedAnnotationDetail(CRUDMixin):
task = DOCUMENT_CLASSIFICATION
view_name = 'annotation_detail'
def setUp(self):
self.project = prepare_project(task=DOCUMENT_CLASSIFICATION, collaborative_annotation=True)
self.project = prepare_project(task=self.task, collaborative_annotation=True)
doc = make_doc(self.project.item)
annotation = make_annotation(task=DOCUMENT_CLASSIFICATION, doc=doc, user=self.project.users[0])
annotation = make_annotation(task=self.task, doc=doc, user=self.project.users[0])
label = make_label(self.project.item)
self.data = {'label': label.id}
self.url = reverse(viewname='annotation_detail', args=[self.project.item.id, doc.id, annotation.id])
self.url = reverse(viewname=self.view_name, args=[self.project.item.id, doc.id, annotation.id])
def test_allows_any_member_to_get_annotation(self):
for member in self.project.users:
@ -147,3 +180,7 @@ class TestSharedAnnotationDetail(CRUDMixin):
def test_allows_any_member_to_delete_annotation(self):
self.assert_delete(self.project.users[1], status.HTTP_204_NO_CONTENT)
class TestSharedCategoryDetail(TestSharedAnnotationDetail):
view_name = 'category_detail'

11
backend/api/urls.py

@ -4,6 +4,7 @@ from .views import (annotation, annotation_relations, auto_labeling, comment,
example, example_state, export_dataset, health,
import_dataset, import_export, label, project,
relation_types, role, statistics, tag, task, user)
from .views.tasks import category
urlpatterns_project = [
path(
@ -112,6 +113,16 @@ urlpatterns_project = [
view=annotation.AnnotationDetail.as_view(),
name='annotation_detail'
),
path(
route='examples/<int:example_id>/categories',
view=category.CategoryListAPI.as_view(),
name='category_list'
),
path(
route='examples/<int:example_id>/categories/<int:annotation_id>',
view=category.CategoryDetailAPI.as_view(),
name='category_detail'
),
path(
route='tags',
view=tag.TagList.as_view(),

0
backend/api/views/tasks/__init__.py

57
backend/api/views/tasks/base.py

@ -0,0 +1,57 @@
from django.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from ...models import Project
from ...permissions import IsInProjectOrAdmin, IsOwnAnnotation
class BaseListAPI(generics.ListCreateAPIView):
annotation_class = None
pagination_class = None
permission_classes = [IsAuthenticated & IsInProjectOrAdmin]
swagger_schema = None
@property
def project(self):
return get_object_or_404(Project, pk=self.kwargs['project_id'])
def get_queryset(self):
queryset = self.annotation_class.objects.filter(example=self.kwargs['example_id'])
if not self.project.collaborative_annotation:
queryset = queryset.filter(user=self.request.user)
return queryset
def create(self, request, *args, **kwargs):
request.data['example'] = self.kwargs['example_id']
try:
response = super().create(request, args, kwargs)
except ValidationError as err:
response = Response({'detail': err.messages}, status=status.HTTP_400_BAD_REQUEST)
return response
def perform_create(self, serializer):
serializer.save(example_id=self.kwargs['example_id'], user=self.request.user)
def delete(self, request, *args, **kwargs):
queryset = self.get_queryset()
queryset.all().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class BaseDetailAPI(generics.RetrieveUpdateDestroyAPIView):
lookup_url_kwarg = 'annotation_id'
swagger_schema = None
@property
def project(self):
return get_object_or_404(Project, pk=self.kwargs['project_id'])
def get_permissions(self):
if self.project.collaborative_annotation:
self.permission_classes = [IsAuthenticated & IsInProjectOrAdmin]
else:
self.permission_classes = [IsAuthenticated & IsInProjectOrAdmin & IsOwnAnnotation]
return super().get_permissions()

18
backend/api/views/tasks/category.py

@ -0,0 +1,18 @@
from ...models import Category
from ...serializers import CategorySerializer
from .base import BaseDetailAPI, BaseListAPI
class CategoryListAPI(BaseListAPI):
annotation_class = Category
serializer_class = CategorySerializer
def create(self, request, *args, **kwargs):
if self.project.single_class_classification:
self.get_queryset().delete()
return super().create(request, args, kwargs)
class CategoryDetailAPI(BaseDetailAPI):
queryset = Category.objects.all()
serializer_class = CategorySerializer
Loading…
Cancel
Save