Browse Source

Merge pull request #1644 from doccano/enhancement/removeUsersFromProject

[Enhancement]Remove users from project
pull/1646/head
Hiroki Nakayama 3 years ago
committed by GitHub
parent
commit
b774a8ab2b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 63 additions and 56 deletions
  1. 8
      backend/api/managers.py
  2. 25
      backend/api/migrations/0029_auto_20220119_2333.py
  3. 6
      backend/api/models.py
  4. 3
      backend/api/serializers.py
  5. 2
      backend/api/tests/api/utils.py
  6. 2
      backend/api/tests/test_models.py
  7. 4
      backend/api/views/project.py
  8. 7
      backend/members/apps.py
  9. 4
      backend/members/models.py
  10. 46
      backend/members/signals.py
  11. 12
      backend/metrics/views.py

8
backend/api/managers.py

@ -3,22 +3,22 @@ from django.db.models import Count, Manager
class AnnotationManager(Manager):
def calc_label_distribution(self, examples, users, labels):
def calc_label_distribution(self, examples, members, labels):
"""Calculate label distribution.
Args:
examples: example queryset.
users: user queryset.
members: user queryset.
labels: label queryset.
Returns:
label distribution per user.
Examples:
>>> self.calc_label_distribution(examples, users, labels)
>>> self.calc_label_distribution(examples, members, labels)
{'admin': {'positive': 10, 'negative': 5}}
"""
distribution = {user.username: {label.text: 0 for label in labels} for user in users}
distribution = {member.username: {label.text: 0 for label in labels} for member in members}
items = self.filter(example_id__in=examples)\
.values('user__username', 'label__text')\
.annotate(count=Count('label__text'))

25
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),
),
]

6
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)

3
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',

2
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
)

2
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}

4
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']

7
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)

4
backend/members/models.py

@ -61,5 +61,9 @@ class Member(models.Model):
message = 'This user is already assigned to a role in this project.'
raise ValidationError(message)
@property
def username(self):
return self.user.username
class Meta:
unique_together = ('user', 'project')

46
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()

12
backend/metrics/views.py

@ -1,12 +1,12 @@
import abc
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from api.models import Example, ExampleState, Project, Annotation, Label, Category, CategoryType, Span, SpanType
from api.models import Example, ExampleState, Annotation, Label, Category, CategoryType, Span, SpanType
from members.models import Member
from members.permissions import IsInProjectReadOnlyOrAdmin
@ -24,9 +24,9 @@ class MemberProgressAPI(APIView):
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get(self, request, *args, **kwargs):
project = get_object_or_404(Project, pk=self.kwargs['project_id'])
examples = Example.objects.filter(project=self.kwargs['project_id']).values('id')
data = ExampleState.objects.measure_member_progress(examples, project.users.all())
members = Member.objects.filter(project=self.kwargs['project_id'])
data = ExampleState.objects.measure_member_progress(examples, members)
return Response(data=data, status=status.HTTP_200_OK)
@ -36,10 +36,10 @@ class LabelDistribution(abc.ABC, APIView):
label_type = Label
def get(self, request, *args, **kwargs):
project = get_object_or_404(Project, pk=self.kwargs['project_id'])
labels = self.label_type.objects.filter(project=self.kwargs['project_id'])
examples = Example.objects.filter(project=self.kwargs['project_id']).values('id')
data = self.model.objects.calc_label_distribution(examples, project.users.all(), labels)
members = Member.objects.filter(project=self.kwargs['project_id'])
data = self.model.objects.calc_label_distribution(examples, members, labels)
return Response(data=data, status=status.HTTP_200_OK)

Loading…
Cancel
Save