mirror of https://github.com/doccano/doccano.git
Browse Source
Merge pull request #1656 from doccano/enhancement/separateExampleApp
Merge pull request #1656 from doccano/enhancement/separateExampleApp
[Enhancement] Separate example apppull/1657/head
Hiroki Nakayama
2 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 439 additions and 212 deletions
Split View
Diff Options
-
18backend/api/admin.py
-
46backend/api/migrations/0034_auto_20220128_0246.py
-
34backend/api/migrations/0035_auto_20220128_0246.py
-
79backend/api/models.py
-
9backend/api/permissions.py
-
50backend/api/serializers.py
-
27backend/api/urls.py
-
1backend/app/settings.py
-
1backend/app/urls.py
-
3backend/auto_labeling/pipeline/labels.py
-
3backend/data_export/pipeline/repositories.py
-
3backend/data_import/pipeline/data.py
-
3backend/data_import/pipeline/writers.py
-
3backend/data_import/tests/test_tasks.py
-
0backend/examples/__init__.py
-
19backend/examples/admin.py
-
6backend/examples/apps.py
-
0backend/examples/filters.py
-
0backend/examples/managers.py
-
79backend/examples/migrations/0001_initial.py
-
0backend/examples/migrations/__init__.py
-
83backend/examples/models.py
-
10backend/examples/permissions.py
-
50backend/examples/serializers.py
-
0backend/examples/tests/__init__.py
-
3backend/examples/tests/test_comment.py
-
5backend/examples/tests/test_document.py
-
3backend/examples/tests/test_example_state.py
-
7backend/examples/tests/test_filters.py
-
6backend/examples/tests/test_models.py
-
34backend/examples/urls.py
-
0backend/examples/views/__init__.py
-
7backend/examples/views/comment.py
-
8backend/examples/views/example.py
-
6backend/examples/views/example_state.py
-
38backend/labels/migrations/0004_auto_20220128_0246.py
-
3backend/labels/models.py
-
2backend/labels/serializers.py
-
2backend/metrics/views.py
@ -0,0 +1,46 @@ |
|||
# Generated by Django 3.2.11 on 2022-01-28 02:46 |
|||
|
|||
from django.db import migrations |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('api', '0033_auto_20220127_0654'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.SeparateDatabaseAndState( |
|||
state_operations=[ |
|||
migrations.RemoveField( |
|||
model_name='example', |
|||
name='annotations_approved_by', |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='example', |
|||
name='project', |
|||
), |
|||
migrations.AlterUniqueTogether( |
|||
name='examplestate', |
|||
unique_together=None, |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='examplestate', |
|||
name='confirmed_by', |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='examplestate', |
|||
name='example', |
|||
), |
|||
migrations.DeleteModel( |
|||
name='Comment', |
|||
), |
|||
], |
|||
database_operations=[ |
|||
migrations.AlterModelTable( |
|||
name='Comment', |
|||
table='examples_comment' |
|||
) |
|||
] |
|||
) |
|||
] |
@ -0,0 +1,34 @@ |
|||
# Generated by Django 3.2.11 on 2022-01-28 02:46 |
|||
|
|||
from django.db import migrations |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('api', '0034_auto_20220128_0246'), |
|||
('labels', '0004_auto_20220128_0246'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.SeparateDatabaseAndState( |
|||
state_operations=[ |
|||
migrations.DeleteModel( |
|||
name='Example', |
|||
), |
|||
migrations.DeleteModel( |
|||
name='ExampleState', |
|||
), |
|||
], |
|||
database_operations=[ |
|||
migrations.AlterModelTable( |
|||
name='Example', |
|||
table='examples_example' |
|||
), |
|||
migrations.AlterModelTable( |
|||
name='ExampleState', |
|||
table='examples_examplestate' |
|||
) |
|||
] |
|||
) |
|||
] |
@ -0,0 +1,19 @@ |
|||
from django.contrib import admin |
|||
|
|||
from .models import Example, Comment |
|||
|
|||
|
|||
class ExampleAdmin(admin.ModelAdmin): |
|||
list_display = ('text', 'project', 'meta') |
|||
ordering = ('project',) |
|||
search_fields = ('text',) |
|||
|
|||
|
|||
class CommentAdmin(admin.ModelAdmin): |
|||
list_display = ('user', 'example', 'text', 'created_at', ) |
|||
ordering = ('user', 'created_at', ) |
|||
search_fields = ('user',) |
|||
|
|||
|
|||
admin.site.register(Example, ExampleAdmin) |
|||
admin.site.register(Comment, CommentAdmin) |
@ -0,0 +1,6 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class ExamplesConfig(AppConfig): |
|||
default_auto_field = 'django.db.models.BigAutoField' |
|||
name = 'examples' |
@ -0,0 +1,79 @@ |
|||
# Generated by Django 3.2.11 on 2022-01-28 02:46 |
|||
|
|||
from django.conf import settings |
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
import uuid |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
initial = True |
|||
|
|||
dependencies = [ |
|||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
|||
('api', '0034_auto_20220128_0246'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.SeparateDatabaseAndState( |
|||
state_operations=[ |
|||
migrations.CreateModel( |
|||
name='Example', |
|||
fields=[ |
|||
('id', |
|||
models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, unique=True)), |
|||
('meta', models.JSONField(default=dict)), |
|||
('filename', models.FileField(default='.', max_length=1024, upload_to='')), |
|||
('text', models.TextField(blank=True, null=True)), |
|||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), |
|||
('updated_at', models.DateTimeField(auto_now=True)), |
|||
('annotations_approved_by', |
|||
models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, |
|||
to=settings.AUTH_USER_MODEL)), |
|||
('project', |
|||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='examples', |
|||
to='api.project')), |
|||
], |
|||
options={ |
|||
'ordering': ['created_at'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Comment', |
|||
fields=[ |
|||
('id', |
|||
models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('text', models.TextField()), |
|||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), |
|||
('updated_at', models.DateTimeField(auto_now=True)), |
|||
('example', |
|||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', |
|||
to='examples.example')), |
|||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, |
|||
to=settings.AUTH_USER_MODEL)), |
|||
], |
|||
options={ |
|||
'ordering': ['created_at'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='ExampleState', |
|||
fields=[ |
|||
('id', |
|||
models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('confirmed_at', models.DateTimeField(auto_now=True)), |
|||
('confirmed_by', |
|||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
|||
('example', |
|||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='states', |
|||
to='examples.example')), |
|||
], |
|||
options={ |
|||
'unique_together': {('example', 'confirmed_by')}, |
|||
}, |
|||
), |
|||
] |
|||
) |
|||
] |
@ -0,0 +1,83 @@ |
|||
import uuid |
|||
|
|||
from django.contrib.auth.models import User |
|||
from django.db import models |
|||
|
|||
from api.models import Project |
|||
from .managers import ExampleManager, ExampleStateManager |
|||
|
|||
|
|||
class Example(models.Model): |
|||
objects = ExampleManager() |
|||
|
|||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True, unique=True) |
|||
meta = models.JSONField(default=dict) |
|||
filename = models.FileField(default='.', max_length=1024) |
|||
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 |
|||
) |
|||
text = models.TextField(null=True, blank=True) |
|||
created_at = models.DateTimeField(auto_now_add=True, db_index=True) |
|||
updated_at = models.DateTimeField(auto_now=True) |
|||
|
|||
@property |
|||
def comment_count(self): |
|||
return Comment.objects.filter(example=self.id).count() |
|||
|
|||
@property |
|||
def data(self): |
|||
if self.project.is_text_project: |
|||
return self.text |
|||
else: |
|||
return str(self.filename) |
|||
|
|||
class Meta: |
|||
ordering = ['created_at'] |
|||
|
|||
|
|||
class ExampleState(models.Model): |
|||
objects = ExampleStateManager() |
|||
example = models.ForeignKey( |
|||
to=Example, |
|||
on_delete=models.CASCADE, |
|||
related_name='states' |
|||
) |
|||
confirmed_by = models.ForeignKey( |
|||
to=User, |
|||
on_delete=models.CASCADE |
|||
) |
|||
confirmed_at = models.DateTimeField(auto_now=True) |
|||
|
|||
class Meta: |
|||
unique_together = (('example', 'confirmed_by'),) |
|||
|
|||
|
|||
class Comment(models.Model): |
|||
text = models.TextField() |
|||
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, db_index=True) |
|||
updated_at = models.DateTimeField(auto_now=True) |
|||
|
|||
@property |
|||
def username(self): |
|||
return self.user.username |
|||
|
|||
class Meta: |
|||
ordering = ['created_at'] |
@ -0,0 +1,10 @@ |
|||
from rest_framework.permissions import BasePermission |
|||
|
|||
|
|||
class IsOwnComment(BasePermission): |
|||
@classmethod |
|||
def has_object_permission(cls, request, view, obj): |
|||
if request.user.is_superuser: |
|||
return True |
|||
|
|||
return obj.user.id == request.user.id |
@ -0,0 +1,50 @@ |
|||
from rest_framework import serializers |
|||
|
|||
from .models import Example, ExampleState, Comment |
|||
|
|||
|
|||
class CommentSerializer(serializers.ModelSerializer): |
|||
|
|||
class Meta: |
|||
model = Comment |
|||
fields = ('id', 'user', 'username', 'example', 'text', 'created_at', ) |
|||
read_only_fields = ('user', 'example') |
|||
|
|||
|
|||
class ExampleSerializer(serializers.ModelSerializer): |
|||
annotation_approver = serializers.SerializerMethodField() |
|||
is_confirmed = serializers.SerializerMethodField() |
|||
|
|||
@classmethod |
|||
def get_annotation_approver(cls, instance): |
|||
approver = instance.annotations_approved_by |
|||
return approver.username if approver else None |
|||
|
|||
def get_is_confirmed(self, instance): |
|||
user = self.context.get('request').user |
|||
if instance.project.collaborative_annotation: |
|||
states = instance.states.all() |
|||
else: |
|||
states = instance.states.filter(confirmed_by_id=user.id) |
|||
return states.count() > 0 |
|||
|
|||
class Meta: |
|||
model = Example |
|||
fields = [ |
|||
'id', |
|||
'filename', |
|||
'meta', |
|||
'annotation_approver', |
|||
'comment_count', |
|||
'text', |
|||
'is_confirmed' |
|||
] |
|||
read_only_fields = ['filename', 'is_confirmed'] |
|||
|
|||
|
|||
class ExampleStateSerializer(serializers.ModelSerializer): |
|||
|
|||
class Meta: |
|||
model = ExampleState |
|||
fields = ('id', 'example', 'confirmed_by') |
|||
read_only_fields = ('id', 'example', 'confirmed_by') |
@ -1,8 +1,7 @@ |
|||
from rest_framework import status |
|||
from rest_framework.reverse import reverse |
|||
|
|||
from .utils import (CRUDMixin, make_comment, make_doc, make_user, |
|||
prepare_project) |
|||
from api.tests.api.utils import (CRUDMixin, make_comment, make_doc, make_user, prepare_project) |
|||
|
|||
|
|||
class TestCommentListDocAPI(CRUDMixin): |
@ -1,8 +1,7 @@ |
|||
from rest_framework import status |
|||
from rest_framework.reverse import reverse |
|||
|
|||
from .utils import (CRUDMixin, make_doc, make_example_state, make_user, |
|||
prepare_project) |
|||
from api.tests.api.utils import (CRUDMixin, make_doc, make_example_state, make_user, prepare_project) |
|||
|
|||
|
|||
class TestExampleStateList(CRUDMixin): |
@ -1,9 +1,9 @@ |
|||
from django.test import TestCase |
|||
from model_mommy import mommy |
|||
|
|||
from api.models import IMAGE_CLASSIFICATION, SEQUENCE_LABELING, ExampleState |
|||
|
|||
from .api.utils import prepare_project |
|||
from api.models import IMAGE_CLASSIFICATION, SEQUENCE_LABELING |
|||
from api.tests.api.utils import prepare_project |
|||
from examples.models import ExampleState |
|||
|
|||
|
|||
class TestExampleState(TestCase): |
@ -0,0 +1,34 @@ |
|||
from django.urls import path |
|||
|
|||
from .views.example import ExampleList, ExampleDetail |
|||
from .views.comment import CommentList, CommentDetail |
|||
from .views.example_state import ExampleStateList |
|||
|
|||
|
|||
urlpatterns = [ |
|||
path( |
|||
route='examples', |
|||
view=ExampleList.as_view(), |
|||
name='example_list' |
|||
), |
|||
path( |
|||
route='examples/<int:example_id>', |
|||
view=ExampleDetail.as_view(), |
|||
name='example_detail' |
|||
), |
|||
path( |
|||
route='comments', |
|||
view=CommentList.as_view(), |
|||
name='comment_list' |
|||
), |
|||
path( |
|||
route='comments/<int:comment_id>', |
|||
view=CommentDetail.as_view(), |
|||
name='comment_detail' |
|||
), |
|||
path( |
|||
route='examples/<int:example_id>/states', |
|||
view=ExampleStateList.as_view(), |
|||
name='example_state_list' |
|||
), |
|||
] |
@ -0,0 +1,38 @@ |
|||
# Generated by Django 3.2.11 on 2022-01-28 02:46 |
|||
|
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('examples', '0001_initial'), |
|||
('labels', '0003_auto_20220127_0654'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.SeparateDatabaseAndState( |
|||
state_operations=[ |
|||
migrations.AlterField( |
|||
model_name='category', |
|||
name='example', |
|||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='categories', |
|||
to='examples.example'), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='span', |
|||
name='example', |
|||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='spans', |
|||
to='examples.example'), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='textlabel', |
|||
name='example', |
|||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='texts', |
|||
to='examples.example'), |
|||
), |
|||
], |
|||
database_operations=[] |
|||
) |
|||
] |
Write
Preview
Loading…
Cancel
Save