Browse Source

Divide Label model into two separated model

pull/1619/head
Hironsan 2 years ago
parent
commit
b4d5fa34e6
11 changed files with 59 additions and 112 deletions
  1. 36
      backend/api/migrations/0020_auto_20211220_2327.py
  2. 18
      backend/api/migrations/0020_label_task_type.py
  3. 36
      backend/api/migrations/0021_auto_20211209_0644.py
  4. 21
      backend/api/migrations/0022_auto_20211210_0052.py
  5. 19
      backend/api/models.py
  6. 1
      backend/api/serializers.py
  7. 9
      backend/api/tests/api/test_label.py
  8. 10
      backend/api/tests/test_models.py
  9. 1
      backend/api/views/label.py
  10. 18
      backend/api/views/upload/label.py
  11. 2
      backend/api/views/upload/writers.py

36
backend/api/migrations/0020_auto_20211220_2327.py

@ -0,0 +1,36 @@
# Generated by Django 3.2.8 on 2021-12-20 23:27
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('api', '0019_auto_20211124_0506'),
]
operations = [
migrations.CreateModel(
name='DocType',
fields=[
('label_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.label')),
],
bases=('api.label',),
),
migrations.CreateModel(
name='SpanType',
fields=[
('label_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.label')),
],
bases=('api.label',),
),
migrations.AlterUniqueTogether(
name='label',
unique_together=set(),
),
migrations.AddConstraint(
model_name='label',
constraint=models.UniqueConstraint(fields=('project', 'text'), name='unique_label'),
),
]

18
backend/api/migrations/0020_label_task_type.py

@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-12-09 06:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0019_auto_20211124_0506'),
]
operations = [
migrations.AddField(
model_name='label',
name='task_type',
field=models.CharField(choices=[('Category', 'Category'), ('Span', 'Span'), ('Relation', 'Relation')], default='Category', max_length=30),
),
]

36
backend/api/migrations/0021_auto_20211209_0644.py

@ -1,36 +0,0 @@
from django.db import migrations
def update_label_type(apps, schema_editor):
Label = apps.get_model('api', 'Label')
for label in Label.objects.all():
project_type = label.project.project_type
if project_type.endswith('Classification'):
label.task_type = 'Category'
else:
label.task_type = 'Span'
label.save()
# def move_relation_type_to_label(apps, schema_editor):
# Label = apps.get_model('api', 'Label')
# RelationTypes = apps.get_model('api', 'RelationTypes')
# for relation_type in RelationTypes.objects.all():
# Label.objects.create(
# text=relation_type.name,
# project=relation_type.project,
# background_color=relation_type.color,
# task_type='Relation'
# )
class Migration(migrations.Migration):
dependencies = [
('api', '0020_label_task_type'),
]
operations = [
migrations.RunPython(update_label_type),
# migrations.RunPython(move_relation_type_to_label),
]

21
backend/api/migrations/0022_auto_20211210_0052.py

@ -1,21 +0,0 @@
# Generated by Django 3.2.8 on 2021-12-10 00:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0021_auto_20211209_0644'),
]
operations = [
migrations.AlterUniqueTogether(
name='label',
unique_together=set(),
),
migrations.AddConstraint(
model_name='label',
constraint=models.UniqueConstraint(fields=('project', 'text', 'task_type'), name='unique_label'),
),
]

19
backend/api/models.py

@ -126,15 +126,6 @@ class Label(models.Model):
) )
background_color = models.CharField(max_length=7, default=generate_random_hex_color) background_color = models.CharField(max_length=7, default=generate_random_hex_color)
text_color = models.CharField(max_length=7, default='#ffffff') text_color = models.CharField(max_length=7, default='#ffffff')
task_type = models.CharField(
max_length=30,
choices=(
('Category', 'Category'),
('Span', 'Span'),
('Relation', 'Relation')
),
default='Category'
)
created_at = models.DateTimeField(auto_now_add=True, db_index=True) created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
@ -159,13 +150,21 @@ class Label(models.Model):
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint( models.UniqueConstraint(
fields=['project', 'text', 'task_type'],
fields=['project', 'text'],
name='unique_label' name='unique_label'
) )
] ]
ordering = ['created_at'] ordering = ['created_at']
class DocType(Label):
pass
class SpanType(Label):
pass
class Example(models.Model): class Example(models.Model):
objects = ExampleManager() objects = ExampleManager()

1
backend/api/serializers.py

@ -67,7 +67,6 @@ class LabelSerializer(serializers.ModelSerializer):
'suffix_key', 'suffix_key',
'background_color', 'background_color',
'text_color', 'text_color',
'task_type',
) )

9
backend/api/tests/api/test_label.py

@ -43,17 +43,10 @@ class TestLabelSearch(CRUDMixin):
def setUp(self): def setUp(self):
self.project = prepare_project() self.project = prepare_project()
make_label(self.project.item, task_type='Category')
make_label(self.project.item, task_type='Span')
make_label(self.project.item)
self.url = reverse(viewname='label_list', args=[self.project.item.id]) self.url = reverse(viewname='label_list', args=[self.project.item.id])
def test_search(self): def test_search(self):
for member in self.project.users:
response = self.assert_fetch(member, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
def test_search_label_by_type(self):
self.url = f'{self.url}?task_type=Category'
for member in self.project.users: for member in self.project.users:
response = self.assert_fetch(member, status.HTTP_200_OK) response = self.assert_fetch(member, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1) self.assertEqual(len(response.data), 1)

10
backend/api/tests/test_models.py

@ -58,14 +58,10 @@ class TestSpeech2textProject(TestCase):
class TestLabel(TestCase): class TestLabel(TestCase):
def test_allow_creating_same_text_different_type(self):
label = mommy.make('Label', task_type='Category')
mommy.make('Label', project=label.project, text=label.text, task_type='Span')
def test_deny_creating_same_text_same_type(self):
label = mommy.make('Label', task_type='Category')
def test_deny_creating_same_text(self):
label = mommy.make('Label')
with self.assertRaises(IntegrityError): with self.assertRaises(IntegrityError):
mommy.make('Label', project=label.project, text=label.text, task_type='Category')
mommy.make('Label', project=label.project, text=label.text)
def test_keys_uniqueness(self): def test_keys_uniqueness(self):
label = mommy.make('Label', prefix_key='ctrl', suffix_key='a') label = mommy.make('Label', prefix_key='ctrl', suffix_key='a')

1
backend/api/views/label.py

@ -28,7 +28,6 @@ def camel_to_snake_dict(d):
class LabelList(generics.ListCreateAPIView): class LabelList(generics.ListCreateAPIView):
filter_backends = [DjangoFilterBackend] filter_backends = [DjangoFilterBackend]
filterset_fields = ['task_type']
serializer_class = LabelSerializer serializer_class = LabelSerializer
pagination_class = None pagination_class = None
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin] permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]

18
backend/api/views/upload/label.py

@ -1,11 +1,11 @@
import abc import abc
from typing import Any, Optional, Union
from typing import Any, Dict, Optional, Union
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from ...models import Category
from ...models import Category, DocType
from ...models import Label as LabelModel from ...models import Label as LabelModel
from ...models import Project, Span
from ...models import Project, Span, SpanType
from ...models import TextLabel as TL from ...models import TextLabel as TL
@ -63,13 +63,13 @@ class CategoryLabel(Label):
raise TypeError(f'{obj} is not str.') raise TypeError(f'{obj} is not str.')
def create(self, project: Project) -> Optional[LabelModel]: def create(self, project: Project) -> Optional[LabelModel]:
return LabelModel(text=self.label, project=project, task_type='Category')
return DocType(text=self.label, project=project)
def create_annotation(self, user, example, mapping):
def create_annotation(self, user, example, mapping: Dict[str, LabelModel]):
return Category( return Category(
user=user, user=user,
example=example, example=example,
label=mapping[(self.label, 'Category')]
label=mapping[self.label]
) )
@ -97,15 +97,15 @@ class SpanLabel(Label):
raise TypeError(f'{obj} is invalid type.') raise TypeError(f'{obj} is invalid type.')
def create(self, project: Project) -> Optional[LabelModel]: def create(self, project: Project) -> Optional[LabelModel]:
return LabelModel(text=self.label, project=project, task_type='Span')
return SpanType(text=self.label, project=project)
def create_annotation(self, user, example, mapping):
def create_annotation(self, user, example, mapping: Dict[str, LabelModel]):
return Span( return Span(
user=user, user=user,
example=example, example=example,
start_offset=self.start_offset, start_offset=self.start_offset,
end_offset=self.end_offset, end_offset=self.end_offset,
label=mapping[(self.label, 'Span')]
label=mapping[self.label]
) )

2
backend/api/views/upload/writers.py

@ -64,7 +64,7 @@ class Examples:
return Example.objects.bulk_create(examples) return Example.objects.bulk_create(examples)
def save_annotation(self, project: Project, user, examples): def save_annotation(self, project: Project, user, examples):
mapping = {(label.text, label.task_type): label for label in project.labels.all()}
mapping = {label.text: label for label in project.labels.all()}
annotations = list(itertools.chain.from_iterable([ annotations = list(itertools.chain.from_iterable([
data.create_annotation(user, example, mapping) for data, example in zip(self.buffer, examples) data.create_annotation(user, example, mapping) for data, example in zip(self.buffer, examples)
])) ]))

Loading…
Cancel
Save