diff --git a/backend/api/migrations/0029_auto_20220119_2333.py b/backend/api/migrations/0029_auto_20220119_2333.py new file mode 100644 index 00000000..424cb322 --- /dev/null +++ b/backend/api/migrations/0029_auto_20220119_2333.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.11 on 2022-01-19 23:33 + +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), + ('api', '0028_auto_20220111_0655'), + ] + + operations = [ + migrations.RemoveField( + model_name='project', + name='users', + ), + migrations.AddField( + model_name='project', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 9ba82249..28e12a18 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -33,7 +33,11 @@ class Project(PolymorphicModel): guideline = models.TextField(default='', blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - users = models.ManyToManyField(User, related_name='projects') + 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) diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 904fbbda..c86333a3 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -159,10 +159,10 @@ class ProjectSerializer(serializers.ModelSerializer): 'name', 'description', 'guideline', - 'users', 'project_type', 'updated_at', 'random_order', + 'created_by', 'collaborative_annotation', 'single_class_classification', 'is_text_project', @@ -174,7 +174,6 @@ class ProjectSerializer(serializers.ModelSerializer): ) read_only_fields = ( 'updated_at', - 'users', 'is_text_project', 'can_define_label', 'can_define_relation', diff --git a/backend/api/tests/api/utils.py b/backend/api/tests/api/utils.py index 01138075..4cf9e59f 100644 --- a/backend/api/tests/api/utils.py +++ b/backend/api/tests/api/utils.py @@ -73,8 +73,8 @@ def make_project( project = mommy.make( _model=project_model, project_type=task, - users=users, collaborative_annotation=collaborative_annotation, + created_by=users[0], **kwargs ) diff --git a/backend/api/tests/test_models.py b/backend/api/tests/test_models.py index c4ac06b5..7ad54a22 100644 --- a/backend/api/tests/test_models.py +++ b/backend/api/tests/test_models.py @@ -231,7 +231,7 @@ class TestLabelDistribution(TestCase): mommy.make('Span', example=self.example, start_offset=10, end_offset=15, user=self.user, label=label_b) distribution = Span.objects.calc_label_distribution( examples=self.project.item.examples.all(), - users=self.project.item.users.all(), + members=self.project.users, labels=SpanType.objects.all() ) expected = {user.username: {label.text: 0 for label in SpanType.objects.all()} for user in self.project.users} diff --git a/backend/api/views/project.py b/backend/api/views/project.py index 7e789f5a..da7ee6f1 100644 --- a/backend/api/views/project.py +++ b/backend/api/views/project.py @@ -22,10 +22,10 @@ class ProjectList(generics.ListCreateAPIView): return super().get_permissions() def get_queryset(self): - return self.request.user.projects + return Project.objects.filter(role_mappings__user=self.request.user) def perform_create(self, serializer): - serializer.save(users=[self.request.user]) + serializer.save(created_by=self.request.user) def delete(self, request, *args, **kwargs): delete_ids = request.data['ids'] diff --git a/backend/members/apps.py b/backend/members/apps.py index 5379ddf5..c0b5c1ca 100644 --- a/backend/members/apps.py +++ b/backend/members/apps.py @@ -1,6 +1,7 @@ import importlib from django.apps import AppConfig +from django.db.models.signals import post_save class MembersConfig(AppConfig): @@ -9,3 +10,9 @@ class MembersConfig(AppConfig): def ready(self): importlib.import_module('members.signals') + from api.models import Project + from .signals import add_administrator_on_project_creation + + # Registering signals with the subclasses of project. + for project in Project.__subclasses__(): + post_save.connect(add_administrator_on_project_creation, project) diff --git a/backend/members/signals.py b/backend/members/signals.py index 8245b2df..134b3570 100644 --- a/backend/members/signals.py +++ b/backend/members/signals.py @@ -1,48 +1,16 @@ from django.conf import settings -from django.contrib.auth.models import User -from django.db.models.signals import m2m_changed, post_save, pre_delete -from django.dispatch import receiver from api.models import Project from roles.models import Role from .models import Member -@receiver(post_save, sender=Member) -def add_linked_project(sender, instance, created, **kwargs): - if not created: - return - userInstance = instance.user - projectInstance = instance.project - if userInstance and projectInstance: - user = User.objects.get(pk=userInstance.pk) - project = Project.objects.get(pk=projectInstance.pk) - user.projects.add(project) - user.save() - - -@receiver(m2m_changed, sender=Project.users.through) -def remove_mapping_on_remove_user_from_project(sender, instance, action, reverse, **kwargs): - # if reverse is True, pk_set is project_ids and instance is user. - # else, pk_set is user_ids and instance is project. - user_ids = kwargs['pk_set'] - if action.startswith('post_remove') and not reverse: - Member.objects.filter(user__in=user_ids, project=instance).delete() - elif action.startswith('post_add') and not reverse: +def add_administrator_on_project_creation(sender, instance: Project, created: bool, **kwargs): + # In the case of creating a project. + if created: admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN) - Member.objects.bulk_create( - [Member(role=admin_role, project=instance, user_id=user) - for user in user_ids - if not Member.objects.filter(project=instance, user_id=user).exists()] + Member.objects.create( + project=instance, + user=instance.created_by, + role=admin_role, ) - - -@receiver(pre_delete, sender=Member) -def delete_linked_project(sender, instance, using, **kwargs): - userInstance = instance.user - projectInstance = instance.project - if userInstance and projectInstance: - user = User.objects.get(pk=userInstance.pk) - project = Project.objects.get(pk=projectInstance.pk) - user.projects.remove(project) - user.save()