Skip to content

Commit

Permalink
Merge pull request #1655 from doccano/enhancement/separateLabelType
Browse files Browse the repository at this point in the history
[Enhancement] Separate label type app
  • Loading branch information
Hironsan committed Jan 27, 2022
2 parents 188b085 + b3a20ca commit da608cf
Show file tree
Hide file tree
Showing 34 changed files with 623 additions and 355 deletions.
21 changes: 2 additions & 19 deletions backend/api/admin.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
from django.contrib import admin

from .models import (CategoryType, Comment, Example, Project, Seq2seqProject,
SequenceLabelingProject, SpanType, Tag,
TextClassificationProject)


class LabelAdmin(admin.ModelAdmin):
list_display = ('text', 'project', 'text_color', 'background_color')
ordering = ('project',)
search_fields = ('text',)


class CategoryTypeAdmin(LabelAdmin):
pass


class SpanTypeAdmin(LabelAdmin):
pass
from .models import (Comment, Example, Project, Seq2seqProject,
SequenceLabelingProject, Tag, TextClassificationProject)


class ExampleAdmin(admin.ModelAdmin):
Expand All @@ -43,8 +28,6 @@ class CommentAdmin(admin.ModelAdmin):
search_fields = ('user',)


admin.site.register(CategoryType, CategoryTypeAdmin)
admin.site.register(SpanType, SpanTypeAdmin)
admin.site.register(Example, ExampleAdmin)
admin.site.register(Project, ProjectAdmin)
admin.site.register(TextClassificationProject, ProjectAdmin)
Expand Down
5 changes: 3 additions & 2 deletions backend/api/migrations/0018_alter_label_background_color.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Generated by Django 3.2.8 on 2021-11-17 05:56

import api.models
from django.db import migrations, models

from label_types.models import generate_random_hex_color


class Migration(migrations.Migration):

Expand All @@ -14,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='label',
name='background_color',
field=models.CharField(default=api.models.generate_random_hex_color, max_length=7),
field=models.CharField(default=generate_random_hex_color, max_length=7),
),
]
7 changes: 4 additions & 3 deletions backend/api/migrations/0020_auto_20211221_1415.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Generated by Django 3.2.8 on 2021-12-21 14:15

import api.models
from django.db import migrations, models
import django.db.models.deletion

from label_types.models import generate_random_hex_color


class Migration(migrations.Migration):

Expand All @@ -19,7 +20,7 @@ class Migration(migrations.Migration):
('text', models.CharField(db_index=True, max_length=100)),
('prefix_key', models.CharField(blank=True, choices=[('ctrl', 'ctrl'), ('shift', 'shift'), ('ctrl shift', 'ctrl shift')], max_length=10, null=True)),
('suffix_key', models.CharField(blank=True, choices=[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8'), ('9', '9'), ('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('e', 'e'), ('f', 'f'), ('g', 'g'), ('h', 'h'), ('i', 'i'), ('j', 'j'), ('k', 'k'), ('l', 'l'), ('m', 'm'), ('n', 'n'), ('o', 'o'), ('p', 'p'), ('q', 'q'), ('r', 'r'), ('s', 's'), ('t', 't'), ('u', 'u'), ('v', 'v'), ('w', 'w'), ('x', 'x'), ('y', 'y'), ('z', 'z')], max_length=1, null=True)),
('background_color', models.CharField(default=api.models.generate_random_hex_color, max_length=7)),
('background_color', models.CharField(default=generate_random_hex_color, max_length=7)),
('text_color', models.CharField(default='#ffffff', max_length=7)),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
('updated_at', models.DateTimeField(auto_now=True)),
Expand All @@ -36,7 +37,7 @@ class Migration(migrations.Migration):
('text', models.CharField(db_index=True, max_length=100)),
('prefix_key', models.CharField(blank=True, choices=[('ctrl', 'ctrl'), ('shift', 'shift'), ('ctrl shift', 'ctrl shift')], max_length=10, null=True)),
('suffix_key', models.CharField(blank=True, choices=[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8'), ('9', '9'), ('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('e', 'e'), ('f', 'f'), ('g', 'g'), ('h', 'h'), ('i', 'i'), ('j', 'j'), ('k', 'k'), ('l', 'l'), ('m', 'm'), ('n', 'n'), ('o', 'o'), ('p', 'p'), ('q', 'q'), ('r', 'r'), ('s', 's'), ('t', 't'), ('u', 'u'), ('v', 'v'), ('w', 'w'), ('x', 'x'), ('y', 'y'), ('z', 'z')], max_length=1, null=True)),
('background_color', models.CharField(default=api.models.generate_random_hex_color, max_length=7)),
('background_color', models.CharField(default=generate_random_hex_color, max_length=7)),
('text_color', models.CharField(default='#ffffff', max_length=7)),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
('updated_at', models.DateTimeField(auto_now=True)),
Expand Down
34 changes: 34 additions & 0 deletions backend/api/migrations/0032_auto_20220127_0654.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 3.2.11 on 2022-01-27 06:54

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0031_auto_20220127_0032'),
]

operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(
model_name='categorytype',
name='project',
),
migrations.AlterUniqueTogether(
name='relationtypes',
unique_together=None,
),
migrations.RemoveField(
model_name='relationtypes',
name='project',
),
migrations.RemoveField(
model_name='spantype',
name='project',
),
],
database_operations=[]
)
]
41 changes: 41 additions & 0 deletions backend/api/migrations/0033_auto_20220127_0654.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 3.2.11 on 2022-01-27 06:54

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('labels', '0003_auto_20220127_0654'),
('api', '0032_auto_20220127_0654'),
]

operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.DeleteModel(
name='CategoryType',
),
migrations.DeleteModel(
name='RelationTypes',
),
migrations.DeleteModel(
name='SpanType',
),
],
database_operations=[
migrations.AlterModelTable(
name='CategoryType',
table='label_types_categorytype'
),
migrations.AlterModelTable(
name='RelationTypes',
table='label_types_relationtypes'
),
migrations.AlterModelTable(
name='SpanType',
table='label_types_spanType'
)
]
)
]
96 changes: 0 additions & 96 deletions backend/api/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import abc
import random
import string
import uuid

from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models
from polymorphic.models import PolymorphicModel

Expand Down Expand Up @@ -151,87 +148,6 @@ def can_define_category(self) -> bool:
return True


def generate_random_hex_color():
return f'#{random.randint(0, 0xFFFFFF):06x}'


class Label(models.Model):
text = models.CharField(max_length=100, db_index=True)
prefix_key = models.CharField(
max_length=10,
blank=True,
null=True,
choices=(
('ctrl', 'ctrl'),
('shift', 'shift'),
('ctrl shift', 'ctrl shift')
)
)
suffix_key = models.CharField(
max_length=1,
blank=True,
null=True,
choices=tuple(
(c, c) for c in string.digits + string.ascii_lowercase
)
)
project = models.ForeignKey(
to=Project,
on_delete=models.CASCADE,
# related_name='labels'
)
background_color = models.CharField(max_length=7, default=generate_random_hex_color)
text_color = models.CharField(max_length=7, default='#ffffff')
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.text

@property
def labels(self):
raise NotImplementedError()

def clean(self):
# Don't allow shortcut key not to have a suffix key.
if self.prefix_key and not self.suffix_key:
message = 'Shortcut key may not have a suffix key.'
raise ValidationError(message)

# each shortcut (prefix key + suffix key) can only be assigned to one label
if self.suffix_key or self.prefix_key:
other_labels = self.labels.exclude(id=self.id)
if other_labels.filter(suffix_key=self.suffix_key, prefix_key=self.prefix_key).exists():
message = 'A label with the shortcut already exists in the project.'
raise ValidationError(message)

super().clean()

class Meta:
abstract = True
constraints = [
models.UniqueConstraint(
fields=['project', 'text'],
name='%(app_label)s_%(class)s_is_unique'
)
]
ordering = ['created_at']


class CategoryType(Label):

@property
def labels(self):
return CategoryType.objects.filter(project=self.project)


class SpanType(Label):

@property
def labels(self):
return SpanType.objects.filter(project=self.project)


class Example(models.Model):
objects = ExampleManager()

Expand Down Expand Up @@ -318,15 +234,3 @@ class Tag(models.Model):

def __str__(self):
return self.text


class RelationTypes(models.Model):
color = models.TextField()
name = models.TextField()
project = models.ForeignKey(Project, related_name='relation_types', on_delete=models.CASCADE)

def __str__(self):
return self.name

class Meta:
unique_together = ('color', 'name')
101 changes: 4 additions & 97 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,11 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_polymorphic.serializers import PolymorphicSerializer

from .models import (CategoryType, Comment, Example, ExampleState,
from .models import (Comment, Example, ExampleState,
ImageClassificationProject,
IntentDetectionAndSlotFillingProject, Label, Project,
RelationTypes, Seq2seqProject, SequenceLabelingProject,
SpanType, Speech2textProject, Tag,
TextClassificationProject)


class LabelSerializer(serializers.ModelSerializer):

def validate(self, attrs):
prefix_key = attrs.get('prefix_key')
suffix_key = attrs.get('suffix_key')

# In the case of user don't set any shortcut key.
if prefix_key is None and suffix_key is None:
return super().validate(attrs)

# Don't allow shortcut key not to have a suffix key.
if prefix_key and not suffix_key:
raise ValidationError('Shortcut key may not have a suffix key.')

# Don't allow to save same shortcut key when prefix_key is null.
try:
context = self.context['request'].parser_context
project_id = context['kwargs']['project_id']
label_id = context['kwargs'].get('label_id')
except (AttributeError, KeyError):
pass # unit tests don't always have the correct context set up
else:
conflicting_labels = self.Meta.model.objects.filter(
suffix_key=suffix_key,
prefix_key=prefix_key,
project=project_id,
)

if label_id is not None:
conflicting_labels = conflicting_labels.exclude(id=label_id)

if conflicting_labels.exists():
raise ValidationError('Duplicate shortcut key.')

return super().validate(attrs)

class Meta:
model = Label
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)


class CategoryTypeSerializer(LabelSerializer):
class Meta:
model = CategoryType
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)


class SpanTypeSerializer(LabelSerializer):
class Meta:
model = SpanType
fields = (
'id',
'text',
'prefix_key',
'suffix_key',
'background_color',
'text_color',
)
IntentDetectionAndSlotFillingProject, Project,
Seq2seqProject, SequenceLabelingProject,
Speech2textProject, Tag, TextClassificationProject)


class CommentSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -139,13 +63,6 @@ class Meta:
read_only_fields = ('id', 'example', 'confirmed_by')


class ApproverSerializer(ExampleSerializer):

class Meta:
model = Example
fields = ('id', 'annotation_approver')


class ProjectSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, required=False)

Expand Down Expand Up @@ -224,13 +141,3 @@ class ProjectPolymorphicSerializer(PolymorphicSerializer):
cls.Meta.model: cls for cls in ProjectSerializer.__subclasses__()
}
}


class RelationTypesSerializer(serializers.ModelSerializer):

def validate(self, attrs):
return super().validate(attrs)

class Meta:
model = RelationTypes
fields = ('id', 'color', 'name')

0 comments on commit da608cf

Please sign in to comment.