Browse Source

Add SpanManager

pull/1650/head
Hironsan 2 years ago
parent
commit
18373aa160
3 changed files with 162 additions and 1 deletions
  1. 13
      backend/api/managers.py
  2. 8
      backend/api/models.py
  3. 142
      backend/api/tests/test_span.py

13
backend/api/managers.py

@ -53,6 +53,19 @@ class CategoryManager(AnnotationManager):
return not categories.filter(label=label.label).exists()
class SpanManager(AnnotationManager):
def can_annotate(self, label, project) -> bool:
overlapping = getattr(project, 'allow_overlapping', False)
spans = self.get_labels(label, project)
if overlapping:
return True
for span in spans:
if span.is_overlapping(label):
return False
return True
class ExampleManager(Manager):
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):

8
backend/api/models.py

@ -9,7 +9,7 @@ from django.db import models
from polymorphic.models import PolymorphicModel
from .managers import (AnnotationManager, CategoryManager, ExampleManager,
ExampleStateManager)
ExampleStateManager, SpanManager)
DOCUMENT_CLASSIFICATION = 'DocumentClassification'
SEQUENCE_LABELING = 'SequenceLabeling'
@ -352,6 +352,7 @@ class Category(Annotation):
class Span(Annotation):
objects = SpanManager()
example = models.ForeignKey(
to=Example,
on_delete=models.CASCADE,
@ -385,6 +386,11 @@ class Span(Annotation):
self.full_clean()
super().save(force_insert, force_update, using, update_fields)
def is_overlapping(self, other: 'Span'):
return (other.start_offset <= self.start_offset < other.end_offset) or\
(other.start_offset < self.end_offset <= other.end_offset) or\
(self.start_offset < other.start_offset and other.end_offset < self.end_offset)
class Meta:
constraints = [
models.CheckConstraint(check=models.Q(start_offset__gte=0), name='startOffset >= 0'),

142
backend/api/tests/test_span.py

@ -0,0 +1,142 @@
import abc
from django.test import TestCase
from model_mommy import mommy
from api.models import SEQUENCE_LABELING, Span
from .api.utils import prepare_project
class TestSpanAnnotation(abc.ABC, TestCase):
overlapping = False
collaborative = False
@classmethod
def setUpTestData(cls):
cls.project = prepare_project(
SEQUENCE_LABELING,
allow_overlapping=cls.overlapping,
collaborative_annotation=cls.collaborative
)
cls.example = mommy.make('Example', project=cls.project.item)
cls.label_type = mommy.make('SpanType', project=cls.project.item)
users = cls.project.users
cls.user = users[0]
cls.another_user = users[1]
cls.span = Span(
example=cls.example,
label=cls.label_type,
user=cls.user,
start_offset=0,
end_offset=5
)
def test_can_annotate_span_to_unannotated_data(self):
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertTrue(can_annotate)
class NonCollaborativeMixin:
def test_allow_another_user_to_annotate_same_span(self):
mommy.make(
'Span',
example=self.example,
label=self.label_type,
user=self.another_user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertTrue(can_annotate)
class TestNonOverlappingSpanAnnotation(TestSpanAnnotation, NonCollaborativeMixin):
overlapping = False
collaborative = False
def test_cannot_annotate_same_span_to_annotated_data(self):
mommy.make(
'Span',
example=self.example,
label=self.label_type,
user=self.user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset,
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertFalse(can_annotate)
def test_cannot_annotate_different_span_type_to_annotated_data(self):
mommy.make(
'Span',
example=self.example,
user=self.user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertFalse(can_annotate)
class TestOverlappingSpanAnnotation(TestSpanAnnotation, NonCollaborativeMixin):
overlapping = True
collaborative = False
def test_can_annotate_same_span_to_annotated_data(self):
mommy.make(
'Span',
example=self.example,
label=self.label_type,
user=self.user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset,
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertTrue(can_annotate)
class TestCollaborativeNonOverlappingSpanAnnotation(TestSpanAnnotation):
overlapping = False
collaborative = True
def test_deny_another_user_to_annotate_same_span_type(self):
mommy.make(
'Span',
example=self.example,
label=self.label_type,
user=self.another_user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertFalse(can_annotate)
def test_deny_another_user_to_annotate_different_span_type(self):
mommy.make(
'Span',
example=self.example,
user=self.another_user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertFalse(can_annotate)
class TestCollaborativeOverlappingSpanAnnotation(TestSpanAnnotation):
overlapping = True
collaborative = True
def test_allow_another_user_to_annotate_same_span(self):
mommy.make(
'Span',
example=self.example,
label=self.label_type,
user=self.another_user,
start_offset=self.span.start_offset,
end_offset=self.span.end_offset
)
can_annotate = Span.objects.can_annotate(self.span, self.project.item)
self.assertTrue(can_annotate)
Loading…
Cancel
Save