From 99f77b2bd2b2c5e0bac7a5a921225fb3b99c5871 Mon Sep 17 00:00:00 2001 From: Hironsan Date: Fri, 4 Feb 2022 15:08:39 +0900 Subject: [PATCH] Move project models to projects app --- backend/api/admin.py | 23 --- .../api/migrations/0036_auto_20220204_0201.py | 54 ++++++ .../api/migrations/0037_auto_20220204_0201.py | 81 +++++++++ backend/api/models.py | 157 ------------------ backend/api/serializers.py | 95 ----------- backend/api/tests/api/utils.py | 7 +- backend/api/urls.py | 28 +--- backend/app/urls.py | 2 +- .../migrations/0003_fill_task_type.py | 2 +- .../0004_alter_autolabelingconfig_project.py | 26 +++ backend/auto_labeling/models.py | 2 +- backend/auto_labeling/pipeline/labels.py | 2 +- backend/auto_labeling/tests/test_views.py | 2 +- backend/auto_labeling/views.py | 2 +- backend/data_export/celery_tasks.py | 2 +- backend/data_export/pipeline/catalog.py | 5 +- backend/data_export/pipeline/factories.py | 5 +- backend/data_export/pipeline/repositories.py | 2 +- .../data_export/tests/test_repositories.py | 2 +- backend/data_export/tests/test_views.py | 2 +- backend/data_export/views.py | 2 +- backend/data_import/celery_tasks.py | 2 +- backend/data_import/pipeline/catalog.py | 5 +- backend/data_import/pipeline/cleaners.py | 2 +- backend/data_import/pipeline/data.py | 2 +- backend/data_import/pipeline/factories.py | 5 +- backend/data_import/pipeline/labels.py | 2 +- backend/data_import/pipeline/writers.py | 2 +- backend/data_import/tests/test_tasks.py | 5 +- backend/data_import/tests/test_views.py | 2 +- backend/data_import/views.py | 3 +- .../migrations/0002_alter_example_project.py | 29 ++++ backend/examples/models.py | 2 +- backend/examples/tests/test_document.py | 2 +- backend/examples/tests/test_models.py | 2 +- backend/examples/views/example.py | 2 +- backend/examples/views/example_state.py | 2 +- .../migrations/0003_auto_20220204_0201.py | 36 ++++ backend/label_types/models.py | 2 +- backend/label_types/tests/test_views.py | 2 +- .../migrations/0005_alter_relation_project.py | 26 +++ backend/labels/models.py | 2 +- backend/labels/tests/test_category.py | 2 +- backend/labels/tests/test_span.py | 2 +- backend/labels/tests/test_text_label.py | 2 +- backend/labels/tests/test_views.py | 2 +- backend/labels/views.py | 2 +- backend/metrics/tests.py | 2 +- backend/projects/admin.py | 18 ++ backend/projects/apps.py | 2 +- .../management/commands/create_member.py | 2 +- .../migrations/0002_auto_20220204_0201.py | 155 +++++++++++++++++ .../migrations/0003_auto_20220204_0551.py | 49 ++++++ backend/projects/models.py | 157 +++++++++++++++++- backend/projects/serializers.py | 91 ++++++++++ backend/projects/signals.py | 2 +- backend/projects/tests/__init__.py | 0 .../{tests.py => tests/test_member.py} | 0 .../api => projects/tests}/test_project.py | 2 +- .../tests/api => projects/tests}/test_tag.py | 2 +- backend/projects/urls.py | 29 +++- .../projects/{views.py => views/member.py} | 8 +- backend/{api => projects}/views/project.py | 4 +- backend/{api => projects}/views/tag.py | 4 +- 64 files changed, 806 insertions(+), 371 deletions(-) delete mode 100644 backend/api/admin.py create mode 100644 backend/api/migrations/0036_auto_20220204_0201.py create mode 100644 backend/api/migrations/0037_auto_20220204_0201.py delete mode 100644 backend/api/models.py delete mode 100644 backend/api/serializers.py create mode 100644 backend/auto_labeling/migrations/0004_alter_autolabelingconfig_project.py create mode 100644 backend/examples/migrations/0002_alter_example_project.py create mode 100644 backend/label_types/migrations/0003_auto_20220204_0201.py create mode 100644 backend/labels/migrations/0005_alter_relation_project.py create mode 100644 backend/projects/migrations/0002_auto_20220204_0201.py create mode 100644 backend/projects/migrations/0003_auto_20220204_0551.py create mode 100644 backend/projects/tests/__init__.py rename backend/projects/{tests.py => tests/test_member.py} (100%) rename backend/{api/tests/api => projects/tests}/test_project.py (97%) rename backend/{api/tests/api => projects/tests}/test_tag.py (96%) rename backend/projects/{views.py => views/member.py} (89%) rename backend/{api => projects}/views/project.py (95%) rename backend/{api => projects}/views/tag.py (91%) diff --git a/backend/api/admin.py b/backend/api/admin.py deleted file mode 100644 index cee0ae18..00000000 --- a/backend/api/admin.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.contrib import admin - -from .models import (Project, Seq2seqProject, SequenceLabelingProject, Tag, - TextClassificationProject) - - -class ProjectAdmin(admin.ModelAdmin): - list_display = ('name', 'description', 'project_type', 'random_order', 'collaborative_annotation') - ordering = ('project_type',) - search_fields = ('name',) - - -class TagAdmin(admin.ModelAdmin): - list_display = ('project', 'text', ) - ordering = ('project', 'text', ) - search_fields = ('text',) - - -admin.site.register(Project, ProjectAdmin) -admin.site.register(TextClassificationProject, ProjectAdmin) -admin.site.register(SequenceLabelingProject, ProjectAdmin) -admin.site.register(Seq2seqProject, ProjectAdmin) -admin.site.register(Tag, TagAdmin) diff --git a/backend/api/migrations/0036_auto_20220204_0201.py b/backend/api/migrations/0036_auto_20220204_0201.py new file mode 100644 index 00000000..45732b7f --- /dev/null +++ b/backend/api/migrations/0036_auto_20220204_0201.py @@ -0,0 +1,54 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0035_auto_20220128_0246'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name='imageclassificationproject', + name='project_ptr', + ), + migrations.RemoveField( + model_name='intentdetectionandslotfillingproject', + name='project_ptr', + ), + migrations.RemoveField( + model_name='project', + name='created_by', + ), + migrations.RemoveField( + model_name='project', + name='polymorphic_ctype', + ), + migrations.RemoveField( + model_name='seq2seqproject', + name='project_ptr', + ), + migrations.RemoveField( + model_name='sequencelabelingproject', + name='project_ptr', + ), + migrations.RemoveField( + model_name='speech2textproject', + name='project_ptr', + ), + migrations.RemoveField( + model_name='tag', + name='project', + ), + migrations.RemoveField( + model_name='textclassificationproject', + name='project_ptr', + ), + ], + database_operations=[] + ) + ] diff --git a/backend/api/migrations/0037_auto_20220204_0201.py b/backend/api/migrations/0037_auto_20220204_0201.py new file mode 100644 index 00000000..2dedea61 --- /dev/null +++ b/backend/api/migrations/0037_auto_20220204_0201.py @@ -0,0 +1,81 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('examples', '0002_alter_example_project'), + ('labels', '0005_alter_relation_project'), + ('projects', '0002_auto_20220204_0201'), + ('auto_labeling', '0004_alter_autolabelingconfig_project'), + ('api', '0036_auto_20220204_0201'), + ('label_types', '0003_auto_20220204_0201'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name='ImageClassificationProject', + ), + migrations.DeleteModel( + name='IntentDetectionAndSlotFillingProject', + ), + migrations.DeleteModel( + name='Project', + ), + migrations.DeleteModel( + name='Seq2seqProject', + ), + migrations.DeleteModel( + name='SequenceLabelingProject', + ), + migrations.DeleteModel( + name='Speech2textProject', + ), + migrations.DeleteModel( + name='Tag', + ), + migrations.DeleteModel( + name='TextClassificationProject', + ), + ], + database_operations=[ + migrations.AlterModelTable( + name='ImageClassificationProject', + table='projects_imageclassificationproject' + ), + migrations.AlterModelTable( + name='IntentDetectionAndSlotFillingProject', + table='projects_intentdetectionandslotfillingproject' + ), + migrations.AlterModelTable( + name='Project', + table='projects_project' + ), + migrations.AlterModelTable( + name='Seq2seqProject', + table='projects_seq2seqproject' + ), + migrations.AlterModelTable( + name='SequenceLabelingProject', + table='projects_sequencelabelingproject' + ), + migrations.AlterModelTable( + name='Speech2textProject', + table='projects_speech2textproject' + ), + migrations.AlterModelTable( + name='Tag', + table='projects_tag' + ), + migrations.AlterModelTable( + name='TextClassificationProject', + table='projects_textclassificationproject' + ), + ] + ) + + ] diff --git a/backend/api/models.py b/backend/api/models.py deleted file mode 100644 index 760f1133..00000000 --- a/backend/api/models.py +++ /dev/null @@ -1,157 +0,0 @@ -import abc - -from django.contrib.auth.models import User -from django.db import models -from polymorphic.models import PolymorphicModel - -DOCUMENT_CLASSIFICATION = 'DocumentClassification' -SEQUENCE_LABELING = 'SequenceLabeling' -SEQ2SEQ = 'Seq2seq' -SPEECH2TEXT = 'Speech2text' -IMAGE_CLASSIFICATION = 'ImageClassification' -INTENT_DETECTION_AND_SLOT_FILLING = 'IntentDetectionAndSlotFilling' -PROJECT_CHOICES = ( - (DOCUMENT_CLASSIFICATION, 'document classification'), - (SEQUENCE_LABELING, 'sequence labeling'), - (SEQ2SEQ, 'sequence to sequence'), - (INTENT_DETECTION_AND_SLOT_FILLING, 'intent detection and slot filling'), - (SPEECH2TEXT, 'speech to text'), - (IMAGE_CLASSIFICATION, 'image classification') -) - - -class Project(PolymorphicModel): - name = models.CharField(max_length=100) - description = models.TextField(default='') - guideline = models.TextField(default='', blank=True) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - created_by = models.ForeignKey( - User, - on_delete=models.SET_NULL, - null=True, - ) - project_type = models.CharField(max_length=30, choices=PROJECT_CHOICES) - random_order = models.BooleanField(default=False) - collaborative_annotation = models.BooleanField(default=False) - single_class_classification = models.BooleanField(default=False) - - @property - @abc.abstractmethod - def is_text_project(self) -> bool: - return False - - @property - def can_define_label(self) -> bool: - """Whether or not the project can define label(ignoring the type of label)""" - return False - - @property - def can_define_relation(self) -> bool: - """Whether or not the project can define relation.""" - return False - - @property - def can_define_category(self) -> bool: - """Whether or not the project can define category.""" - return False - - @property - def can_define_span(self) -> bool: - """Whether or not the project can define span.""" - return False - - def __str__(self): - return self.name - - -class TextClassificationProject(Project): - - @property - def is_text_project(self) -> bool: - return True - - @property - def can_define_label(self) -> bool: - return True - - @property - def can_define_category(self) -> bool: - return True - - -class SequenceLabelingProject(Project): - allow_overlapping = models.BooleanField(default=False) - grapheme_mode = models.BooleanField(default=False) - - @property - def is_text_project(self) -> bool: - return True - - @property - def can_define_label(self) -> bool: - return True - - @property - def can_define_span(self) -> bool: - return True - - -class Seq2seqProject(Project): - - @property - def is_text_project(self) -> bool: - return True - - -class IntentDetectionAndSlotFillingProject(Project): - - @property - def is_text_project(self) -> bool: - return True - - @property - def can_define_label(self) -> bool: - return True - - @property - def can_define_category(self) -> bool: - return True - - @property - def can_define_span(self) -> bool: - return True - - -class Speech2textProject(Project): - - @property - def is_text_project(self) -> bool: - return False - - -class ImageClassificationProject(Project): - - @property - def is_text_project(self) -> bool: - return False - - @property - def can_define_label(self) -> bool: - return True - - @property - def can_define_category(self) -> bool: - return True - - -class Tag(models.Model): - text = models.TextField() - project = models.ForeignKey( - to=Project, - on_delete=models.CASCADE, - related_name='tags' - ) - - def __str__(self): - return self.text diff --git a/backend/api/serializers.py b/backend/api/serializers.py deleted file mode 100644 index 7e112b85..00000000 --- a/backend/api/serializers.py +++ /dev/null @@ -1,95 +0,0 @@ -from rest_framework import serializers -from rest_polymorphic.serializers import PolymorphicSerializer - -from .models import (ImageClassificationProject, - IntentDetectionAndSlotFillingProject, Project, - Seq2seqProject, SequenceLabelingProject, - Speech2textProject, Tag, TextClassificationProject) - - -class TagSerializer(serializers.ModelSerializer): - - class Meta: - model = Tag - fields = ('id', 'project', 'text', ) - read_only_fields = ('id', 'project') - - -class ProjectSerializer(serializers.ModelSerializer): - tags = TagSerializer(many=True, required=False) - - class Meta: - model = Project - fields = ( - 'id', - 'name', - 'description', - 'guideline', - 'project_type', - 'updated_at', - 'random_order', - 'created_by', - 'collaborative_annotation', - 'single_class_classification', - 'is_text_project', - 'can_define_label', - 'can_define_relation', - 'can_define_category', - 'can_define_span', - 'tags' - ) - read_only_fields = ( - 'updated_at', - 'is_text_project', - 'can_define_label', - 'can_define_relation', - 'can_define_category', - 'can_define_span', - 'tags' - ) - - -class TextClassificationProjectSerializer(ProjectSerializer): - - class Meta(ProjectSerializer.Meta): - model = TextClassificationProject - - -class SequenceLabelingProjectSerializer(ProjectSerializer): - - class Meta(ProjectSerializer.Meta): - model = SequenceLabelingProject - fields = ProjectSerializer.Meta.fields + ('allow_overlapping', 'grapheme_mode') - - -class Seq2seqProjectSerializer(ProjectSerializer): - - class Meta(ProjectSerializer.Meta): - model = Seq2seqProject - - -class IntentDetectionAndSlotFillingProjectSerializer(ProjectSerializer): - - class Meta(ProjectSerializer.Meta): - model = IntentDetectionAndSlotFillingProject - - -class Speech2textProjectSerializer(ProjectSerializer): - - class Meta(ProjectSerializer.Meta): - model = Speech2textProject - - -class ImageClassificationProjectSerializer(ProjectSerializer): - - class Meta(ProjectSerializer.Meta): - model = ImageClassificationProject - - -class ProjectPolymorphicSerializer(PolymorphicSerializer): - model_serializer_mapping = { - Project: ProjectSerializer, - **{ - cls.Meta.model: cls for cls in ProjectSerializer.__subclasses__() - } - } diff --git a/backend/api/tests/api/utils.py b/backend/api/tests/api/utils.py index 71953df1..1242b455 100644 --- a/backend/api/tests/api/utils.py +++ b/backend/api/tests/api/utils.py @@ -7,10 +7,9 @@ from model_mommy import mommy from rest_framework import status from rest_framework.test import APITestCase -from api.models import (DOCUMENT_CLASSIFICATION, IMAGE_CLASSIFICATION, - INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, - SEQUENCE_LABELING, SPEECH2TEXT) -from projects.models import Member +from projects.models import (DOCUMENT_CLASSIFICATION, IMAGE_CLASSIFICATION, + INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, + SEQUENCE_LABELING, SPEECH2TEXT, Member) from roles.models import Role DATA_DIR = os.path.join(os.path.dirname(__file__), '../../../data_import/tests/data') diff --git a/backend/api/urls.py b/backend/api/urls.py index fd96c095..7eca385e 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -1,35 +1,11 @@ -from django.urls import include, path +from django.urls import path -from .views import project, tag, task - -urlpatterns_project = [ - path( - route='tags', - view=tag.TagList.as_view(), - name='tag_list' - ), - path( - route='tags/', - view=tag.TagDetail.as_view(), - name='tag_detail' - ), -] +from .views import task urlpatterns = [ - path( - route='projects', - view=project.ProjectList.as_view(), - name='project_list' - ), path( route='tasks/status/', view=task.TaskStatus.as_view(), name='task_status' ), - path( - route='projects/', - view=project.ProjectDetail.as_view(), - name='project_detail' - ), - path('projects//', include(urlpatterns_project)) ] diff --git a/backend/app/urls.py b/backend/app/urls.py index 44ad8a35..0fb38646 100644 --- a/backend/app/urls.py +++ b/backend/app/urls.py @@ -55,7 +55,7 @@ urlpatterns += [ path('v1/', include('users.urls')), path('v1/', include('data_import.urls')), path('v1/', include('data_export.urls')), - path('v1/projects//', include('projects.urls')), + path('v1/', include('projects.urls')), path('v1/projects//metrics/', include('metrics.urls')), path('v1/projects//', include('auto_labeling.urls')), path('v1/projects//', include('examples.urls')), diff --git a/backend/auto_labeling/migrations/0003_fill_task_type.py b/backend/auto_labeling/migrations/0003_fill_task_type.py index 07332d49..02b301fa 100644 --- a/backend/auto_labeling/migrations/0003_fill_task_type.py +++ b/backend/auto_labeling/migrations/0003_fill_task_type.py @@ -1,6 +1,6 @@ from django.db import migrations -from api.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT, IMAGE_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT, IMAGE_CLASSIFICATION def fill_task_type(apps, schema_editor): diff --git a/backend/auto_labeling/migrations/0004_alter_autolabelingconfig_project.py b/backend/auto_labeling/migrations/0004_alter_autolabelingconfig_project.py new file mode 100644 index 00000000..91794e72 --- /dev/null +++ b/backend/auto_labeling/migrations/0004_alter_autolabelingconfig_project.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_auto_20220204_0201'), + ('auto_labeling', '0003_fill_task_type'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='autolabelingconfig', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='auto_labeling_config', to='projects.project'), + ), + ], + database_operations=[] + ) + ] diff --git a/backend/auto_labeling/models.py b/backend/auto_labeling/models.py index 48092194..cd51aa45 100644 --- a/backend/auto_labeling/models.py +++ b/backend/auto_labeling/models.py @@ -2,7 +2,7 @@ from auto_labeling_pipeline.models import RequestModelFactory from django.core.exceptions import ValidationError from django.db import models -from api.models import Project +from projects.models import Project class AutoLabelingConfig(models.Model): diff --git a/backend/auto_labeling/pipeline/labels.py b/backend/auto_labeling/pipeline/labels.py index 7e3c833e..82a57797 100644 --- a/backend/auto_labeling/pipeline/labels.py +++ b/backend/auto_labeling/pipeline/labels.py @@ -4,7 +4,7 @@ from typing import List from auto_labeling_pipeline.labels import Labels from django.contrib.auth.models import User -from api.models import Project +from projects.models import Project from examples.models import Example from label_types.models import CategoryType, SpanType from labels.models import Label, Category, Span, TextLabel diff --git a/backend/auto_labeling/tests/test_views.py b/backend/auto_labeling/tests/test_views.py index 432155cf..48078342 100644 --- a/backend/auto_labeling/tests/test_views.py +++ b/backend/auto_labeling/tests/test_views.py @@ -7,7 +7,7 @@ from model_mommy import mommy from rest_framework import status from rest_framework.reverse import reverse -from api.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ from labels.models import Category, Span, TextLabel from api.tests.api.utils import CRUDMixin, make_doc, prepare_project from auto_labeling.pipeline.labels import Categories, Spans, Texts diff --git a/backend/auto_labeling/views.py b/backend/auto_labeling/views.py index 81d5a88a..fc87269f 100644 --- a/backend/auto_labeling/views.py +++ b/backend/auto_labeling/views.py @@ -15,7 +15,7 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView -from api.models import Project +from projects.models import Project from projects.permissions import IsProjectMember, IsProjectAdmin from .pipeline.execution import execute_pipeline, get_label_collection from .exceptions import AWSTokenError, SampleDataException, TemplateMappingError, URLConnectionError diff --git a/backend/data_export/celery_tasks.py b/backend/data_export/celery_tasks.py index ab01786f..b81c10e4 100644 --- a/backend/data_export/celery_tasks.py +++ b/backend/data_export/celery_tasks.py @@ -3,7 +3,7 @@ from celery.utils.log import get_task_logger from django.conf import settings from django.shortcuts import get_object_or_404 -from api.models import Project +from projects.models import Project from .pipeline.factories import create_repository, create_writer from .pipeline.services import ExportApplicationService diff --git a/backend/data_export/pipeline/catalog.py b/backend/data_export/pipeline/catalog.py index 04e59e93..a19d5fec 100644 --- a/backend/data_export/pipeline/catalog.py +++ b/backend/data_export/pipeline/catalog.py @@ -4,9 +4,8 @@ from typing import Dict, List, Type from pydantic import BaseModel from typing_extensions import Literal -from api.models import (DOCUMENT_CLASSIFICATION, IMAGE_CLASSIFICATION, - INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, - SEQUENCE_LABELING, SPEECH2TEXT) +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT, IMAGE_CLASSIFICATION, \ + INTENT_DETECTION_AND_SLOT_FILLING from . import examples diff --git a/backend/data_export/pipeline/factories.py b/backend/data_export/pipeline/factories.py index 1bf432e1..de6636bc 100644 --- a/backend/data_export/pipeline/factories.py +++ b/backend/data_export/pipeline/factories.py @@ -1,8 +1,7 @@ from typing import Type -from api.models import (DOCUMENT_CLASSIFICATION, IMAGE_CLASSIFICATION, - INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, - SEQUENCE_LABELING, SPEECH2TEXT) +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT, IMAGE_CLASSIFICATION, \ + INTENT_DETECTION_AND_SLOT_FILLING from . import catalog, repositories, writers diff --git a/backend/data_export/pipeline/repositories.py b/backend/data_export/pipeline/repositories.py index d0ee0269..d49197be 100644 --- a/backend/data_export/pipeline/repositories.py +++ b/backend/data_export/pipeline/repositories.py @@ -3,7 +3,7 @@ import itertools from collections import defaultdict from typing import Dict, Iterator, List -from api.models import Project +from projects.models import Project from examples.models import Example from .data import Record diff --git a/backend/data_export/tests/test_repositories.py b/backend/data_export/tests/test_repositories.py index 7f1f8d96..aa0cc4de 100644 --- a/backend/data_export/tests/test_repositories.py +++ b/backend/data_export/tests/test_repositories.py @@ -2,7 +2,7 @@ import unittest from model_mommy import mommy -from api.models import INTENT_DETECTION_AND_SLOT_FILLING +from projects.models import INTENT_DETECTION_AND_SLOT_FILLING from api.tests.api.utils import prepare_project from ..pipeline.repositories import IntentDetectionSlotFillingRepository diff --git a/backend/data_export/tests/test_views.py b/backend/data_export/tests/test_views.py index e2984662..a9e47ff8 100644 --- a/backend/data_export/tests/test_views.py +++ b/backend/data_export/tests/test_views.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.reverse import reverse -from api.models import DOCUMENT_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION from api.tests.api.utils import CRUDMixin, prepare_project diff --git a/backend/data_export/views.py b/backend/data_export/views.py index edebc0cd..94e468a3 100644 --- a/backend/data_export/views.py +++ b/backend/data_export/views.py @@ -6,7 +6,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from api.models import Project +from projects.models import Project from projects.permissions import IsProjectAdmin from .celery_tasks import export_dataset from .pipeline.catalog import Options diff --git a/backend/data_import/celery_tasks.py b/backend/data_import/celery_tasks.py index 0c86ff36..0d87f546 100644 --- a/backend/data_import/celery_tasks.py +++ b/backend/data_import/celery_tasks.py @@ -3,7 +3,7 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 -from api.models import Project +from projects.models import Project from .pipeline.factories import create_parser, create_builder, create_cleaner from .pipeline.readers import Reader from .pipeline.writers import BulkWriter diff --git a/backend/data_import/pipeline/catalog.py b/backend/data_import/pipeline/catalog.py index acc69c59..26c6aa2e 100644 --- a/backend/data_import/pipeline/catalog.py +++ b/backend/data_import/pipeline/catalog.py @@ -4,9 +4,8 @@ from typing import Dict, List, Type from pydantic import BaseModel from typing_extensions import Literal -from api.models import (DOCUMENT_CLASSIFICATION, IMAGE_CLASSIFICATION, - INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, - SEQUENCE_LABELING, SPEECH2TEXT) +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT, IMAGE_CLASSIFICATION, \ + INTENT_DETECTION_AND_SLOT_FILLING from . import examples encodings = Literal[ diff --git a/backend/data_import/pipeline/cleaners.py b/backend/data_import/pipeline/cleaners.py index 73d6a6bb..531b398b 100644 --- a/backend/data_import/pipeline/cleaners.py +++ b/backend/data_import/pipeline/cleaners.py @@ -1,6 +1,6 @@ from typing import List -from api.models import Project +from projects.models import Project from .labels import CategoryLabel, Label, SpanLabel diff --git a/backend/data_import/pipeline/data.py b/backend/data_import/pipeline/data.py index 2bcb7d25..eb55130e 100644 --- a/backend/data_import/pipeline/data.py +++ b/backend/data_import/pipeline/data.py @@ -4,7 +4,7 @@ from typing import Any, Dict from pydantic import BaseModel, validator -from api.models import Project +from projects.models import Project from examples.models import Example diff --git a/backend/data_import/pipeline/factories.py b/backend/data_import/pipeline/factories.py index 5eccea89..dfaaed32 100644 --- a/backend/data_import/pipeline/factories.py +++ b/backend/data_import/pipeline/factories.py @@ -1,6 +1,5 @@ -from api.models import (DOCUMENT_CLASSIFICATION, IMAGE_CLASSIFICATION, - INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, - SEQUENCE_LABELING, SPEECH2TEXT) +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT, IMAGE_CLASSIFICATION, \ + INTENT_DETECTION_AND_SLOT_FILLING from . import builders, catalog, cleaners, data, labels, parsers, readers diff --git a/backend/data_import/pipeline/labels.py b/backend/data_import/pipeline/labels.py index 443cd523..d1e2a98f 100644 --- a/backend/data_import/pipeline/labels.py +++ b/backend/data_import/pipeline/labels.py @@ -3,7 +3,7 @@ from typing import Any, Dict, Optional, Union from pydantic import BaseModel, validator -from api.models import Project +from projects.models import Project from label_types.models import LabelType, CategoryType, SpanType from labels.models import Category, Span, TextLabel as TL diff --git a/backend/data_import/pipeline/writers.py b/backend/data_import/pipeline/writers.py index e0d3a10c..cd2f14f0 100644 --- a/backend/data_import/pipeline/writers.py +++ b/backend/data_import/pipeline/writers.py @@ -5,7 +5,7 @@ from typing import Any, Dict, List from django.conf import settings -from api.models import Project +from projects.models import Project from examples.models import Example from label_types.models import CategoryType, SpanType from .exceptions import FileParseException diff --git a/backend/data_import/tests/test_tasks.py b/backend/data_import/tests/test_tasks.py index 71628695..36124cd6 100644 --- a/backend/data_import/tests/test_tasks.py +++ b/backend/data_import/tests/test_tasks.py @@ -3,9 +3,8 @@ import pathlib from django.test import TestCase from data_import.celery_tasks import import_dataset -from api.models import (DOCUMENT_CLASSIFICATION, - INTENT_DETECTION_AND_SLOT_FILLING, SEQ2SEQ, - SEQUENCE_LABELING, IMAGE_CLASSIFICATION) +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, IMAGE_CLASSIFICATION, \ + INTENT_DETECTION_AND_SLOT_FILLING from examples.models import Example from label_types.models import CategoryType, SpanType from labels.models import Category, Span diff --git a/backend/data_import/tests/test_views.py b/backend/data_import/tests/test_views.py index 8754074b..dbab52de 100644 --- a/backend/data_import/tests/test_views.py +++ b/backend/data_import/tests/test_views.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.reverse import reverse -from api.models import DOCUMENT_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION from api.tests.api.utils import CRUDMixin, prepare_project diff --git a/backend/data_import/views.py b/backend/data_import/views.py index b31705b4..2bd97812 100644 --- a/backend/data_import/views.py +++ b/backend/data_import/views.py @@ -8,8 +8,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView - -from api.models import Project +from projects.models import Project from projects.permissions import IsProjectAdmin from .celery_tasks import import_dataset from .pipeline.catalog import Options diff --git a/backend/examples/migrations/0002_alter_example_project.py b/backend/examples/migrations/0002_alter_example_project.py new file mode 100644 index 00000000..3d4305c7 --- /dev/null +++ b/backend/examples/migrations/0002_alter_example_project.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_auto_20220204_0201'), + ('examples', '0001_initial'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='example', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='examples', + to='projects.project'), + ), + ], + database_operations=[ + + ] + ) + + ] diff --git a/backend/examples/models.py b/backend/examples/models.py index 6eb3cdc7..1d68bc13 100644 --- a/backend/examples/models.py +++ b/backend/examples/models.py @@ -3,7 +3,7 @@ import uuid from django.contrib.auth.models import User from django.db import models -from api.models import Project +from projects.models import Project from .managers import ExampleManager, ExampleStateManager diff --git a/backend/examples/tests/test_document.py b/backend/examples/tests/test_document.py index 3d51c317..c5b9553e 100644 --- a/backend/examples/tests/test_document.py +++ b/backend/examples/tests/test_document.py @@ -3,7 +3,7 @@ from django.utils.http import urlencode from rest_framework import status from rest_framework.reverse import reverse -from api.models import DOCUMENT_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION from api.tests.api.utils import (CRUDMixin, assign_user_to_role, make_doc, make_example_state, make_user, prepare_project) diff --git a/backend/examples/tests/test_models.py b/backend/examples/tests/test_models.py index 2604d0a1..592a1f85 100644 --- a/backend/examples/tests/test_models.py +++ b/backend/examples/tests/test_models.py @@ -1,7 +1,7 @@ from django.test import TestCase from model_mommy import mommy -from api.models import IMAGE_CLASSIFICATION, SEQUENCE_LABELING +from projects.models import SEQUENCE_LABELING, IMAGE_CLASSIFICATION from api.tests.api.utils import prepare_project from examples.models import ExampleState diff --git a/backend/examples/views/example.py b/backend/examples/views/example.py index 654ce4c8..56559cf6 100644 --- a/backend/examples/views/example.py +++ b/backend/examples/views/example.py @@ -7,7 +7,7 @@ from rest_framework import filters, generics, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from api.models import Project +from projects.models import Project from examples.filters import ExampleFilter from examples.models import Example from examples.serializers import ExampleSerializer diff --git a/backend/examples/views/example_state.py b/backend/examples/views/example_state.py index 41a42995..dcc331fc 100644 --- a/backend/examples/views/example_state.py +++ b/backend/examples/views/example_state.py @@ -2,7 +2,7 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics from rest_framework.permissions import IsAuthenticated -from api.models import Project +from projects.models import Project from examples.models import Example, ExampleState from examples.serializers import ExampleStateSerializer from projects.permissions import IsProjectMember diff --git a/backend/label_types/migrations/0003_auto_20220204_0201.py b/backend/label_types/migrations/0003_auto_20220204_0201.py new file mode 100644 index 00000000..d0422f9c --- /dev/null +++ b/backend/label_types/migrations/0003_auto_20220204_0201.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_auto_20220204_0201'), + ('label_types', '0002_rename_relationtypes_relationtype'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='categorytype', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.project'), + ), + migrations.AlterField( + model_name='relationtype', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relation_types', + to='projects.project'), + ), + migrations.AlterField( + model_name='spantype', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.project'), + ), + ], + database_operations=[] + ) + ] diff --git a/backend/label_types/models.py b/backend/label_types/models.py index b340f34b..9ab48748 100644 --- a/backend/label_types/models.py +++ b/backend/label_types/models.py @@ -4,7 +4,7 @@ import string from django.core.exceptions import ValidationError from django.db import models -from api.models import Project +from projects.models import Project def generate_random_hex_color(): diff --git a/backend/label_types/tests/test_views.py b/backend/label_types/tests/test_views.py index faa71430..209a196c 100644 --- a/backend/label_types/tests/test_views.py +++ b/backend/label_types/tests/test_views.py @@ -5,7 +5,7 @@ from rest_framework import status from rest_framework.reverse import reverse from rest_framework.test import APITestCase -from api.models import DOCUMENT_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION from api.tests.api.utils import (DATA_DIR, CRUDMixin, make_label, make_project, make_user, prepare_project) diff --git a/backend/labels/migrations/0005_alter_relation_project.py b/backend/labels/migrations/0005_alter_relation_project.py new file mode 100644 index 00000000..da009cad --- /dev/null +++ b/backend/labels/migrations/0005_alter_relation_project.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_auto_20220204_0201'), + ('labels', '0004_auto_20220128_0246'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='relation', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='annotation_relations', to='projects.project'), + ), + ], + database_operations=[] + ) + ] diff --git a/backend/labels/models.py b/backend/labels/models.py index 462bf1ca..24a335bc 100644 --- a/backend/labels/models.py +++ b/backend/labels/models.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from django.db import models from .managers import LabelManager, CategoryManager, SpanManager, TextLabelManager -from api.models import Project +from projects.models import Project from examples.models import Example from label_types.models import CategoryType, SpanType, RelationType diff --git a/backend/labels/tests/test_category.py b/backend/labels/tests/test_category.py index 18c3eccf..727ff1fe 100644 --- a/backend/labels/tests/test_category.py +++ b/backend/labels/tests/test_category.py @@ -4,7 +4,7 @@ from django.db import IntegrityError from django.test import TestCase from model_mommy import mommy -from api.models import DOCUMENT_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION from labels.models import Category from api.tests.api.utils import prepare_project diff --git a/backend/labels/tests/test_span.py b/backend/labels/tests/test_span.py index fef5c621..7a3e0d8b 100644 --- a/backend/labels/tests/test_span.py +++ b/backend/labels/tests/test_span.py @@ -5,7 +5,7 @@ from django.db import IntegrityError from django.test import TestCase from model_mommy import mommy -from api.models import SEQUENCE_LABELING +from projects.models import SEQUENCE_LABELING from label_types.models import SpanType from labels.models import Span from api.tests.api.utils import prepare_project diff --git a/backend/labels/tests/test_text_label.py b/backend/labels/tests/test_text_label.py index 14c1cec6..fc181a63 100644 --- a/backend/labels/tests/test_text_label.py +++ b/backend/labels/tests/test_text_label.py @@ -4,7 +4,7 @@ from django.db import IntegrityError from django.test import TestCase from model_mommy import mommy -from api.models import SEQ2SEQ +from projects.models import SEQ2SEQ from labels.models import TextLabel from api.tests.api.utils import prepare_project diff --git a/backend/labels/tests/test_views.py b/backend/labels/tests/test_views.py index 482583e1..48cec7a4 100644 --- a/backend/labels/tests/test_views.py +++ b/backend/labels/tests/test_views.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.reverse import reverse -from api.models import (DOCUMENT_CLASSIFICATION, SEQ2SEQ, SEQUENCE_LABELING) +from projects.models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ from labels.models import Category, Span, TextLabel from api.tests.api.utils import (CRUDMixin, make_annotation, make_doc, make_label, make_user, prepare_project) diff --git a/backend/labels/views.py b/backend/labels/views.py index ab67aacd..3819df98 100644 --- a/backend/labels/views.py +++ b/backend/labels/views.py @@ -6,7 +6,7 @@ from rest_framework import generics, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from api.models import Project +from projects.models import Project from labels.models import Category, Span, TextLabel, Relation from projects.permissions import IsProjectMember from .permissions import CanEditLabel diff --git a/backend/metrics/tests.py b/backend/metrics/tests.py index a46dec39..47358e38 100644 --- a/backend/metrics/tests.py +++ b/backend/metrics/tests.py @@ -3,7 +3,7 @@ from rest_framework import status from rest_framework.reverse import reverse from api.tests.api.utils import CRUDMixin, prepare_project, make_doc, make_label -from api.models import DOCUMENT_CLASSIFICATION +from projects.models import DOCUMENT_CLASSIFICATION class TestMemberProgress(CRUDMixin): diff --git a/backend/projects/admin.py b/backend/projects/admin.py index 0d18a671..9a6dd42b 100644 --- a/backend/projects/admin.py +++ b/backend/projects/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from .models import Member +from .models import Project, TextClassificationProject, SequenceLabelingProject, Seq2seqProject, Tag class MemberAdmin(admin.ModelAdmin): @@ -9,4 +10,21 @@ class MemberAdmin(admin.ModelAdmin): search_fields = ('user__username',) +class ProjectAdmin(admin.ModelAdmin): + list_display = ('name', 'description', 'project_type', 'random_order', 'collaborative_annotation') + ordering = ('project_type',) + search_fields = ('name',) + + +class TagAdmin(admin.ModelAdmin): + list_display = ('project', 'text', ) + ordering = ('project', 'text', ) + search_fields = ('text',) + + admin.site.register(Member, MemberAdmin) +admin.site.register(Project, ProjectAdmin) +admin.site.register(TextClassificationProject, ProjectAdmin) +admin.site.register(SequenceLabelingProject, ProjectAdmin) +admin.site.register(Seq2seqProject, ProjectAdmin) +admin.site.register(Tag, TagAdmin) diff --git a/backend/projects/apps.py b/backend/projects/apps.py index 9293907d..3d71e6b9 100644 --- a/backend/projects/apps.py +++ b/backend/projects/apps.py @@ -10,7 +10,7 @@ class ProjectsConfig(AppConfig): def ready(self): importlib.import_module('projects.signals') - from api.models import Project + from projects.models import Project from .signals import add_administrator_on_project_creation # Registering signals with the subclasses of project. diff --git a/backend/projects/management/commands/create_member.py b/backend/projects/management/commands/create_member.py index a01b047e..49f98fa6 100644 --- a/backend/projects/management/commands/create_member.py +++ b/backend/projects/management/commands/create_member.py @@ -1,7 +1,7 @@ from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import User -from api.models import Project +from models import Project from roles.models import Role from ...models import Member diff --git a/backend/projects/migrations/0002_auto_20220204_0201.py b/backend/projects/migrations/0002_auto_20220204_0201.py new file mode 100644 index 00000000..40af5189 --- /dev/null +++ b/backend/projects/migrations/0002_auto_20220204_0201.py @@ -0,0 +1,155 @@ +# Generated by Django 3.2.11 on 2022-02-04 02:01 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ('projects', '0001_initial'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.CreateModel( + name='Project', + fields=[ + ('id', + models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField(default='')), + ('guideline', models.TextField(blank=True, default='')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('project_type', models.CharField( + choices=[('DocumentClassification', 'document classification'), + ('SequenceLabeling', 'sequence labeling'), ('Seq2seq', 'sequence to sequence'), + ('IntentDetectionAndSlotFilling', 'intent detection and slot filling'), + ('Speech2text', 'speech to text'), + ('ImageClassification', 'image classification')], max_length=30)), + ('random_order', models.BooleanField(default=False)), + ('collaborative_annotation', models.BooleanField(default=False)), + ('single_class_classification', models.BooleanField(default=False)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL)), + ('polymorphic_ctype', + models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='polymorphic_projects.project_set+', + to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='ImageClassificationProject', + fields=[ + ('project_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, + parent_link=True, primary_key=True, serialize=False, + to='projects.project')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('projects.project',), + ), + migrations.CreateModel( + name='IntentDetectionAndSlotFillingProject', + fields=[ + ('project_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, + parent_link=True, primary_key=True, serialize=False, + to='projects.project')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('projects.project',), + ), + migrations.CreateModel( + name='Seq2seqProject', + fields=[ + ('project_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, + parent_link=True, primary_key=True, serialize=False, + to='projects.project')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('projects.project',), + ), + migrations.CreateModel( + name='SequenceLabelingProject', + fields=[ + ('project_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, + parent_link=True, primary_key=True, serialize=False, + to='projects.project')), + ('allow_overlapping', models.BooleanField(default=False)), + ('grapheme_mode', models.BooleanField(default=False)), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('projects.project',), + ), + migrations.CreateModel( + name='Speech2textProject', + fields=[ + ('project_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, + parent_link=True, primary_key=True, serialize=False, + to='projects.project')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('projects.project',), + ), + migrations.CreateModel( + name='TextClassificationProject', + fields=[ + ('project_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, + parent_link=True, primary_key=True, serialize=False, + to='projects.project')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('projects.project',), + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', + models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.TextField()), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', + to='projects.project')), + ], + ), + migrations.AlterField( + model_name='member', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_mappings', + to='projects.project'), + ), + ], + database_operations=[] + ) + ] diff --git a/backend/projects/migrations/0003_auto_20220204_0551.py b/backend/projects/migrations/0003_auto_20220204_0551.py new file mode 100644 index 00000000..259d5fea --- /dev/null +++ b/backend/projects/migrations/0003_auto_20220204_0551.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.11 on 2022-02-04 05:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_auto_20220204_0201'), + ] + + operations = [ + migrations.RunSQL( + sql=""" + UPDATE + projects_project + SET + polymorphic_ctype_id = ( + SELECT + t2.id + from + django_content_type t1 + INNER JOIN django_content_type t2 ON t1.model = t2.model + WHERE + t1.app_label = 'api' + AND t2.app_label = 'projects' + AND t1.model LIKE '%project' + AND t1.id = projects_project.polymorphic_ctype_id + ); + """, + reverse_sql=""" + UPDATE + projects_project + SET + polymorphic_ctype_id = ( + SELECT + t1.id + from + django_content_type t1 + INNER JOIN django_content_type t2 ON t1.model = t2.model + WHERE + t1.app_label = 'api' + AND t2.app_label = 'projects' + AND t1.model LIKE '%project' + AND t2.id = projects_project.polymorphic_ctype_id + ); + """ + ) + ] diff --git a/backend/projects/models.py b/backend/projects/models.py index 9a16d862..a0cbada1 100644 --- a/backend/projects/models.py +++ b/backend/projects/models.py @@ -1,13 +1,166 @@ +import abc + from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.db import models - from django.db.models import Manager +from polymorphic.models import PolymorphicModel -from api.models import Project from roles.models import Role +DOCUMENT_CLASSIFICATION = 'DocumentClassification' +SEQUENCE_LABELING = 'SequenceLabeling' +SEQ2SEQ = 'Seq2seq' +SPEECH2TEXT = 'Speech2text' +IMAGE_CLASSIFICATION = 'ImageClassification' +INTENT_DETECTION_AND_SLOT_FILLING = 'IntentDetectionAndSlotFilling' +PROJECT_CHOICES = ( + (DOCUMENT_CLASSIFICATION, 'document classification'), + (SEQUENCE_LABELING, 'sequence labeling'), + (SEQ2SEQ, 'sequence to sequence'), + (INTENT_DETECTION_AND_SLOT_FILLING, 'intent detection and slot filling'), + (SPEECH2TEXT, 'speech to text'), + (IMAGE_CLASSIFICATION, 'image classification') +) + + +class Project(PolymorphicModel): + name = models.CharField(max_length=100) + description = models.TextField(default='') + guideline = models.TextField(default='', blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + created_by = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + ) + project_type = models.CharField(max_length=30, choices=PROJECT_CHOICES) + random_order = models.BooleanField(default=False) + collaborative_annotation = models.BooleanField(default=False) + single_class_classification = models.BooleanField(default=False) + + @property + @abc.abstractmethod + def is_text_project(self) -> bool: + return False + + @property + def can_define_label(self) -> bool: + """Whether or not the project can define label(ignoring the type of label)""" + return False + + @property + def can_define_relation(self) -> bool: + """Whether or not the project can define relation.""" + return False + + @property + def can_define_category(self) -> bool: + """Whether or not the project can define category.""" + return False + + @property + def can_define_span(self) -> bool: + """Whether or not the project can define span.""" + return False + + def __str__(self): + return self.name + + +class TextClassificationProject(Project): + + @property + def is_text_project(self) -> bool: + return True + + @property + def can_define_label(self) -> bool: + return True + + @property + def can_define_category(self) -> bool: + return True + + +class SequenceLabelingProject(Project): + allow_overlapping = models.BooleanField(default=False) + grapheme_mode = models.BooleanField(default=False) + + @property + def is_text_project(self) -> bool: + return True + + @property + def can_define_label(self) -> bool: + return True + + @property + def can_define_span(self) -> bool: + return True + + +class Seq2seqProject(Project): + + @property + def is_text_project(self) -> bool: + return True + + +class IntentDetectionAndSlotFillingProject(Project): + + @property + def is_text_project(self) -> bool: + return True + + @property + def can_define_label(self) -> bool: + return True + + @property + def can_define_category(self) -> bool: + return True + + @property + def can_define_span(self) -> bool: + return True + + +class Speech2textProject(Project): + + @property + def is_text_project(self) -> bool: + return False + + +class ImageClassificationProject(Project): + + @property + def is_text_project(self) -> bool: + return False + + @property + def can_define_label(self) -> bool: + return True + + @property + def can_define_category(self) -> bool: + return True + + +class Tag(models.Model): + text = models.TextField() + project = models.ForeignKey( + to=Project, + on_delete=models.CASCADE, + related_name='tags' + ) + + def __str__(self): + return self.text + class MemberManager(Manager): diff --git a/backend/projects/serializers.py b/backend/projects/serializers.py index 5b514652..863aff0f 100644 --- a/backend/projects/serializers.py +++ b/backend/projects/serializers.py @@ -1,5 +1,8 @@ from rest_framework import serializers +from rest_polymorphic.serializers import PolymorphicSerializer +from .models import Tag, Project, TextClassificationProject, SequenceLabelingProject, Seq2seqProject, \ + IntentDetectionAndSlotFillingProject, Speech2textProject, ImageClassificationProject from .models import Member @@ -20,3 +23,91 @@ class MemberSerializer(serializers.ModelSerializer): class Meta: model = Member fields = ('id', 'user', 'role', 'username', 'rolename') + + +class TagSerializer(serializers.ModelSerializer): + + class Meta: + model = Tag + fields = ('id', 'project', 'text', ) + read_only_fields = ('id', 'project') + + +class ProjectSerializer(serializers.ModelSerializer): + tags = TagSerializer(many=True, required=False) + + class Meta: + model = Project + fields = ( + 'id', + 'name', + 'description', + 'guideline', + 'project_type', + 'updated_at', + 'random_order', + 'created_by', + 'collaborative_annotation', + 'single_class_classification', + 'is_text_project', + 'can_define_label', + 'can_define_relation', + 'can_define_category', + 'can_define_span', + 'tags' + ) + read_only_fields = ( + 'updated_at', + 'is_text_project', + 'can_define_label', + 'can_define_relation', + 'can_define_category', + 'can_define_span', + 'tags' + ) + + +class TextClassificationProjectSerializer(ProjectSerializer): + + class Meta(ProjectSerializer.Meta): + model = TextClassificationProject + + +class SequenceLabelingProjectSerializer(ProjectSerializer): + + class Meta(ProjectSerializer.Meta): + model = SequenceLabelingProject + fields = ProjectSerializer.Meta.fields + ('allow_overlapping', 'grapheme_mode') + + +class Seq2seqProjectSerializer(ProjectSerializer): + + class Meta(ProjectSerializer.Meta): + model = Seq2seqProject + + +class IntentDetectionAndSlotFillingProjectSerializer(ProjectSerializer): + + class Meta(ProjectSerializer.Meta): + model = IntentDetectionAndSlotFillingProject + + +class Speech2textProjectSerializer(ProjectSerializer): + + class Meta(ProjectSerializer.Meta): + model = Speech2textProject + + +class ImageClassificationProjectSerializer(ProjectSerializer): + + class Meta(ProjectSerializer.Meta): + model = ImageClassificationProject + + +class ProjectPolymorphicSerializer(PolymorphicSerializer): + model_serializer_mapping = { + Project: ProjectSerializer, + **{ + cls.Meta.model: cls for cls in ProjectSerializer.__subclasses__() + } + } diff --git a/backend/projects/signals.py b/backend/projects/signals.py index 134b3570..1030be33 100644 --- a/backend/projects/signals.py +++ b/backend/projects/signals.py @@ -1,6 +1,6 @@ from django.conf import settings -from api.models import Project +from projects.models import Project from roles.models import Role from .models import Member diff --git a/backend/projects/tests/__init__.py b/backend/projects/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/projects/tests.py b/backend/projects/tests/test_member.py similarity index 100% rename from backend/projects/tests.py rename to backend/projects/tests/test_member.py diff --git a/backend/api/tests/api/test_project.py b/backend/projects/tests/test_project.py similarity index 97% rename from backend/api/tests/api/test_project.py rename to backend/projects/tests/test_project.py index 960d68af..9b281cbb 100644 --- a/backend/api/tests/api/test_project.py +++ b/backend/projects/tests/test_project.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.reverse import reverse -from .utils import CRUDMixin, create_default_roles, make_user, prepare_project +from api.tests.api.utils import CRUDMixin, create_default_roles, make_user, prepare_project class TestProjectList(CRUDMixin): diff --git a/backend/api/tests/api/test_tag.py b/backend/projects/tests/test_tag.py similarity index 96% rename from backend/api/tests/api/test_tag.py rename to backend/projects/tests/test_tag.py index f7d3a9a3..b748fee5 100644 --- a/backend/api/tests/api/test_tag.py +++ b/backend/projects/tests/test_tag.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.reverse import reverse -from .utils import CRUDMixin, make_tag, make_user, prepare_project +from api.tests.api.utils import CRUDMixin, make_tag, make_user, prepare_project class TestTagList(CRUDMixin): diff --git a/backend/projects/urls.py b/backend/projects/urls.py index f9236ca9..0ae096a6 100644 --- a/backend/projects/urls.py +++ b/backend/projects/urls.py @@ -1,15 +1,38 @@ from django.urls import path -from .views import MemberList, MemberDetail +from .views.member import MemberList, MemberDetail +from .views.tag import TagList, TagDetail +from .views.project import ProjectList, ProjectDetail + urlpatterns = [ path( - route='members', + route='projects', + view=ProjectList.as_view(), + name='project_list' + ), + path( + route='projects/', + view=ProjectDetail.as_view(), + name='project_detail' + ), + path( + route='projects//tags', + view=TagList.as_view(), + name='tag_list' + ), + path( + route='projects//tags/', + view=TagDetail.as_view(), + name='tag_detail' + ), + path( + route='projects//members', view=MemberList.as_view(), name='member_list' ), path( - route='members/', + route='projects//members/', view=MemberDetail.as_view(), name='member_detail' ) diff --git a/backend/projects/views.py b/backend/projects/views/member.py similarity index 89% rename from backend/projects/views.py rename to backend/projects/views/member.py index 2d9c8ee1..b887cf3e 100644 --- a/backend/projects/views.py +++ b/backend/projects/views/member.py @@ -4,10 +4,10 @@ from rest_framework import generics, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from .permissions import IsProjectAdmin -from .serializers import MemberSerializer -from .exceptions import RoleAlreadyAssignedException, RoleConstraintException -from .models import Member +from projects.permissions import IsProjectAdmin +from projects.serializers import MemberSerializer +from projects.exceptions import RoleAlreadyAssignedException, RoleConstraintException +from projects.models import Member class MemberList(generics.ListCreateAPIView): diff --git a/backend/api/views/project.py b/backend/projects/views/project.py similarity index 95% rename from backend/api/views/project.py rename to backend/projects/views/project.py index 9110125c..6f26626e 100644 --- a/backend/api/views/project.py +++ b/backend/projects/views/project.py @@ -6,8 +6,8 @@ from rest_framework.response import Response from projects.permissions import IsProjectAdmin, IsProjectStaffAndReadOnly -from ..models import Project -from ..serializers import ProjectPolymorphicSerializer +from projects.models import Project +from projects.serializers import ProjectPolymorphicSerializer class ProjectList(generics.ListCreateAPIView): diff --git a/backend/api/views/tag.py b/backend/projects/views/tag.py similarity index 91% rename from backend/api/views/tag.py rename to backend/projects/views/tag.py index e8f9adfe..96997ffd 100644 --- a/backend/api/views/tag.py +++ b/backend/projects/views/tag.py @@ -3,8 +3,8 @@ from rest_framework.permissions import IsAuthenticated from projects.permissions import IsProjectAdmin, IsProjectStaffAndReadOnly -from ..models import Tag -from ..serializers import TagSerializer +from projects.models import Tag +from projects.serializers import TagSerializer class TagList(generics.ListCreateAPIView):