diff --git a/backend/api/admin.py b/backend/api/admin.py index 22297bde..09c661e3 100644 --- a/backend/api/admin.py +++ b/backend/api/admin.py @@ -1,9 +1,8 @@ from django.contrib import admin -from .models import (Comment, Document, DocumentAnnotation, Label, Project, - Role, RoleMapping, Seq2seqAnnotation, Seq2seqProject, - SequenceAnnotation, SequenceLabelingProject, Tag, - TextClassificationProject) +from .models import (Category, Comment, Document, Label, Project, Role, + RoleMapping, Seq2seqProject, SequenceLabelingProject, + Span, Tag, TextClassificationProject, TextLabel) class LabelAdmin(admin.ModelAdmin): @@ -24,22 +23,19 @@ class ProjectAdmin(admin.ModelAdmin): search_fields = ('name',) -class SequenceAnnotationAdmin(admin.ModelAdmin): - list_display = ('document', 'label', 'start_offset', 'user') - ordering = ('document',) - search_fields = ('document__text',) +class SpanAdmin(admin.ModelAdmin): + list_display = ('example', 'label', 'start_offset', 'user') + ordering = ('example',) -class DocumentAnnotationAdmin(admin.ModelAdmin): - list_display = ('document', 'label', 'user') - ordering = ('document',) - search_fields = ('document__text',) +class CategoryAdmin(admin.ModelAdmin): + list_display = ('example', 'label', 'user') + ordering = ('example',) -class Seq2seqAnnotationAdmin(admin.ModelAdmin): - list_display = ('document', 'text', 'user') - ordering = ('document',) - search_fields = ('document__text',) +class TextLabelAdmin(admin.ModelAdmin): + list_display = ('example', 'text', 'user') + ordering = ('example',) class RoleAdmin(admin.ModelAdmin): @@ -66,9 +62,9 @@ class CommentAdmin(admin.ModelAdmin): search_fields = ('user',) -admin.site.register(DocumentAnnotation, DocumentAnnotationAdmin) -admin.site.register(SequenceAnnotation, SequenceAnnotationAdmin) -admin.site.register(Seq2seqAnnotation, Seq2seqAnnotationAdmin) +admin.site.register(Category, CategoryAdmin) +admin.site.register(Span, SpanAdmin) +admin.site.register(TextLabel, TextLabelAdmin) admin.site.register(Label, LabelAdmin) admin.site.register(Document, DocumentAdmin) admin.site.register(Project, ProjectAdmin) diff --git a/backend/api/filters.py b/backend/api/filters.py index c9e9dcf5..4e90b148 100644 --- a/backend/api/filters.py +++ b/backend/api/filters.py @@ -5,10 +5,9 @@ from .models import Document class DocumentFilter(FilterSet): - seq_annotations__isnull = BooleanFilter(field_name='seq_annotations', method='filter_annotations') - doc_annotations__isnull = BooleanFilter(field_name='doc_annotations', method='filter_annotations') - seq2seq_annotations__isnull = BooleanFilter(field_name='seq2seq_annotations', method='filter_annotations') - speech2text_annotations__isnull = BooleanFilter(field_name='speech2text_annotations', method='filter_annotations') + spans__isnull = BooleanFilter(field_name='spans', method='filter_annotations') + categories__isnull = BooleanFilter(field_name='categories', method='filter_annotations') + texts__isnull = BooleanFilter(field_name='texts', method='filter_annotations') def filter_annotations(self, queryset, field_name, value): queryset = queryset.annotate(num_annotations=Count( @@ -27,7 +26,8 @@ class DocumentFilter(FilterSet): class Meta: model = Document - fields = ('project', 'text', 'created_at', 'updated_at', - 'doc_annotations__label__id', 'seq_annotations__label__id', - 'doc_annotations__isnull', 'seq_annotations__isnull', 'seq2seq_annotations__isnull', - 'speech2text_annotations__isnull') + fields = ( + 'project', 'text', 'created_at', 'updated_at', + 'categories__label__id', 'spans__label__id', + 'categories__isnull', 'spans__isnull', 'texts__isnull' + ) diff --git a/backend/api/managers.py b/backend/api/managers.py index 1d1fb2b0..90001b0a 100644 --- a/backend/api/managers.py +++ b/backend/api/managers.py @@ -10,7 +10,7 @@ class AnnotationManager(Manager): label_count = Counter() user_count = Counter() docs = project.examples.all() - annotations = self.filter(document_id__in=docs.all()) + annotations = self.filter(example_id__in=docs.all()) for d in annotations.values('label__text', 'user__username').annotate(Count('label'), Count('user')): label_count[d['label__text']] += d['label__count'] @@ -25,7 +25,7 @@ class Seq2seqAnnotationManager(Manager): label_count = Counter() user_count = Counter() docs = project.examples.all() - annotations = self.filter(document_id__in=docs.all()) + annotations = self.filter(example_id__in=docs.all()) for d in annotations.values('text', 'user__username').annotate(Count('text'), Count('user')): label_count[d['text']] += d['text__count'] diff --git a/backend/api/models.py b/backend/api/models.py index 710d98c8..ff448079 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -45,31 +45,31 @@ class Project(PolymorphicModel): class TextClassificationProject(Project): def get_annotation_class(self): - return DocumentAnnotation + return Category class SequenceLabelingProject(Project): def get_annotation_class(self): - return SequenceAnnotation + return Span class Seq2seqProject(Project): def get_annotation_class(self): - return Seq2seqAnnotation + return TextLabel class Speech2textProject(Project): def get_annotation_class(self): - return Speech2textAnnotation + return TextLabel class ImageClassificationProject(Project): def get_annotation_class(self): - return ImageCategoryLabel + return Category class Label(models.Model): @@ -116,10 +116,19 @@ class Label(models.Model): class Example(PolymorphicModel): meta = models.JSONField(default=dict) filename = models.FileField(default='.') - project = models.ForeignKey(Project, related_name='examples', on_delete=models.CASCADE) + project = models.ForeignKey( + to=Project, + on_delete=models.CASCADE, + related_name='examples' + ) + annotations_approved_by = models.ForeignKey( + to=User, + on_delete=models.SET_NULL, + null=True, + blank=True + ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - annotations_approved_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) @property def comment_count(self): @@ -128,22 +137,26 @@ class Example(PolymorphicModel): class Document(Example): text = models.TextField() - example = models.OneToOneField(Example, - on_delete=models.CASCADE, - primary_key=True, - db_column='id', - parent_link=True) + example = models.OneToOneField( + to=Example, + on_delete=models.CASCADE, + primary_key=True, + db_column='id', + parent_link=True + ) def __str__(self): return self.text[:50] class Image(Example): - example = models.OneToOneField(Example, - on_delete=models.CASCADE, - primary_key=True, - db_column='id', - parent_link=True) + example = models.OneToOneField( + to=Example, + on_delete=models.CASCADE, + primary_key=True, + db_column='id', + parent_link=True + ) def __str__(self): return self.filename @@ -151,8 +164,16 @@ class Image(Example): class Comment(models.Model): text = models.TextField() - example = models.ForeignKey(Example, related_name='comments', on_delete=models.CASCADE) - user = models.ForeignKey(User, on_delete=models.CASCADE, null=True) + example = models.ForeignKey( + to=Example, + on_delete=models.CASCADE, + related_name='comments' + ) + user = models.ForeignKey( + to=User, + on_delete=models.CASCADE, + null=True + ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -166,7 +187,11 @@ class Comment(models.Model): class Tag(models.Model): text = models.TextField() - project = models.ForeignKey(Project, related_name='tags', on_delete=models.CASCADE) + project = models.ForeignKey( + to=Project, + on_delete=models.CASCADE, + related_name='tags' + ) def __str__(self): return self.text @@ -185,17 +210,35 @@ class Annotation(models.Model): abstract = True -class DocumentAnnotation(Annotation): - document = models.ForeignKey(Document, related_name='doc_annotations', on_delete=models.CASCADE) - label = models.ForeignKey(Label, on_delete=models.CASCADE) +class Category(Annotation): + example = models.ForeignKey( + to=Example, + on_delete=models.CASCADE, + related_name='categories' + ) + label = models.ForeignKey( + to=Label, + on_delete=models.CASCADE + ) class Meta: - unique_together = ('document', 'user', 'label') + unique_together = ( + 'example', + 'user', + 'label' + ) -class SequenceAnnotation(Annotation): - document = models.ForeignKey(Document, related_name='seq_annotations', on_delete=models.CASCADE) - label = models.ForeignKey(Label, on_delete=models.CASCADE) +class Span(Annotation): + example = models.ForeignKey( + to=Example, + on_delete=models.CASCADE, + related_name='spans' + ) + label = models.ForeignKey( + to=Label, + on_delete=models.CASCADE + ) start_offset = models.IntegerField() end_offset = models.IntegerField() @@ -204,34 +247,30 @@ class SequenceAnnotation(Annotation): raise ValidationError('start_offset is after end_offset') class Meta: - unique_together = ('document', 'user', 'label', 'start_offset', 'end_offset') + unique_together = ( + 'example', + 'user', + 'label', + 'start_offset', + 'end_offset' + ) -class Seq2seqAnnotation(Annotation): - # Override AnnotationManager for custom functionality +class TextLabel(Annotation): objects = Seq2seqAnnotationManager() - - document = models.ForeignKey(Document, related_name='seq2seq_annotations', on_delete=models.CASCADE) - text = models.CharField(max_length=500) - - class Meta: - unique_together = ('document', 'user', 'text') - - -class Speech2textAnnotation(Annotation): - document = models.ForeignKey(Document, related_name='speech2text_annotations', on_delete=models.CASCADE) + example = models.ForeignKey( + to=Example, + on_delete=models.CASCADE, + related_name='texts' + ) text = models.TextField() class Meta: - unique_together = ('document', 'user') - - -class ImageCategoryLabel(Annotation): - image = models.ForeignKey(Image, related_name='categories', on_delete=models.CASCADE) - label = models.ForeignKey(Label, on_delete=models.CASCADE) - - class Meta: - unique_together = ('image', 'user', 'label') + unique_together = ( + 'example', + 'user', + 'text' + ) class Role(models.Model): diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 0152e5b1..8fc7c93f 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -7,13 +7,11 @@ from rest_framework.exceptions import ValidationError from rest_polymorphic.serializers import PolymorphicSerializer from .models import (DOCUMENT_CLASSIFICATION, SEQ2SEQ, SEQUENCE_LABELING, - SPEECH2TEXT, AutoLabelingConfig, Comment, Document, - DocumentAnnotation, Example, Image, ImageCategoryLabel, - ImageClassificationProject, Label, Project, Role, - RoleMapping, Seq2seqAnnotation, Seq2seqProject, - SequenceAnnotation, SequenceLabelingProject, - Speech2textAnnotation, Speech2textProject, Tag, - TextClassificationProject) + SPEECH2TEXT, AutoLabelingConfig, Category, Comment, + Document, Example, Image, ImageClassificationProject, + Label, Project, Role, RoleMapping, Seq2seqProject, + SequenceLabelingProject, Span, Speech2textProject, Tag, + TextClassificationProject, TextLabel) class UserSerializer(serializers.ModelSerializer): @@ -89,7 +87,7 @@ class BaseDataSerializer(serializers.ModelSerializer): project = instance.project model = project.get_annotation_class() serializer = get_annotation_serializer(task=project.project_type) - annotations = model.objects.filter(document=instance.id) + annotations = model.objects.filter(example=instance.id) if request and not project.collaborative_annotation: annotations = annotations.filter(user=request.user) serializer = serializer(annotations, many=True) @@ -225,51 +223,58 @@ class ProjectFilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): return queryset.filter(project=view.kwargs['project_id']) -class DocumentAnnotationSerializer(serializers.ModelSerializer): +class CategorySerializer(serializers.ModelSerializer): label = serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()) - document = serializers.PrimaryKeyRelatedField(queryset=Document.objects.all()) + example = serializers.PrimaryKeyRelatedField(queryset=Example.objects.all()) class Meta: - model = DocumentAnnotation - fields = ('id', 'prob', 'label', 'user', 'document', 'created_at', 'updated_at') - read_only_fields = ('user', ) - - -class SequenceAnnotationSerializer(serializers.ModelSerializer): - label = serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()) - document = serializers.PrimaryKeyRelatedField(queryset=Document.objects.all()) - - class Meta: - model = SequenceAnnotation - fields = ('id', 'prob', 'label', 'start_offset', 'end_offset', 'user', 'document', 'created_at', 'updated_at') - read_only_fields = ('user',) - - -class Seq2seqAnnotationSerializer(serializers.ModelSerializer): - document = serializers.PrimaryKeyRelatedField(queryset=Document.objects.all()) - - class Meta: - model = Seq2seqAnnotation - fields = ('id', 'text', 'user', 'document', 'prob', 'created_at', 'updated_at') + model = Category + fields = ( + 'id', + 'prob', + 'user', + 'example', + 'created_at', + 'updated_at', + 'label', + ) read_only_fields = ('user',) -class Speech2textAnnotationSerializer(serializers.ModelSerializer): - document = serializers.PrimaryKeyRelatedField(queryset=Document.objects.all()) +class SpanSerializer(serializers.ModelSerializer): + label = serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()) + example = serializers.PrimaryKeyRelatedField(queryset=Example.objects.all()) class Meta: - model = Speech2textAnnotation - fields = ('id', 'prob', 'text', 'user', 'document', 'created_at', 'updated_at') + model = Span + fields = ( + 'id', + 'prob', + 'user', + 'example', + 'created_at', + 'updated_at', + 'label', + 'start_offset', + 'end_offset', + ) read_only_fields = ('user',) -class ImageClassificationLabelSerializer(serializers.ModelSerializer): - label = serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()) - image = serializers.PrimaryKeyRelatedField(queryset=Image.objects.all()) +class TextLabelSerializer(serializers.ModelSerializer): + example = serializers.PrimaryKeyRelatedField(queryset=Example.objects.all()) class Meta: - model = ImageCategoryLabel - fields = ('id', 'prob', 'label', 'user', 'image', 'created_at', 'updated_at') + model = TextLabel + fields = ( + 'id', + 'prob', + 'user', + 'example', + 'created_at', + 'updated_at', + 'text', + ) read_only_fields = ('user',) @@ -334,10 +339,10 @@ class AutoLabelingConfigSerializer(serializers.ModelSerializer): def get_annotation_serializer(task: str): mapping = { - DOCUMENT_CLASSIFICATION: DocumentAnnotationSerializer, - SEQUENCE_LABELING: SequenceAnnotationSerializer, - SEQ2SEQ: Seq2seqAnnotationSerializer, - SPEECH2TEXT: Speech2textAnnotationSerializer + DOCUMENT_CLASSIFICATION: CategorySerializer, + SEQUENCE_LABELING: SpanSerializer, + SEQ2SEQ: TextLabelSerializer, + SPEECH2TEXT: TextLabelSerializer } try: return mapping[task] diff --git a/backend/api/tests/api/test_annotation.py b/backend/api/tests/api/test_annotation.py index 53c70fbe..27d2f485 100644 --- a/backend/api/tests/api/test_annotation.py +++ b/backend/api/tests/api/test_annotation.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.reverse import reverse -from ...models import DOCUMENT_CLASSIFICATION, DocumentAnnotation +from ...models import DOCUMENT_CLASSIFICATION, Category from .utils import (CRUDMixin, make_annotation, make_doc, make_label, make_user, prepare_project) @@ -30,7 +30,7 @@ class TestAnnotationList(CRUDMixin): def test_allows_project_member_to_bulk_delete_annotation(self): self.assert_delete(self.project.users[0], status.HTTP_204_NO_CONTENT) - count = DocumentAnnotation.objects.count() + count = Category.objects.count() self.assertEqual(count, 2) # delete only own annotation @@ -51,7 +51,7 @@ class TestSharedAnnotationList(CRUDMixin): def test_allows_project_member_to_bulk_delete_annotation(self): self.assert_delete(self.project.users[0], status.HTTP_204_NO_CONTENT) - count = DocumentAnnotation.objects.count() + count = Category.objects.count() self.assertEqual(count, 0) # delete all annotation in the doc diff --git a/backend/api/tests/api/test_statistics.py b/backend/api/tests/api/test_statistics.py index 8ec7b478..be5eb555 100644 --- a/backend/api/tests/api/test_statistics.py +++ b/backend/api/tests/api/test_statistics.py @@ -34,8 +34,8 @@ class TestStatisticsAPI(APITestCase, TestUtilsMixin): ) doc1 = mommy.make('Document', project=cls.project) doc2 = mommy.make('Document', project=cls.project) - mommy.make('DocumentAnnotation', document=doc1, user=super_user) - mommy.make('DocumentAnnotation', document=doc2, user=other_user) + mommy.make('Category', example=doc1, user=super_user) + mommy.make('Category', example=doc2, user=other_user) cls.url = reverse(viewname='statistics', args=[cls.project.id]) cls.doc = Document.objects.filter(project=cls.project) diff --git a/backend/api/tests/api/utils.py b/backend/api/tests/api/utils.py index 518274fb..72305d47 100644 --- a/backend/api/tests/api/utils.py +++ b/backend/api/tests/api/utils.py @@ -102,12 +102,12 @@ def make_comment(doc, user): def make_annotation(task, doc, user): annotation_model = { - DOCUMENT_CLASSIFICATION: 'DocumentAnnotation', - SEQUENCE_LABELING: 'SequenceAnnotation', - SEQ2SEQ: 'Seq2seqAnnotation', - SPEECH2TEXT: 'Speech2textAnnotation' + DOCUMENT_CLASSIFICATION: 'Category', + SEQUENCE_LABELING: 'Span', + SEQ2SEQ: 'TextLabel', + SPEECH2TEXT: 'TextLabel' }.get(task) - return mommy.make(annotation_model, document=doc, user=user) + return mommy.make(annotation_model, example=doc, user=user) def prepare_project(task: str = 'Any', collaborative_annotation=False): diff --git a/backend/api/tests/test_models.py b/backend/api/tests/test_models.py index 51e2f705..fee0c46d 100644 --- a/backend/api/tests/test_models.py +++ b/backend/api/tests/test_models.py @@ -3,8 +3,7 @@ from django.db.utils import IntegrityError from django.test import TestCase, override_settings from model_mommy import mommy -from ..models import (DocumentAnnotation, Label, Seq2seqAnnotation, - SequenceAnnotation, Speech2textAnnotation) +from ..models import Category, Label, Span, TextLabel @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage') @@ -16,7 +15,7 @@ class TestTextClassificationProject(TestCase): def test_get_annotation_class(self): klass = self.project.get_annotation_class() - self.assertEqual(klass, DocumentAnnotation) + self.assertEqual(klass, Category) @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage') @@ -28,7 +27,7 @@ class TestSequenceLabelingProject(TestCase): def test_get_annotation_class(self): klass = self.project.get_annotation_class() - self.assertEqual(klass, SequenceAnnotation) + self.assertEqual(klass, Span) @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage') @@ -40,7 +39,7 @@ class TestSeq2seqProject(TestCase): def test_get_annotation_class(self): klass = self.project.get_annotation_class() - self.assertEqual(klass, Seq2seqAnnotation) + self.assertEqual(klass, TextLabel) @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage') @@ -52,7 +51,7 @@ class TestSpeech2textProject(TestCase): def test_get_annotation_class(self): klass = self.project.get_annotation_class() - self.assertEqual(klass, Speech2textAnnotation) + self.assertEqual(klass, TextLabel) class TestLabel(TestCase): @@ -109,36 +108,36 @@ class TestLabel(TestCase): self.fail(msg=ValidationError) -class TestDocumentAnnotation(TestCase): +class TestCategory(TestCase): def test_uniqueness(self): - a = mommy.make('DocumentAnnotation') + a = mommy.make('Category') with self.assertRaises(IntegrityError): - DocumentAnnotation(document=a.document, user=a.user, label=a.label).save() + Category(example=a.example, user=a.user, label=a.label).save() class TestSequenceAnnotation(TestCase): def test_uniqueness(self): - a = mommy.make('SequenceAnnotation') + a = mommy.make('Span') with self.assertRaises(IntegrityError): - SequenceAnnotation(document=a.document, - user=a.user, - label=a.label, - start_offset=a.start_offset, - end_offset=a.end_offset).save() + Span(example=a.example, + user=a.user, + label=a.label, + start_offset=a.start_offset, + end_offset=a.end_offset).save() def test_position_constraint(self): with self.assertRaises(ValidationError): - mommy.make('SequenceAnnotation', + mommy.make('Span', start_offset=1, end_offset=0).clean() class TestSeq2seqAnnotation(TestCase): def test_uniqueness(self): - a = mommy.make('Seq2seqAnnotation') + a = mommy.make('TextLabel') with self.assertRaises(IntegrityError): - Seq2seqAnnotation(document=a.document, - user=a.user, - text=a.text).save() + TextLabel(example=a.example, + user=a.user, + text=a.text).save() diff --git a/backend/api/views/annotation.py b/backend/api/views/annotation.py index a119d51c..9b9475e9 100644 --- a/backend/api/views/annotation.py +++ b/backend/api/views/annotation.py @@ -25,7 +25,7 @@ class AnnotationList(generics.ListCreateAPIView): def get_queryset(self): model = self.project.get_annotation_class() - queryset = model.objects.filter(document=self.kwargs['doc_id']) + queryset = model.objects.filter(example=self.kwargs['doc_id']) if not self.project.collaborative_annotation: queryset = queryset.filter(user=self.request.user) return queryset @@ -33,11 +33,11 @@ class AnnotationList(generics.ListCreateAPIView): def create(self, request, *args, **kwargs): if self.project.single_class_classification: self.get_queryset().delete() - request.data['document'] = self.kwargs['doc_id'] + request.data['example'] = self.kwargs['doc_id'] return super().create(request, args, kwargs) def perform_create(self, serializer): - serializer.save(document_id=self.kwargs['doc_id'], user=self.request.user) + serializer.save(example_id=self.kwargs['doc_id'], user=self.request.user) def delete(self, request, *args, **kwargs): queryset = self.get_queryset() @@ -49,22 +49,23 @@ class AnnotationDetail(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): - project = get_object_or_404(Project, pk=self.kwargs['project_id']) - if project.collaborative_annotation: + if self.project.collaborative_annotation: self.permission_classes = [IsAuthenticated & IsInProjectOrAdmin] else: self.permission_classes = [IsAuthenticated & IsInProjectOrAdmin & IsOwnAnnotation] return super().get_permissions() def get_serializer_class(self): - project = get_object_or_404(Project, pk=self.kwargs['project_id']) - self.serializer_class = get_annotation_serializer(task=project.project_type) + self.serializer_class = get_annotation_serializer(task=self.project.project_type) return self.serializer_class def get_queryset(self): - project = get_object_or_404(Project, pk=self.kwargs['project_id']) - model = project.get_annotation_class() + model = self.project.get_annotation_class() self.queryset = model.objects.all() return self.queryset diff --git a/backend/api/views/example.py b/backend/api/views/example.py index 5a73805b..8d200e3e 100644 --- a/backend/api/views/example.py +++ b/backend/api/views/example.py @@ -10,7 +10,8 @@ from rest_framework.response import Response from ..filters import DocumentFilter from ..models import Document, Example, Image, Project from ..permissions import IsInProjectReadOnlyOrAdmin -from ..serializers import DocumentSerializer, ExampleSerializer, ImageSerializer +from ..serializers import (DocumentSerializer, ExampleSerializer, + ImageSerializer) class ExampleList(generics.ListCreateAPIView): diff --git a/backend/api/views/statistics.py b/backend/api/views/statistics.py index 17dc71d3..bdbed40b 100644 --- a/backend/api/views/statistics.py +++ b/backend/api/views/statistics.py @@ -39,20 +39,20 @@ class StatisticsAPI(APIView): def _get_user_completion_data(annotation_class, annotation_filter): all_annotation_objects = annotation_class.objects.filter(annotation_filter) set_user_data = collections.defaultdict(set) - for ind_obj in all_annotation_objects.values('user__username', 'document__id'): - set_user_data[ind_obj['user__username']].add(ind_obj['document__id']) + for ind_obj in all_annotation_objects.values('user__username', 'example__id'): + set_user_data[ind_obj['user__username']].add(ind_obj['example__id']) return {i: len(set_user_data[i]) for i in set_user_data} def progress(self, project): docs = project.examples annotation_class = project.get_annotation_class() total = docs.count() - annotation_filter = Q(document_id__in=docs.all()) + annotation_filter = Q(example_id__in=docs.all()) user_data = self._get_user_completion_data(annotation_class, annotation_filter) if not project.collaborative_annotation: annotation_filter &= Q(user_id=self.request.user) done = annotation_class.objects.filter(annotation_filter)\ - .aggregate(Count('document', distinct=True))['document__count'] + .aggregate(Count('example', distinct=True))['example__count'] remaining = total - done return {'total': total, 'remaining': remaining, 'user': user_data}