diff --git a/backend/api/managers.py b/backend/api/managers.py index cdf04f03..4294e515 100644 --- a/backend/api/managers.py +++ b/backend/api/managers.py @@ -29,6 +29,26 @@ class AnnotationManager(Manager): distribution[username][label] = count return distribution + def can_annotate(self, label, project) -> bool: + raise NotImplementedError('Please implement this method in the subclass') + + +class CategoryManager(AnnotationManager): + + def get_labels(self, label, project): + if project.collaborative_annotation: + return self.filter(example=label.example) + else: + return self.filter(example=label.example, user=label.user) + + def can_annotate(self, label, project) -> bool: + is_exclusive = project.single_class_classification + categories = self.get_labels(label, project) + if is_exclusive: + return not categories.exists() + else: + return not categories.filter(label=label.label).exists() + class ExampleManager(Manager): diff --git a/backend/api/models.py b/backend/api/models.py index f12f281e..d2a3f18a 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -8,7 +8,8 @@ from django.core.exceptions import ValidationError from django.db import models from polymorphic.models import PolymorphicModel -from .managers import AnnotationManager, ExampleManager, ExampleStateManager +from .managers import (AnnotationManager, CategoryManager, ExampleManager, + ExampleStateManager) DOCUMENT_CLASSIFICATION = 'DocumentClassification' SEQUENCE_LABELING = 'SequenceLabeling' @@ -334,6 +335,7 @@ class Annotation(models.Model): class Category(Annotation): + objects = CategoryManager() example = models.ForeignKey( to=Example, on_delete=models.CASCADE, diff --git a/backend/api/tests/test_category.py b/backend/api/tests/test_category.py new file mode 100644 index 00000000..9d007a80 --- /dev/null +++ b/backend/api/tests/test_category.py @@ -0,0 +1,133 @@ +import abc + +from django.test import TestCase +from model_mommy import mommy + +from api.models import DOCUMENT_CLASSIFICATION, Category + +from .api.utils import prepare_project + + +class TestCategoryAnnotation(abc.ABC, TestCase): + exclusive = True + collaborative = False + + @classmethod + def setUpTestData(cls): + cls.project = prepare_project( + DOCUMENT_CLASSIFICATION, + single_class_classification=cls.exclusive, + collaborative_annotation=cls.collaborative + ) + cls.example = mommy.make('Example', project=cls.project.item) + cls.label_type = mommy.make('CategoryType', project=cls.project.item) + users = cls.project.users + cls.user = users[0] + cls.another_user = users[1] + cls.category = Category( + example=cls.example, + label=cls.label_type, + user=cls.user + ) + + def test_can_annotate_category_to_unannotated_data(self): + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertTrue(can_annotate) + + +class TestExclusiveCategoryAnnotation(TestCategoryAnnotation): + exclusive = True + collaborative = False + + def test_cannot_annotate_different_category_to_annotated_data(self): + mommy.make('Category', example=self.example, user=self.user) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertFalse(can_annotate) + + def test_cannot_annotate_same_category_to_annotated_data(self): + mommy.make('Category', example=self.example, label=self.label_type, user=self.user) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertFalse(can_annotate) + + def test_allow_another_user_to_annotate_same_category(self): + mommy.make( + 'Category', + example=self.example, + label=self.label_type, + user=self.another_user + ) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertTrue(can_annotate) + + +class TestNonExclusiveCategoryAnnotation(TestCategoryAnnotation): + exclusive = False + collaborative = False + + def test_can_annotate_different_category_to_annotated_data(self): + mommy.make('Category', example=self.example, user=self.user) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertTrue(can_annotate) + + def test_cannot_annotate_same_category_to_annotated_data(self): + mommy.make('Category', example=self.example, label=self.label_type, user=self.user) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertFalse(can_annotate) + + def test_allow_another_user_to_annotate_same_category(self): + mommy.make( + 'Category', + example=self.example, + label=self.label_type, + user=self.another_user + ) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertTrue(can_annotate) + + +class TestCollaborativeExclusiveCategoryAnnotation(TestCategoryAnnotation): + exclusive = True + collaborative = True + + def test_deny_another_user_to_annotate_same_category(self): + mommy.make( + 'Category', + example=self.example, + label=self.label_type, + user=self.another_user + ) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertFalse(can_annotate) + + def test_deny_another_user_to_annotate_different_category(self): + mommy.make( + 'Category', + example=self.example, + user=self.another_user + ) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertFalse(can_annotate) + + +class TestCollaborativeNonExclusiveCategoryAnnotation(TestCategoryAnnotation): + exclusive = False + collaborative = True + + def test_deny_another_user_to_annotate_same_category(self): + mommy.make( + 'Category', + example=self.example, + label=self.label_type, + user=self.another_user + ) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertFalse(can_annotate) + + def test_allow_another_user_to_annotate_different_category(self): + mommy.make( + 'Category', + example=self.example, + user=self.another_user + ) + can_annotate = Category.objects.can_annotate(self.category, self.project.item) + self.assertTrue(can_annotate)