Browse Source

Move label types code to label types app

pull/1655/head
Hironsan 2 years ago
parent
commit
6fe1f17438
13 changed files with 288 additions and 275 deletions
  1. 21
      backend/api/admin.py
  2. 94
      backend/api/serializers.py
  3. 48
      backend/api/urls.py
  4. 116
      backend/api/views/label.py
  5. 1
      backend/app/settings.py
  6. 1
      backend/app/urls.py
  7. 21
      backend/label_types/admin.py
  8. 0
      backend/label_types/exceptions.py
  9. 88
      backend/label_types/serializers.py
  10. 0
      backend/label_types/tests.py
  11. 4
      backend/label_types/tests/test_views.py
  12. 54
      backend/label_types/urls.py
  13. 115
      backend/label_types/views.py

21
backend/api/admin.py

@ -1,22 +1,7 @@
from django.contrib import admin
from .models import (CategoryType, Comment, Example, Project, Seq2seqProject,
SequenceLabelingProject, SpanType, Tag,
TextClassificationProject)
class LabelAdmin(admin.ModelAdmin):
list_display = ('text', 'project', 'text_color', 'background_color')
ordering = ('project',)
search_fields = ('text',)
class CategoryTypeAdmin(LabelAdmin):
pass
class SpanTypeAdmin(LabelAdmin):
pass
from .models import (Comment, Example, Project, Seq2seqProject,
SequenceLabelingProject, Tag, TextClassificationProject)
class ExampleAdmin(admin.ModelAdmin):
@ -43,8 +28,6 @@ class CommentAdmin(admin.ModelAdmin):
search_fields = ('user',)
admin.site.register(CategoryType, CategoryTypeAdmin)
admin.site.register(SpanType, SpanTypeAdmin)
admin.site.register(Example, ExampleAdmin)
admin.site.register(Project, ProjectAdmin)
admin.site.register(TextClassificationProject, ProjectAdmin)

94
backend/api/serializers.py

@ -1,87 +1,11 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_polymorphic.serializers import PolymorphicSerializer
from .models import (CategoryType, Comment, Example, ExampleState,
from .models import (Comment, Example, ExampleState,
ImageClassificationProject,
IntentDetectionAndSlotFillingProject, Label, Project,
RelationTypes, Seq2seqProject, SequenceLabelingProject,
SpanType, Speech2textProject, Tag,
TextClassificationProject)
class LabelSerializer(serializers.ModelSerializer):
def validate(self, attrs):
prefix_key = attrs.get('prefix_key')
suffix_key = attrs.get('suffix_key')
# In the case of user don't set any shortcut key.
if prefix_key is None and suffix_key is None:
return super().validate(attrs)
# Don't allow shortcut key not to have a suffix key.
if prefix_key and not suffix_key:
raise ValidationError('Shortcut key may not have a suffix key.')
# Don't allow to save same shortcut key when prefix_key is null.
try:
context = self.context['request'].parser_context
project_id = context['kwargs']['project_id']
label_id = context['kwargs'].get('label_id')
except (AttributeError, KeyError):
pass # unit tests don't always have the correct context set up
else:
conflicting_labels = self.Meta.model.objects.filter(
suffix_key=suffix_key,
prefix_key=prefix_key,
project=project_id,
)
if label_id is not None:
conflicting_labels = conflicting_labels.exclude(id=label_id)
if conflicting_labels.exists():
raise ValidationError('Duplicate shortcut key.')
return super().validate(attrs)
class Meta:
model = Label
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
class CategoryTypeSerializer(LabelSerializer):
class Meta:
model = CategoryType
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
class SpanTypeSerializer(LabelSerializer):
class Meta:
model = SpanType
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
IntentDetectionAndSlotFillingProject, Project,
Seq2seqProject, SequenceLabelingProject,
Speech2textProject, Tag, TextClassificationProject)
class CommentSerializer(serializers.ModelSerializer):
@ -224,13 +148,3 @@ class ProjectPolymorphicSerializer(PolymorphicSerializer):
cls.Meta.model: cls for cls in ProjectSerializer.__subclasses__()
}
}
class RelationTypesSerializer(serializers.ModelSerializer):
def validate(self, attrs):
return super().validate(attrs)
class Meta:
model = RelationTypes
fields = ('id', 'color', 'name')

48
backend/api/urls.py

@ -1,39 +1,8 @@
from django.urls import include, path
from .views import (comment, example, example_state, health, label, project,
tag, task)
from .views import comment, example, example_state, health, project, tag, task
urlpatterns_project = [
path(
route='category-types',
view=label.CategoryTypeList.as_view(),
name='category_types'
),
path(
route='category-types/<int:label_id>',
view=label.CategoryTypeDetail.as_view(),
name='category_type'
),
path(
route='span-types',
view=label.SpanTypeList.as_view(),
name='span_types'
),
path(
route='span-types/<int:label_id>',
view=label.SpanTypeDetail.as_view(),
name='span_type'
),
path(
route='category-type-upload',
view=label.CategoryTypeUploadAPI.as_view(),
name='category_type_upload'
),
path(
route='span-type-upload',
view=label.SpanTypeUploadAPI.as_view(),
name='span_type_upload'
),
path(
route='examples',
view=example.ExampleList.as_view(),
@ -44,21 +13,6 @@ urlpatterns_project = [
view=example.ExampleDetail.as_view(),
name='example_detail'
),
path(
route='relation_types',
view=label.RelationTypeList.as_view(),
name='relation_types_list'
),
path(
route='relation_type-upload',
view=label.RelationTypeUploadAPI.as_view(),
name='relation_type-upload'
),
path(
route='relation_types/<int:relation_type_id>',
view=label.RelationTypeDetail.as_view(),
name='relation_type_detail'
),
path(
route='tags',
view=tag.TagList.as_view(),

116
backend/api/views/label.py

@ -1,116 +0,0 @@
import json
import re
from django.db import IntegrityError, transaction
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, status
from rest_framework.exceptions import ParseError
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from members.permissions import IsInProjectReadOnlyOrAdmin, IsProjectAdmin
from ..exceptions import LabelValidationError
from ..models import CategoryType, Label, RelationTypes, SpanType
from ..serializers import (CategoryTypeSerializer, LabelSerializer,
RelationTypesSerializer, SpanTypeSerializer)
def camel_to_snake(name):
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
def camel_to_snake_dict(d):
return {camel_to_snake(k): v for k, v in d.items()}
class LabelList(generics.ListCreateAPIView):
model = Label
filter_backends = [DjangoFilterBackend]
serializer_class = LabelSerializer
pagination_class = None
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get_queryset(self):
return self.model.objects.filter(project=self.kwargs['project_id'])
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs['project_id'])
def delete(self, request, *args, **kwargs):
delete_ids = request.data['ids']
self.model.objects.filter(pk__in=delete_ids).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class CategoryTypeList(LabelList):
model = CategoryType
serializer_class = CategoryTypeSerializer
class CategoryTypeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = CategoryType.objects.all()
serializer_class = CategoryTypeSerializer
lookup_url_kwarg = 'label_id'
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class SpanTypeList(LabelList):
model = SpanType
serializer_class = SpanTypeSerializer
class SpanTypeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = SpanType.objects.all()
serializer_class = SpanTypeSerializer
lookup_url_kwarg = 'label_id'
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class RelationTypeList(LabelList):
model = RelationTypes
serializer_class = RelationTypesSerializer
class RelationTypeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = RelationTypes.objects.all()
serializer_class = RelationTypesSerializer
lookup_url_kwarg = 'relation_type_id'
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class LabelUploadAPI(APIView):
parser_classes = (MultiPartParser,)
permission_classes = [IsAuthenticated & IsProjectAdmin]
serializer_class = LabelSerializer
@transaction.atomic
def post(self, request, *args, **kwargs):
if 'file' not in request.data:
raise ParseError('Empty content')
try:
labels = json.load(request.data['file'])
labels = list(map(camel_to_snake_dict, labels))
serializer = self.serializer_class(data=labels, many=True)
serializer.is_valid(raise_exception=True)
serializer.save(project_id=kwargs['project_id'])
return Response(status=status.HTTP_201_CREATED)
except json.decoder.JSONDecodeError:
raise ParseError('The file format is invalid.')
except IntegrityError:
raise LabelValidationError
class CategoryTypeUploadAPI(LabelUploadAPI):
serializer_class = CategoryTypeSerializer
class SpanTypeUploadAPI(LabelUploadAPI):
serializer_class = SpanTypeSerializer
class RelationTypeUploadAPI(LabelUploadAPI):
serializer_class = RelationTypesSerializer

1
backend/app/settings.py

@ -60,6 +60,7 @@ INSTALLED_APPS = [
'data_export.apps.DataExportConfig',
'auto_labeling.apps.AutoLabelingConfig',
'labels.apps.LabelsConfig',
'label_types.apps.LabelTypesConfig',
'rest_framework',
'rest_framework.authtoken',
'django_filters',

1
backend/app/urls.py

@ -49,6 +49,7 @@ urlpatterns += [
path('v1/projects/<int:project_id>/metrics/', include('metrics.urls')),
path('v1/projects/<int:project_id>/', include('auto_labeling.urls')),
path('v1/projects/<int:project_id>/', include('labels.urls')),
path('v1/projects/<int:project_id>/', include('label_types.urls')),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
re_path('', TemplateView.as_view(template_name='index.html')),
]

21
backend/label_types/admin.py

@ -0,0 +1,21 @@
from django.contrib import admin
from api.models import CategoryType, SpanType
class LabelAdmin(admin.ModelAdmin):
list_display = ('text', 'project', 'text_color', 'background_color')
ordering = ('project',)
search_fields = ('text',)
class CategoryTypeAdmin(LabelAdmin):
pass
class SpanTypeAdmin(LabelAdmin):
pass
admin.site.register(CategoryType, CategoryTypeAdmin)
admin.site.register(SpanType, SpanTypeAdmin)

backend/api/exceptions.py → backend/label_types/exceptions.py

88
backend/label_types/serializers.py

@ -0,0 +1,88 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from api.models import Label, CategoryType, SpanType, RelationTypes
class LabelSerializer(serializers.ModelSerializer):
def validate(self, attrs):
prefix_key = attrs.get('prefix_key')
suffix_key = attrs.get('suffix_key')
# In the case of user don't set any shortcut key.
if prefix_key is None and suffix_key is None:
return super().validate(attrs)
# Don't allow shortcut key not to have a suffix key.
if prefix_key and not suffix_key:
raise ValidationError('Shortcut key may not have a suffix key.')
# Don't allow to save same shortcut key when prefix_key is null.
try:
context = self.context['request'].parser_context
project_id = context['kwargs']['project_id']
label_id = context['kwargs'].get('label_id')
except (AttributeError, KeyError):
pass # unit tests don't always have the correct context set up
else:
conflicting_labels = self.Meta.model.objects.filter(
suffix_key=suffix_key,
prefix_key=prefix_key,
project=project_id,
)
if label_id is not None:
conflicting_labels = conflicting_labels.exclude(id=label_id)
if conflicting_labels.exists():
raise ValidationError('Duplicate shortcut key.')
return super().validate(attrs)
class Meta:
model = Label
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
class CategoryTypeSerializer(LabelSerializer):
class Meta:
model = CategoryType
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
class SpanTypeSerializer(LabelSerializer):
class Meta:
model = SpanType
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
class RelationTypesSerializer(serializers.ModelSerializer):
def validate(self, attrs):
return super().validate(attrs)
class Meta:
model = RelationTypes
fields = ('id', 'color', 'name')

0
backend/label_types/tests.py

backend/api/tests/api/test_label.py → backend/label_types/tests/test_views.py

@ -6,9 +6,7 @@ from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from api.models import DOCUMENT_CLASSIFICATION
from .utils import (DATA_DIR, CRUDMixin, make_label, make_project, make_user,
prepare_project)
from api.tests.api.utils import (DATA_DIR, CRUDMixin, make_label, make_project, make_user, prepare_project)
class TestLabelList(CRUDMixin):

54
backend/label_types/urls.py

@ -0,0 +1,54 @@
from django.urls import path
from .views import CategoryTypeList, CategoryTypeDetail, CategoryTypeUploadAPI
from .views import SpanTypeList, SpanTypeDetail, SpanTypeUploadAPI
from .views import RelationTypeList, RelationTypeDetail, RelationTypeUploadAPI
urlpatterns = [
path(
route='category-types',
view=CategoryTypeList.as_view(),
name='category_types'
),
path(
route='category-types/<int:label_id>',
view=CategoryTypeDetail.as_view(),
name='category_type'
),
path(
route='span-types',
view=SpanTypeList.as_view(),
name='span_types'
),
path(
route='span-types/<int:label_id>',
view=SpanTypeDetail.as_view(),
name='span_type'
),
path(
route='category-type-upload',
view=CategoryTypeUploadAPI.as_view(),
name='category_type_upload'
),
path(
route='span-type-upload',
view=SpanTypeUploadAPI.as_view(),
name='span_type_upload'
),
path(
route='relation_types',
view=RelationTypeList.as_view(),
name='relation_types_list'
),
path(
route='relation_type-upload',
view=RelationTypeUploadAPI.as_view(),
name='relation_type-upload'
),
path(
route='relation_types/<int:relation_type_id>',
view=RelationTypeDetail.as_view(),
name='relation_type_detail'
),
]

115
backend/label_types/views.py

@ -0,0 +1,115 @@
import json
import re
from django.db import IntegrityError, transaction
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, status
from rest_framework.exceptions import ParseError
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from members.permissions import IsInProjectReadOnlyOrAdmin, IsProjectAdmin
from api.models import CategoryType, Label, RelationTypes, SpanType
from .exceptions import LabelValidationError
from .serializers import (CategoryTypeSerializer, LabelSerializer,
RelationTypesSerializer, SpanTypeSerializer)
def camel_to_snake(name):
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
def camel_to_snake_dict(d):
return {camel_to_snake(k): v for k, v in d.items()}
class LabelList(generics.ListCreateAPIView):
model = Label
filter_backends = [DjangoFilterBackend]
serializer_class = LabelSerializer
pagination_class = None
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get_queryset(self):
return self.model.objects.filter(project=self.kwargs['project_id'])
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs['project_id'])
def delete(self, request, *args, **kwargs):
delete_ids = request.data['ids']
self.model.objects.filter(pk__in=delete_ids).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class CategoryTypeList(LabelList):
model = CategoryType
serializer_class = CategoryTypeSerializer
class CategoryTypeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = CategoryType.objects.all()
serializer_class = CategoryTypeSerializer
lookup_url_kwarg = 'label_id'
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class SpanTypeList(LabelList):
model = SpanType
serializer_class = SpanTypeSerializer
class SpanTypeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = SpanType.objects.all()
serializer_class = SpanTypeSerializer
lookup_url_kwarg = 'label_id'
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class RelationTypeList(LabelList):
model = RelationTypes
serializer_class = RelationTypesSerializer
class RelationTypeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = RelationTypes.objects.all()
serializer_class = RelationTypesSerializer
lookup_url_kwarg = 'relation_type_id'
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class LabelUploadAPI(APIView):
parser_classes = (MultiPartParser,)
permission_classes = [IsAuthenticated & IsProjectAdmin]
serializer_class = LabelSerializer
@transaction.atomic
def post(self, request, *args, **kwargs):
if 'file' not in request.data:
raise ParseError('Empty content')
try:
labels = json.load(request.data['file'])
labels = list(map(camel_to_snake_dict, labels))
serializer = self.serializer_class(data=labels, many=True)
serializer.is_valid(raise_exception=True)
serializer.save(project_id=kwargs['project_id'])
return Response(status=status.HTTP_201_CREATED)
except json.decoder.JSONDecodeError:
raise ParseError('The file format is invalid.')
except IntegrityError:
raise LabelValidationError
class CategoryTypeUploadAPI(LabelUploadAPI):
serializer_class = CategoryTypeSerializer
class SpanTypeUploadAPI(LabelUploadAPI):
serializer_class = SpanTypeSerializer
class RelationTypeUploadAPI(LabelUploadAPI):
serializer_class = RelationTypesSerializer
Loading…
Cancel
Save