You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
12 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
5 years ago
  1. from django.conf import settings
  2. from django.shortcuts import get_object_or_404, redirect
  3. from django_filters.rest_framework import DjangoFilterBackend
  4. from django.db.models import Count, F
  5. from libcloud.base import DriverType, get_driver
  6. from libcloud.storage.types import ContainerDoesNotExistError, ObjectDoesNotExistError
  7. from rest_framework import generics, filters, status
  8. from rest_framework.exceptions import ParseError, ValidationError
  9. from rest_framework.permissions import IsAuthenticated, IsAdminUser
  10. from rest_framework.response import Response
  11. from rest_framework.views import APIView
  12. from rest_framework.parsers import MultiPartParser
  13. from rest_framework_csv.renderers import CSVRenderer
  14. from .filters import DocumentFilter
  15. from .models import Project, Label, Document
  16. from .permissions import IsAdminUserAndWriteOnly, IsProjectUser, IsOwnAnnotation
  17. from .serializers import ProjectSerializer, LabelSerializer, DocumentSerializer, UserSerializer
  18. from .serializers import ProjectPolymorphicSerializer
  19. from .utils import CSVParser, JSONParser, PlainTextParser, CoNLLParser, iterable_to_io
  20. from .utils import JSONLRenderer
  21. from .utils import JSONPainter, CSVPainter
  22. class Me(APIView):
  23. permission_classes = (IsAuthenticated,)
  24. def get(self, request, *args, **kwargs):
  25. serializer = UserSerializer(request.user, context={'request': request})
  26. return Response(serializer.data)
  27. class Features(APIView):
  28. permission_classes = (IsAuthenticated,)
  29. def get(self, request, *args, **kwargs):
  30. return Response({
  31. 'cloud_upload': bool(settings.CLOUD_BROWSER_APACHE_LIBCLOUD_PROVIDER),
  32. })
  33. class ProjectList(generics.ListCreateAPIView):
  34. serializer_class = ProjectPolymorphicSerializer
  35. pagination_class = None
  36. permission_classes = (IsAuthenticated, IsAdminUserAndWriteOnly)
  37. def get_queryset(self):
  38. return self.request.user.projects
  39. def perform_create(self, serializer):
  40. serializer.save(users=[self.request.user])
  41. class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
  42. queryset = Project.objects.all()
  43. serializer_class = ProjectSerializer
  44. lookup_url_kwarg = 'project_id'
  45. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
  46. class StatisticsAPI(APIView):
  47. pagination_class = None
  48. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
  49. def get(self, request, *args, **kwargs):
  50. p = get_object_or_404(Project, pk=self.kwargs['project_id'])
  51. label_count, user_count = self.label_per_data(p)
  52. progress = self.progress(project=p)
  53. response = dict()
  54. response['label'] = label_count
  55. response['user'] = user_count
  56. response.update(progress)
  57. return Response(response)
  58. def progress(self, project):
  59. docs = project.documents
  60. annotation_class = project.get_annotation_class()
  61. total = docs.count()
  62. done = annotation_class.objects.filter(document_id__in=docs.all(),
  63. user_id=self.request.user).\
  64. aggregate(Count('document', distinct=True))['document__count']
  65. remaining = total - done
  66. return {'total': total, 'remaining': remaining}
  67. def label_per_data(self, project):
  68. annotation_class = project.get_annotation_class()
  69. return annotation_class.objects.get_label_per_data(project=project)
  70. class ApproveLabelsAPI(APIView):
  71. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUser)
  72. def post(self, request, *args, **kwargs):
  73. approved = self.request.data.get('approved', True)
  74. document = get_object_or_404(Document, pk=self.kwargs['doc_id'])
  75. document.annotations_approved_by = self.request.user if approved else None
  76. document.save()
  77. return Response(DocumentSerializer(document).data)
  78. class LabelList(generics.ListCreateAPIView):
  79. serializer_class = LabelSerializer
  80. pagination_class = None
  81. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
  82. def get_queryset(self):
  83. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  84. return project.labels
  85. def perform_create(self, serializer):
  86. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  87. serializer.save(project=project)
  88. class LabelDetail(generics.RetrieveUpdateDestroyAPIView):
  89. queryset = Label.objects.all()
  90. serializer_class = LabelSerializer
  91. lookup_url_kwarg = 'label_id'
  92. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
  93. class DocumentList(generics.ListCreateAPIView):
  94. serializer_class = DocumentSerializer
  95. filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
  96. search_fields = ('text', )
  97. ordering_fields = ('created_at', 'updated_at', 'doc_annotations__updated_at',
  98. 'seq_annotations__updated_at', 'seq2seq_annotations__updated_at')
  99. filter_class = DocumentFilter
  100. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
  101. def get_queryset(self):
  102. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  103. queryset = project.documents
  104. if project.randomize_document_order:
  105. queryset = queryset.annotate(sort_id=F('id') % self.request.user.id).order_by('sort_id')
  106. return queryset
  107. def perform_create(self, serializer):
  108. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  109. serializer.save(project=project)
  110. class DocumentDetail(generics.RetrieveUpdateDestroyAPIView):
  111. queryset = Document.objects.all()
  112. serializer_class = DocumentSerializer
  113. lookup_url_kwarg = 'doc_id'
  114. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
  115. class AnnotationList(generics.ListCreateAPIView):
  116. pagination_class = None
  117. permission_classes = (IsAuthenticated, IsProjectUser)
  118. def get_serializer_class(self):
  119. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  120. self.serializer_class = project.get_annotation_serializer()
  121. return self.serializer_class
  122. def get_queryset(self):
  123. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  124. model = project.get_annotation_class()
  125. self.queryset = model.objects.filter(document=self.kwargs['doc_id'],
  126. user=self.request.user)
  127. return self.queryset
  128. def create(self, request, *args, **kwargs):
  129. request.data['document'] = self.kwargs['doc_id']
  130. return super().create(request, args, kwargs)
  131. def perform_create(self, serializer):
  132. doc = get_object_or_404(Document, pk=self.kwargs['doc_id'])
  133. serializer.save(document=doc, user=self.request.user)
  134. class AnnotationDetail(generics.RetrieveUpdateDestroyAPIView):
  135. lookup_url_kwarg = 'annotation_id'
  136. permission_classes = (IsAuthenticated, IsProjectUser, IsOwnAnnotation)
  137. def get_serializer_class(self):
  138. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  139. self.serializer_class = project.get_annotation_serializer()
  140. return self.serializer_class
  141. def get_queryset(self):
  142. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  143. model = project.get_annotation_class()
  144. self.queryset = model.objects.all()
  145. return self.queryset
  146. class TextUploadAPI(APIView):
  147. parser_classes = (MultiPartParser,)
  148. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUser)
  149. def post(self, request, *args, **kwargs):
  150. if 'file' not in request.data:
  151. raise ParseError('Empty content')
  152. self.save_file(
  153. user=request.user,
  154. file=request.data['file'],
  155. file_format=request.data['format'],
  156. project_id=kwargs['project_id'],
  157. )
  158. return Response(status=status.HTTP_201_CREATED)
  159. @classmethod
  160. def save_file(cls, user, file, file_format, project_id):
  161. project = get_object_or_404(Project, pk=project_id)
  162. parser = cls.select_parser(file_format)
  163. data = parser.parse(file)
  164. storage = project.get_storage(data)
  165. storage.save(user)
  166. @classmethod
  167. def select_parser(cls, file_format):
  168. if file_format == 'plain':
  169. return PlainTextParser()
  170. elif file_format == 'csv':
  171. return CSVParser()
  172. elif file_format == 'json':
  173. return JSONParser()
  174. elif file_format == 'conll':
  175. return CoNLLParser()
  176. else:
  177. raise ValidationError('format {} is invalid.'.format(file_format))
  178. class CloudUploadAPI(APIView):
  179. permission_classes = TextUploadAPI.permission_classes
  180. def get(self, request, *args, **kwargs):
  181. try:
  182. project_id = request.query_params['project_id']
  183. file_format = request.query_params['upload_format']
  184. cloud_container = request.query_params['container']
  185. cloud_object = request.query_params['object']
  186. except KeyError as ex:
  187. raise ValidationError('query parameter {} is missing'.format(ex))
  188. try:
  189. cloud_file = self.get_cloud_object_as_io(cloud_container, cloud_object)
  190. except ContainerDoesNotExistError:
  191. raise ValidationError('cloud container {} does not exist'.format(cloud_container))
  192. except ObjectDoesNotExistError:
  193. raise ValidationError('cloud object {} does not exist'.format(cloud_object))
  194. TextUploadAPI.save_file(
  195. user=request.user,
  196. file=cloud_file,
  197. file_format=file_format,
  198. project_id=project_id,
  199. )
  200. next_url = request.query_params.get('next')
  201. if next_url == 'about:blank':
  202. return Response(data='', content_type='text/plain', status=status.HTTP_201_CREATED)
  203. if next_url:
  204. return redirect(next_url)
  205. return Response(status=status.HTTP_201_CREATED)
  206. @classmethod
  207. def get_cloud_object_as_io(cls, container_name, object_name):
  208. provider = settings.CLOUD_BROWSER_APACHE_LIBCLOUD_PROVIDER.lower()
  209. account = settings.CLOUD_BROWSER_APACHE_LIBCLOUD_ACCOUNT
  210. key = settings.CLOUD_BROWSER_APACHE_LIBCLOUD_SECRET_KEY
  211. driver = get_driver(DriverType.STORAGE, provider)
  212. client = driver(account, key)
  213. cloud_container = client.get_container(container_name)
  214. cloud_object = cloud_container.get_object(object_name)
  215. return iterable_to_io(cloud_object.as_stream())
  216. class TextDownloadAPI(APIView):
  217. permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUser)
  218. renderer_classes = (CSVRenderer, JSONLRenderer)
  219. def get(self, request, *args, **kwargs):
  220. format = request.query_params.get('q')
  221. project = get_object_or_404(Project, pk=self.kwargs['project_id'])
  222. documents = project.documents.all()
  223. painter = self.select_painter(format)
  224. # json1 format prints text labels while json format prints annotations with label ids
  225. # json1 format - "labels": [[0, 15, "PERSON"], ..]
  226. # json format - "annotations": [{"label": 5, "start_offset": 0, "end_offset": 2, "user": 1},..]
  227. if format == "json1":
  228. labels = project.labels.all()
  229. data = JSONPainter.paint_labels(documents, labels)
  230. else:
  231. data = painter.paint(documents)
  232. return Response(data)
  233. def select_painter(self, format):
  234. if format == 'csv':
  235. return CSVPainter()
  236. elif format == 'json' or format == "json1":
  237. return JSONPainter()
  238. else:
  239. raise ValidationError('format {} is invalid.'.format(format))