Browse Source

Replace 'isSuperUser' with specific roles required for frontend

pull/333/head
margaretmeehan 6 years ago
parent
commit
8e21d5f23d
11 changed files with 215 additions and 146 deletions
  1. 32
      app/api/models.py
  2. 16
      app/api/permissions.py
  3. 49
      app/api/serializers.py
  4. 190
      app/api/tests/test_api.py
  5. 43
      app/api/views.py
  6. 2
      app/server/static/components/annotation.pug
  7. 9
      app/server/static/components/annotationMixin.js
  8. 9
      app/server/static/components/projects.vue
  9. 2
      app/server/templates/annotation.html
  10. 7
      app/server/views.py
  11. 2
      requirements.txt

32
app/api/models.py

@ -4,6 +4,7 @@ from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save, pre_delete from django.db.models.signals import post_save, pre_save, pre_delete
from django.urls import reverse from django.urls import reverse
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -270,15 +271,44 @@ class RoleMapping(models.Model):
@receiver(post_save, sender=RoleMapping) @receiver(post_save, sender=RoleMapping)
def add_linked_project(sender, instance, created, **kwargs): def add_linked_project(sender, instance, created, **kwargs):
if not created:
return
userInstance = instance.user userInstance = instance.user
projectInstance = instance.project projectInstance = instance.project
if created and userInstance and projectInstance:
if userInstance and projectInstance:
user = User.objects.get(pk=userInstance.pk) user = User.objects.get(pk=userInstance.pk)
project = Project.objects.get(pk=projectInstance.pk) project = Project.objects.get(pk=projectInstance.pk)
user.projects.add(project) user.projects.add(project)
user.save() user.save()
@receiver(post_save)
def add_superusers_to_project(sender, instance, created, **kwargs):
if not created:
return
if sender not in Project.__subclasses__():
return
superusers = User.objects.filter(is_superuser=True)
admin_role = Role.objects.filter(name=settings.ROLE_PROJECT_ADMIN).first()
if superusers and admin_role:
RoleMapping.objects.bulk_create(
[RoleMapping(role_id=admin_role.id, user_id=superuser.id, project_id=instance.id)
for superuser in superusers]
)
@receiver(post_save, sender=User)
def add_new_superuser_to_projects(sender, instance, created, **kwargs):
if created and instance.is_superuser:
admin_role = Role.objects.filter(name=settings.ROLE_PROJECT_ADMIN).first()
projects = Project.objects.all()
if admin_role and projects:
RoleMapping.objects.bulk_create(
[RoleMapping(role_id=admin_role.id, user_id=instance.id, project_id=project.id)
for project in projects]
)
@receiver(pre_delete, sender=RoleMapping) @receiver(pre_delete, sender=RoleMapping)
def delete_linked_project(sender, instance, using, **kwargs): def delete_linked_project(sender, instance, using, **kwargs):
userInstance = instance.user userInstance = instance.user

16
app/api/permissions.py

@ -12,13 +12,6 @@ class ProjectMixin:
return view.kwargs.get('project_id') or request.query_params.get('project_id') return view.kwargs.get('project_id') or request.query_params.get('project_id')
class IsProjectUser(ProjectMixin, BasePermission):
def has_permission(self, request, view):
project = get_object_or_404(Project, pk=self.get_project_id(request, view))
return user in project.users.all()
class IsAdminUserAndWriteOnly(BasePermission): class IsAdminUserAndWriteOnly(BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
@ -74,19 +67,20 @@ class IsProjectAdmin(RolePermission):
role_name = settings.ROLE_PROJECT_ADMIN role_name = settings.ROLE_PROJECT_ADMIN
class IsAnnotatorAndCreator(RolePermission):
unsafe_methods_check = False
class IsAnnotatorAndReadOnly(RolePermission):
role_name = settings.ROLE_ANNOTATOR role_name = settings.ROLE_ANNOTATOR
class IsAnnotator(RolePermission): class IsAnnotator(RolePermission):
unsafe_methods_check = False
role_name = settings.ROLE_ANNOTATOR role_name = settings.ROLE_ANNOTATOR
class IsAnnotationApproverAndReadOnly(RolePermission):
role_name = settings.ROLE_ANNOTATION_APPROVER
class IsAnnotationApprover(RolePermission): class IsAnnotationApprover(RolePermission):
unsafe_methods_check = False
role_name = settings.ROLE_ANNOTATION_APPROVER role_name = settings.ROLE_ANNOTATION_APPROVER
def is_in_role(role_name, user_id, project_id): def is_in_role(role_name, user_id, project_id):
return RoleMapping.objects.filter( return RoleMapping.objects.filter(
user_id=user_id, user_id=user_id,

49
app/api/serializers.py

@ -1,4 +1,6 @@
from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from rest_framework import serializers from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer from rest_polymorphic.serializers import PolymorphicSerializer
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
@ -77,39 +79,56 @@ class DocumentSerializer(serializers.ModelSerializer):
class ProjectSerializer(serializers.ModelSerializer): class ProjectSerializer(serializers.ModelSerializer):
current_users_role = serializers.SerializerMethodField()
def get_current_users_role(self, instance):
role_abstractor = {
"is_project_admin": settings.ROLE_PROJECT_ADMIN,
"is_annotator": settings.ROLE_ANNOTATOR,
"is_annotation_approver": settings.ROLE_ANNOTATION_APPROVER,
}
queryset = RoleMapping.objects.values("role_id__name")
if queryset:
users_role = get_object_or_404(
queryset, project=instance.id, user=self.context.get("request").user.id
)
for key, val in role_abstractor.items():
role_abstractor[key] = users_role["role_id__name"] == val
return role_abstractor
class Meta: class Meta:
model = Project model = Project
fields = ('id', 'name', 'description', 'guideline', 'users', 'project_type', 'image', 'updated_at',
'randomize_document_order', 'collaborative_annotation')
read_only_fields = ('image', 'updated_at')
fields = ('id', 'name', 'description', 'guideline', 'users', 'current_users_role', 'project_type', 'image',
'updated_at', 'randomize_document_order', 'collaborative_annotation')
read_only_fields = ('image', 'updated_at', 'current_users_role')
class TextClassificationProjectSerializer(serializers.ModelSerializer):
class TextClassificationProjectSerializer(ProjectSerializer):
class Meta: class Meta:
model = TextClassificationProject model = TextClassificationProject
fields = ('id', 'name', 'description', 'guideline', 'users', 'project_type', 'image', 'updated_at',
'randomize_document_order')
read_only_fields = ('image', 'updated_at', 'users')
fields = ('id', 'name', 'description', 'guideline', 'users', 'current_users_role', 'project_type', 'image',
'updated_at', 'randomize_document_order')
read_only_fields = ('image', 'updated_at', 'users', 'current_users_role')
class SequenceLabelingProjectSerializer(serializers.ModelSerializer):
class SequenceLabelingProjectSerializer(ProjectSerializer):
class Meta: class Meta:
model = SequenceLabelingProject model = SequenceLabelingProject
fields = ('id', 'name', 'description', 'guideline', 'users', 'project_type', 'image', 'updated_at',
'randomize_document_order')
read_only_fields = ('image', 'updated_at', 'users')
fields = ('id', 'name', 'description', 'guideline', 'users', 'current_users_role', 'project_type', 'image',
'updated_at', 'randomize_document_order')
read_only_fields = ('image', 'updated_at', 'users', 'current_users_role')
class Seq2seqProjectSerializer(serializers.ModelSerializer):
class Seq2seqProjectSerializer(ProjectSerializer):
class Meta: class Meta:
model = Seq2seqProject model = Seq2seqProject
fields = ('id', 'name', 'description', 'guideline', 'users', 'project_type', 'image', 'updated_at',
'randomize_document_order')
read_only_fields = ('image', 'updated_at', 'users')
fields = ('id', 'name', 'description', 'guideline', 'users', 'current_users_role', 'project_type', 'image',
'updated_at', 'randomize_document_order')
read_only_fields = ('image', 'updated_at', 'users', 'current_users_role')
class ProjectPolymorphicSerializer(PolymorphicSerializer): class ProjectPolymorphicSerializer(PolymorphicSerializer):

190
app/api/tests/test_api.py

@ -38,29 +38,43 @@ class TestProjectListAPI(APITestCase):
cls.main_project_member_pass = 'project_member_pass' cls.main_project_member_pass = 'project_member_pass'
cls.sub_project_member_name = 'sub_project_member_name' cls.sub_project_member_name = 'sub_project_member_name'
cls.sub_project_member_pass = 'sub_project_member_pass' cls.sub_project_member_pass = 'sub_project_member_pass'
cls.approver_name = 'approver_name_name'
cls.approver_pass = 'approver_pass'
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
create_default_roles()
main_project_member = User.objects.create_user(username=cls.main_project_member_name, main_project_member = User.objects.create_user(username=cls.main_project_member_name,
password=cls.main_project_member_pass) password=cls.main_project_member_pass)
sub_project_member = User.objects.create_user(username=cls.sub_project_member_name, sub_project_member = User.objects.create_user(username=cls.sub_project_member_name,
password=cls.sub_project_member_pass) password=cls.sub_project_member_pass)
# Todo: change super_user to project_admin.
super_user = User.objects.create_superuser(username=cls.super_user_name,
approver = User.objects.create_user(username=cls.approver_name,
password=cls.approver_pass)
project_admin = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass, password=cls.super_user_pass,
email='fizz@buzz.com') email='fizz@buzz.com')
cls.main_project = mommy.make('TextClassificationProject', users=[main_project_member]) cls.main_project = mommy.make('TextClassificationProject', users=[main_project_member])
cls.sub_project = mommy.make('TextClassificationProject', users=[sub_project_member]) cls.sub_project = mommy.make('TextClassificationProject', users=[sub_project_member])
create_default_roles()
assign_user_to_role(project_member=main_project_member, project=cls.main_project, assign_user_to_role(project_member=main_project_member, project=cls.main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
assign_user_to_role(project_member=sub_project_member, project=cls.sub_project, assign_user_to_role(project_member=sub_project_member, project=cls.sub_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
assign_user_to_role(project_member=approver, project=cls.main_project,
role_name=settings.ROLE_ANNOTATION_APPROVER)
cls.url = reverse(viewname='project_list') cls.url = reverse(viewname='project_list')
cls.data = {'name': 'example', 'project_type': 'DocumentClassification', cls.data = {'name': 'example', 'project_type': 'DocumentClassification',
'description': 'example', 'guideline': 'example', 'description': 'example', 'guideline': 'example',
'resourcetype': 'TextClassificationProject'} 'resourcetype': 'TextClassificationProject'}
cls.num_project = main_project_member.projects.count() cls.num_project = main_project_member.projects.count()
def test_returns_main_project_to_approver(self):
self.client.login(username=self.approver_name,
password=self.approver_pass)
response = self.client.get(self.url, format='json')
project = response.data[0]
num_project = len(response.data)
self.assertEqual(num_project, self.num_project)
self.assertEqual(project['id'], self.main_project.id)
def test_returns_main_project_to_main_project_member(self): def test_returns_main_project_to_main_project_member(self):
self.client.login(username=self.main_project_member_name, self.client.login(username=self.main_project_member_name,
password=self.main_project_member_pass) password=self.main_project_member_pass)
@ -105,23 +119,25 @@ class TestProjectDetailAPI(APITestCase):
cls.project_member_pass = 'project_member_pass' cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name' cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass'
cls.admin_user_name = 'admin_user_name'
cls.admin_user_pass = 'admin_user_pass'
create_default_roles()
cls.project_member = User.objects.create_user(username=cls.project_member_name, cls.project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
password=cls.non_project_member_pass) password=cls.non_project_member_pass)
super_user = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass,
project_admin = User.objects.create_superuser(username=cls.admin_user_name,
password=cls.admin_user_pass,
email='fizz@buzz.com') email='fizz@buzz.com')
cls.main_project = mommy.make('TextClassificationProject', users=[cls.project_member, super_user])
cls.main_project = mommy.make('TextClassificationProject', users=[cls.project_member, project_admin])
mommy.make('TextClassificationProject', users=[non_project_member]) mommy.make('TextClassificationProject', users=[non_project_member])
cls.url = reverse(viewname='project_detail', args=[cls.main_project.id]) cls.url = reverse(viewname='project_detail', args=[cls.main_project.id])
cls.data = {'description': 'lorem'} cls.data = {'description': 'lorem'}
create_default_roles()
assign_user_to_role(project_member=cls.project_member, project=cls.main_project, assign_user_to_role(project_member=cls.project_member, project=cls.main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
assign_user_to_role(project_member=project_admin, project=cls.main_project,
role_name=settings.ROLE_PROJECT_ADMIN)
def test_returns_main_project_detail_to_main_project_member(self): def test_returns_main_project_detail_to_main_project_member(self):
self.client.login(username=self.project_member_name, self.client.login(username=self.project_member_name,
@ -135,9 +151,9 @@ class TestProjectDetailAPI(APITestCase):
response = self.client.get(self.url, format='json') response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_allows_superuser_to_update_project(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
def test_allows_admin_to_update_project(self):
self.client.login(username=self.admin_user_name,
password=self.admin_user_pass)
response = self.client.patch(self.url, format='json', data=self.data) response = self.client.patch(self.url, format='json', data=self.data)
self.assertEqual(response.data['description'], self.data['description']) self.assertEqual(response.data['description'], self.data['description'])
@ -147,9 +163,9 @@ class TestProjectDetailAPI(APITestCase):
response = self.client.patch(self.url, format='json', data=self.data) response = self.client.patch(self.url, format='json', data=self.data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_allows_superuser_to_delete_project(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
def test_allows_admin_to_delete_project(self):
self.client.login(username=self.admin_user_name,
password=self.admin_user_pass)
response = self.client.delete(self.url, format='json') response = self.client.delete(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -172,26 +188,25 @@ class TestLabelListAPI(APITestCase):
cls.project_member_pass = 'project_member_pass' cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name' cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass'
cls.admin_user_name = 'admin_user_name'
cls.admin_user_pass = 'admin_user_pass'
create_default_roles()
cls.project_member = User.objects.create_user(username=cls.project_member_name, cls.project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
password=cls.non_project_member_pass) password=cls.non_project_member_pass)
# Todo: change super_user to project_admin.
super_user = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass,
project_admin = User.objects.create_superuser(username=cls.admin_user_name,
password=cls.admin_user_pass,
email='fizz@buzz.com') email='fizz@buzz.com')
cls.main_project = mommy.make('Project', users=[cls.project_member, super_user])
cls.main_project = mommy.make('Project', users=[cls.project_member, project_admin])
cls.main_project_label = mommy.make('Label', project=cls.main_project) cls.main_project_label = mommy.make('Label', project=cls.main_project)
sub_project = mommy.make('Project', users=[non_project_member]) sub_project = mommy.make('Project', users=[non_project_member])
other_project = mommy.make('Project', users=[super_user])
other_project = mommy.make('Project', users=[project_admin])
mommy.make('Label', project=sub_project) mommy.make('Label', project=sub_project)
cls.url = reverse(viewname='label_list', args=[cls.main_project.id]) cls.url = reverse(viewname='label_list', args=[cls.main_project.id])
cls.other_url = reverse(viewname='label_list', args=[other_project.id]) cls.other_url = reverse(viewname='label_list', args=[other_project.id])
cls.data = {'text': 'example'} cls.data = {'text': 'example'}
create_default_roles()
assign_user_to_role(project_member=cls.project_member, project=cls.main_project, assign_user_to_role(project_member=cls.project_member, project=cls.main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
@ -216,15 +231,15 @@ class TestLabelListAPI(APITestCase):
self.assertEqual(num_labels, len(self.main_project.labels.all())) self.assertEqual(num_labels, len(self.main_project.labels.all()))
self.assertEqual(label['id'], self.main_project_label.id) self.assertEqual(label['id'], self.main_project_label.id)
def test_allows_superuser_to_create_label(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
def test_allows_admin_to_create_label(self):
self.client.login(username=self.admin_user_name,
password=self.admin_user_pass)
response = self.client.post(self.url, format='json', data=self.data) response = self.client.post(self.url, format='json', data=self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_can_create_multiple_labels_without_shortcut_key(self): def test_can_create_multiple_labels_without_shortcut_key(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
self.client.login(username=self.admin_user_name,
password=self.admin_user_pass)
labels = [ labels = [
{'text': 'Ruby', 'prefix_key': None, 'suffix_key': None}, {'text': 'Ruby', 'prefix_key': None, 'suffix_key': None},
{'text': 'PHP', 'prefix_key': None, 'suffix_key': None} {'text': 'PHP', 'prefix_key': None, 'suffix_key': None}
@ -234,8 +249,8 @@ class TestLabelListAPI(APITestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_can_create_same_label_in_multiple_projects(self): def test_can_create_same_label_in_multiple_projects(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
self.client.login(username=self.admin_user_name,
password=self.admin_user_pass)
label = {'text': 'LOC', 'prefix_key': None, 'suffix_key': 'l'} label = {'text': 'LOC', 'prefix_key': None, 'suffix_key': 'l'}
response = self.client.post(self.url, format='json', data=label) response = self.client.post(self.url, format='json', data=label)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
@ -283,6 +298,7 @@ class TestLabelDetailAPI(APITestCase):
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
@ -350,6 +366,7 @@ class TestDocumentListAPI(APITestCase):
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
@ -370,7 +387,6 @@ class TestDocumentListAPI(APITestCase):
cls.url = reverse(viewname='doc_list', args=[cls.main_project.id]) cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
cls.random_order_project_url = reverse(viewname='doc_list', args=[cls.random_order_project.id]) cls.random_order_project_url = reverse(viewname='doc_list', args=[cls.random_order_project.id])
cls.data = {'text': 'example'} cls.data = {'text': 'example'}
create_default_roles()
assign_user_to_role(project_member=project_member, project=cls.main_project, assign_user_to_role(project_member=project_member, project=cls.main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
assign_user_to_role(project_member=project_member, project=cls.random_order_project, assign_user_to_role(project_member=project_member, project=cls.random_order_project,
@ -448,6 +464,7 @@ class TestDocumentDetailAPI(APITestCase):
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
@ -460,7 +477,6 @@ class TestDocumentDetailAPI(APITestCase):
cls.doc = mommy.make('Document', project=project) cls.doc = mommy.make('Document', project=project)
cls.url = reverse(viewname='doc_detail', args=[project.id, cls.doc.id]) cls.url = reverse(viewname='doc_detail', args=[project.id, cls.doc.id])
cls.data = {'text': 'example'} cls.data = {'text': 'example'}
create_default_roles()
assign_user_to_role(project_member=project_member, project=project, assign_user_to_role(project_member=project_member, project=project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
@ -508,34 +524,49 @@ class TestDocumentDetailAPI(APITestCase):
class TestApproveLabelsAPI(APITestCase): class TestApproveLabelsAPI(APITestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.project_member_name = 'project_member_name'
cls.project_member_pass = 'project_member_pass'
cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass'
project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass)
# Todo: change super_user to project_admin.
super_user = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass,
email='fizz@buzz.com')
project = mommy.make('TextClassificationProject', users=[project_member, super_user])
cls.annotator_name = 'annotator_name'
cls.annotator_pass = 'annotator_pass'
cls.approver_name = 'approver_name_name'
cls.approver_pass = 'approver_pass'
cls.project_admin_name = 'project_admin_name'
cls.project_admin_pass = 'project_admin_pass'
annotator = User.objects.create_user(username=cls.annotator_name,
password=cls.annotator_pass)
approver = User.objects.create_user(username=cls.approver_name,
password=cls.approver_pass)
project_admin = User.objects.create_user(username=cls.project_admin_name,
password=cls.project_admin_pass)
project = mommy.make('TextClassificationProject', users=[annotator, approver, project_admin])
cls.doc = mommy.make('Document', project=project) cls.doc = mommy.make('Document', project=project)
cls.url = reverse(viewname='approve_labels', args=[project.id, cls.doc.id]) cls.url = reverse(viewname='approve_labels', args=[project.id, cls.doc.id])
create_default_roles() create_default_roles()
assign_user_to_role(project_member=project_member, project=project,
assign_user_to_role(project_member=annotator, project=project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
assign_user_to_role(project_member=approver, project=project,
role_name=settings.ROLE_ANNOTATION_APPROVER)
assign_user_to_role(project_member=project_admin, project=project,
role_name=settings.ROLE_PROJECT_ADMIN)
def test_allows_superuser_to_approve_and_disapprove_labels(self):
self.client.login(username=self.super_user_name, password=self.super_user_pass)
def test_allow_project_admin_to_approve_and_disapprove_labels(self):
self.client.login(username=self.project_admin_name, password=self.project_admin_pass)
response = self.client.post(self.url, format='json', data={'approved': True}) response = self.client.post(self.url, format='json', data={'approved': True})
self.assertEqual(response.data['annotation_approver'], self.super_user_name)
self.assertEqual(response.data['annotation_approver'], self.project_admin_name)
response = self.client.post(self.url, format='json', data={'approved': False})
self.assertIsNone(response.data['annotation_approver'])
def test_allow_approver_to_approve_and_disapprove_labels(self):
self.client.login(username=self.approver_name, password=self.approver_pass)
response = self.client.post(self.url, format='json', data={'approved': True})
self.assertEqual(response.data['annotation_approver'], self.approver_name)
response = self.client.post(self.url, format='json', data={'approved': False}) response = self.client.post(self.url, format='json', data={'approved': False})
self.assertIsNone(response.data['annotation_approver']) self.assertIsNone(response.data['annotation_approver'])
def test_disallows_non_annotation_approver_to_approve_and_disapprove_labels(self): def test_disallows_non_annotation_approver_to_approve_and_disapprove_labels(self):
self.client.login(username=self.project_member_name, password=self.project_member_pass)
self.client.login(username=self.annotator_name, password=self.annotator_pass)
response = self.client.post(self.url, format='json', data={'approved': True}) response = self.client.post(self.url, format='json', data={'approved': True})
@ -556,6 +587,7 @@ class TestAnnotationListAPI(APITestCase):
cls.another_project_member_pass = 'another_project_member_pass' cls.another_project_member_pass = 'another_project_member_pass'
cls.non_project_member_name = 'non_project_member_name' cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
another_project_member = User.objects.create_user(username=cls.another_project_member_name, another_project_member = User.objects.create_user(username=cls.another_project_member_name,
@ -581,8 +613,6 @@ class TestAnnotationListAPI(APITestCase):
document=main_project_doc, document=main_project_doc,
user=another_project_member).count() user=another_project_member).count()
cls.main_project = main_project cls.main_project = main_project
create_default_roles()
assign_user_to_role(project_member=project_member, project=main_project, assign_user_to_role(project_member=project_member, project=main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
@ -651,6 +681,7 @@ class TestAnnotationDetailAPI(APITestCase):
cls.another_project_member_pass = 'another_project_member_pass' cls.another_project_member_pass = 'another_project_member_pass'
cls.non_project_member_name = 'non_project_member_name' cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
another_project_member = User.objects.create_user(username=cls.another_project_member_name, another_project_member = User.objects.create_user(username=cls.another_project_member_name,
@ -677,7 +708,6 @@ class TestAnnotationDetailAPI(APITestCase):
main_project_doc.id, main_project_doc.id,
another_entity.id]) another_entity.id])
cls.post_data = {'start_offset': 0, 'end_offset': 10} cls.post_data = {'start_offset': 0, 'end_offset': 10}
create_default_roles()
assign_user_to_role(project_member=project_member, project=main_project, assign_user_to_role(project_member=project_member, project=main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
@ -748,6 +778,7 @@ class TestSearch(APITestCase):
cls.project_member_pass = 'project_member_pass' cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name' cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
@ -766,7 +797,6 @@ class TestSearch(APITestCase):
mommy.make('Document', text=cls.search_term, project=sub_project) mommy.make('Document', text=cls.search_term, project=sub_project)
cls.url = reverse(viewname='doc_list', args=[cls.main_project.id]) cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
cls.data = {'q': cls.search_term} cls.data = {'q': cls.search_term}
create_default_roles()
assign_user_to_role(project_member=project_member, project=cls.main_project, assign_user_to_role(project_member=project_member, project=cls.main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
@ -825,6 +855,7 @@ class TestFilter(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.project_member_name = 'project_member_name' cls.project_member_name = 'project_member_name'
cls.project_member_pass = 'project_member_pass' cls.project_member_pass = 'project_member_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
cls.main_project = mommy.make('SequenceLabelingProject', users=[project_member]) cls.main_project = mommy.make('SequenceLabelingProject', users=[project_member])
@ -837,7 +868,6 @@ class TestFilter(APITestCase):
mommy.make('SequenceAnnotation', document=doc2, user=project_member, label=cls.label2) mommy.make('SequenceAnnotation', document=doc2, user=project_member, label=cls.label2)
cls.url = reverse(viewname='doc_list', args=[cls.main_project.id]) cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
cls.params = {'seq_annotations__label__id': cls.label1.id} cls.params = {'seq_annotations__label__id': cls.label1.id}
create_default_roles()
assign_user_to_role(project_member=project_member, project=cls.main_project, assign_user_to_role(project_member=project_member, project=cls.main_project,
role_name=settings.ROLE_ANNOTATOR) role_name=settings.ROLE_ANNOTATOR)
@ -882,6 +912,7 @@ class TestUploader(APITestCase):
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
# Todo: change super_user to project_admin. # Todo: change super_user to project_admin.
create_default_roles()
super_user = User.objects.create_superuser(username=cls.super_user_name, super_user = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass, password=cls.super_user_pass,
email='fizz@buzz.com') email='fizz@buzz.com')
@ -890,7 +921,6 @@ class TestUploader(APITestCase):
cls.labeling_project = mommy.make('SequenceLabelingProject', cls.labeling_project = mommy.make('SequenceLabelingProject',
users=[super_user], project_type=SEQUENCE_LABELING) users=[super_user], project_type=SEQUENCE_LABELING)
cls.seq2seq_project = mommy.make('Seq2seqProject', users=[super_user], project_type=SEQ2SEQ) cls.seq2seq_project = mommy.make('Seq2seqProject', users=[super_user], project_type=SEQ2SEQ)
create_default_roles()
assign_user_to_role(project_member=super_user, project=cls.classification_project, assign_user_to_role(project_member=super_user, project=cls.classification_project,
role_name=settings.ROLE_PROJECT_ADMIN) role_name=settings.ROLE_PROJECT_ADMIN)
assign_user_to_role(project_member=super_user, project=cls.labeling_project, assign_user_to_role(project_member=super_user, project=cls.labeling_project,
@ -1119,7 +1149,7 @@ class TestFeatures(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.user_name = 'user_name' cls.user_name = 'user_name'
cls.user_pass = 'user_pass' cls.user_pass = 'user_pass'
create_default_roles()
cls.user = User.objects.create_user(username=cls.user_name, password=cls.user_pass, email='fizz@buzz.com') cls.user = User.objects.create_user(username=cls.user_name, password=cls.user_pass, email='fizz@buzz.com')
def setUp(self): def setUp(self):
@ -1187,6 +1217,7 @@ class TestDownloader(APITestCase):
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
# Todo: change super_user to project_admin. # Todo: change super_user to project_admin.
create_default_roles()
super_user = User.objects.create_superuser(username=cls.super_user_name, super_user = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass, password=cls.super_user_pass,
email='fizz@buzz.com') email='fizz@buzz.com')
@ -1259,6 +1290,7 @@ class TestStatisticsAPI(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
create_default_roles()
# Todo: change super_user to project_admin. # Todo: change super_user to project_admin.
super_user = User.objects.create_superuser(username=cls.super_user_name, super_user = User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass, password=cls.super_user_pass,
@ -1301,6 +1333,7 @@ class TestUserAPI(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.super_user_name = 'super_user_name' cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass' cls.super_user_pass = 'super_user_pass'
create_default_roles()
User.objects.create_superuser(username=cls.super_user_name, User.objects.create_superuser(username=cls.super_user_name,
password=cls.super_user_pass, password=cls.super_user_pass,
email='fizz@buzz.com') email='fizz@buzz.com')
@ -1321,11 +1354,12 @@ class TestRoleAPI(APITestCase):
cls.user_pass = 'user_pass' cls.user_pass = 'user_pass'
cls.project_admin_name = 'project_admin_name' cls.project_admin_name = 'project_admin_name'
cls.project_admin_pass = 'project_admin_pass' cls.project_admin_pass = 'project_admin_pass'
create_default_roles()
cls.user = User.objects.create_user(username=cls.user_name, cls.user = User.objects.create_user(username=cls.user_name,
password=cls.user_pass) password=cls.user_pass)
project_admin = User.objects.create_superuser(username=cls.project_admin_name,
password=cls.project_admin_pass,
email='fizz@buzz.com')
User.objects.create_superuser(username=cls.project_admin_name,
password=cls.project_admin_pass,
email='fizz@buzz.com')
cls.url = reverse(viewname='roles') cls.url = reverse(viewname='roles')
def test_cannot_create_multiple_roles_with_same_name(self): def test_cannot_create_multiple_roles_with_same_name(self):
@ -1370,19 +1404,19 @@ class TestRoleMappingListAPI(APITestCase):
cls.second_project_member_pass = 'second_project_member_pass' cls.second_project_member_pass = 'second_project_member_pass'
cls.project_admin_name = 'project_admin_name' cls.project_admin_name = 'project_admin_name'
cls.project_admin_pass = 'project_admin_pass' cls.project_admin_pass = 'project_admin_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
cls.second_project_member = User.objects.create_user(username=cls.second_project_member_name, cls.second_project_member = User.objects.create_user(username=cls.second_project_member_name,
password=cls.second_project_member_pass) password=cls.second_project_member_pass)
project_admin = User.objects.create_superuser(username=cls.project_admin_name,
password=cls.project_admin_pass,
email='fizz@buzz.com')
project_admin = User.objects.create_user(username=cls.project_admin_name,
password=cls.project_admin_pass)
cls.main_project = mommy.make('Project', users=[project_member, project_admin, cls.second_project_member]) cls.main_project = mommy.make('Project', users=[project_member, project_admin, cls.second_project_member])
cls.other_project = mommy.make('Project', users=[cls.second_project_member, project_admin]) cls.other_project = mommy.make('Project', users=[cls.second_project_member, project_admin])
cls.role = mommy.make('Role', name=settings.ROLE_PROJECT_ADMIN)
rolemapping = mommy.make('RoleMapping', role=cls.role, project=cls.main_project, user=project_admin)
cls.data = {'user': project_member.id, 'role': cls.role.id, 'project': cls.main_project.id}
cls.admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN)
cls.role = mommy.make('Role', name='otherrole')
rolemapping = mommy.make('RoleMapping', role=cls.admin_role, project=cls.main_project, user=project_admin)
cls.data = {'user': project_member.id, 'role': cls.admin_role.id, 'project': cls.main_project.id}
cls.other_url = reverse(viewname='rolemapping_list', args=[cls.other_project.id]) cls.other_url = reverse(viewname='rolemapping_list', args=[cls.other_project.id])
cls.url = reverse(viewname='rolemapping_list', args=[cls.main_project.id]) cls.url = reverse(viewname='rolemapping_list', args=[cls.main_project.id])
@ -1410,18 +1444,6 @@ class TestRoleMappingListAPI(APITestCase):
response = self.client.get(self.url, format='json') response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_can_create_same_mapping_in_multiple_projects(self):
self.client.login(username=self.project_admin_name,
password=self.project_admin_pass)
mapping = [
{'user': self.second_project_member.id, 'role': self.role.id, 'project': self.main_project.id},
{'user': self.second_project_member.id, 'role': self.role.id, 'project': self.other_project.id}
]
response = self.client.post(self.url, format='json', data=mapping[0])
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.post(self.other_url, format='json', data=mapping[1])
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
class TestRoleMappingDetailAPI(APITestCase): class TestRoleMappingDetailAPI(APITestCase):
@ -1433,19 +1455,19 @@ class TestRoleMappingDetailAPI(APITestCase):
cls.project_member_pass = 'project_member_pass' cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name' cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass' cls.non_project_member_pass = 'non_project_member_pass'
project_admin = User.objects.create_superuser(username=cls.project_admin_name,
password=cls.project_admin_pass,
email='fizz@buzz.com')
create_default_roles()
project_admin = User.objects.create_user(username=cls.project_admin_name,
password=cls.project_admin_pass)
project_member = User.objects.create_user(username=cls.project_member_name, project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass) password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name, non_project_member = User.objects.create_user(username=cls.non_project_member_name,
password=cls.non_project_member_pass) password=cls.non_project_member_pass)
project = mommy.make('Project', users=[project_admin, project_member]) project = mommy.make('Project', users=[project_admin, project_member])
role = mommy.make('Role', name=settings.ROLE_PROJECT_ADMIN)
change_role = mommy.make('Role', name=settings.ROLE_ANNOTATOR)
cls.rolemapping = mommy.make('RoleMapping', role=role, project=project, user=project_admin)
admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN)
annotator_role = Role.objects.get(name=settings.ROLE_ANNOTATOR)
cls.rolemapping = mommy.make('RoleMapping', role=admin_role, project=project, user=project_admin)
cls.url = reverse(viewname='rolemapping_detail', args=[project.id, cls.rolemapping.id]) cls.url = reverse(viewname='rolemapping_detail', args=[project.id, cls.rolemapping.id])
cls.data = {'role': change_role.id }
cls.data = {'role': annotator_role.id }
def test_returns_rolemapping_to_project_member(self): def test_returns_rolemapping_to_project_member(self):
self.client.login(username=self.project_admin_name, self.client.login(username=self.project_admin_name,

43
app/api/views.py

@ -15,7 +15,7 @@ from rest_framework_csv.renderers import CSVRenderer
from .filters import DocumentFilter from .filters import DocumentFilter
from .models import Project, Label, Document, RoleMapping, Role from .models import Project, Label, Document, RoleMapping, Role
from .permissions import IsProjectAdmin, IsAnnotator, IsAnnotationApprover, IsAnnotatorAndCreator, IsOwnAnnotation
from .permissions import IsProjectAdmin, IsAnnotatorAndReadOnly, IsAnnotator, IsAnnotationApproverAndReadOnly, IsOwnAnnotation, IsAnnotationApprover
from .serializers import ProjectSerializer, LabelSerializer, DocumentSerializer, UserSerializer from .serializers import ProjectSerializer, LabelSerializer, DocumentSerializer, UserSerializer
from .serializers import ProjectPolymorphicSerializer, RoleMappingSerializer, RoleSerializer from .serializers import ProjectPolymorphicSerializer, RoleMappingSerializer, RoleSerializer
from .utils import CSVParser, ExcelParser, JSONParser, PlainTextParser, CoNLLParser, iterable_to_io from .utils import CSVParser, ExcelParser, JSONParser, PlainTextParser, CoNLLParser, iterable_to_io
@ -23,6 +23,8 @@ from .serializers import ProjectPolymorphicSerializer, RoleMappingSerializer, Ro
from .utils import JSONLRenderer from .utils import JSONLRenderer
from .utils import JSONPainter, CSVPainter from .utils import JSONPainter, CSVPainter
IsInProjectReadOnlyOrAdmin = (IsAnnotatorAndReadOnly | IsAnnotationApproverAndReadOnly | IsProjectAdmin)
IsInProjectOrAdmin = (IsAnnotator | IsAnnotationApprover | IsProjectAdmin)
class Me(APIView): class Me(APIView):
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@ -44,7 +46,7 @@ class Features(APIView):
class ProjectList(generics.ListCreateAPIView): class ProjectList(generics.ListCreateAPIView):
serializer_class = ProjectPolymorphicSerializer serializer_class = ProjectPolymorphicSerializer
pagination_class = None pagination_class = None
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get_queryset(self): def get_queryset(self):
return self.request.user.projects return self.request.user.projects
@ -57,12 +59,12 @@ class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Project.objects.all() queryset = Project.objects.all()
serializer_class = ProjectSerializer serializer_class = ProjectSerializer
lookup_url_kwarg = 'project_id' lookup_url_kwarg = 'project_id'
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class StatisticsAPI(APIView): class StatisticsAPI(APIView):
pagination_class = None pagination_class = None
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
p = get_object_or_404(Project, pk=self.kwargs['project_id']) p = get_object_or_404(Project, pk=self.kwargs['project_id'])
@ -90,7 +92,7 @@ class StatisticsAPI(APIView):
class ApproveLabelsAPI(APIView): class ApproveLabelsAPI(APIView):
permission_classes = (IsAuthenticated, IsAnnotationApprover)
permission_classes = [IsAuthenticated & (IsAnnotationApprover | IsProjectAdmin)]
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
approved = self.request.data.get('approved', True) approved = self.request.data.get('approved', True)
@ -103,7 +105,7 @@ class ApproveLabelsAPI(APIView):
class LabelList(generics.ListCreateAPIView): class LabelList(generics.ListCreateAPIView):
serializer_class = LabelSerializer serializer_class = LabelSerializer
pagination_class = None pagination_class = None
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get_queryset(self): def get_queryset(self):
project = get_object_or_404(Project, pk=self.kwargs['project_id']) project = get_object_or_404(Project, pk=self.kwargs['project_id'])
@ -118,7 +120,7 @@ class LabelDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Label.objects.all() queryset = Label.objects.all()
serializer_class = LabelSerializer serializer_class = LabelSerializer
lookup_url_kwarg = 'label_id' lookup_url_kwarg = 'label_id'
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class DocumentList(generics.ListCreateAPIView): class DocumentList(generics.ListCreateAPIView):
@ -128,7 +130,7 @@ class DocumentList(generics.ListCreateAPIView):
ordering_fields = ('created_at', 'updated_at', 'doc_annotations__updated_at', ordering_fields = ('created_at', 'updated_at', 'doc_annotations__updated_at',
'seq_annotations__updated_at', 'seq2seq_annotations__updated_at') 'seq_annotations__updated_at', 'seq2seq_annotations__updated_at')
filter_class = DocumentFilter filter_class = DocumentFilter
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
def get_queryset(self): def get_queryset(self):
project = get_object_or_404(Project, pk=self.kwargs['project_id']) project = get_object_or_404(Project, pk=self.kwargs['project_id'])
@ -148,12 +150,12 @@ class DocumentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Document.objects.all() queryset = Document.objects.all()
serializer_class = DocumentSerializer serializer_class = DocumentSerializer
lookup_url_kwarg = 'doc_id' lookup_url_kwarg = 'doc_id'
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectReadOnlyOrAdmin]
class AnnotationList(generics.ListCreateAPIView): class AnnotationList(generics.ListCreateAPIView):
pagination_class = None pagination_class = None
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
permission_classes = [IsAuthenticated & IsInProjectOrAdmin]
def get_serializer_class(self): def get_serializer_class(self):
project = get_object_or_404(Project, pk=self.kwargs['project_id']) project = get_object_or_404(Project, pk=self.kwargs['project_id'])
@ -178,17 +180,10 @@ class AnnotationList(generics.ListCreateAPIView):
doc = get_object_or_404(Document, pk=self.kwargs['doc_id']) doc = get_object_or_404(Document, pk=self.kwargs['doc_id'])
serializer.save(document=doc, user=self.request.user) serializer.save(document=doc, user=self.request.user)
def get_permissions(self):
if self.request.method in ('POST'):
self.permission_classes = (IsAnnotatorAndCreator,)
else:
self.permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover),)
return super(AnnotationList, self).get_permissions()
class AnnotationDetail(generics.RetrieveUpdateDestroyAPIView): class AnnotationDetail(generics.RetrieveUpdateDestroyAPIView):
lookup_url_kwarg = 'annotation_id' lookup_url_kwarg = 'annotation_id'
permission_classes = (IsAuthenticated and (IsAnnotator or IsAnnotationApprover) and IsOwnAnnotation,)
permission_classes = [IsAuthenticated & (((IsAnnotator | IsAnnotationApprover) & IsOwnAnnotation) | IsProjectAdmin)]
def get_serializer_class(self): def get_serializer_class(self):
project = get_object_or_404(Project, pk=self.kwargs['project_id']) project = get_object_or_404(Project, pk=self.kwargs['project_id'])
@ -204,7 +199,7 @@ class AnnotationDetail(generics.RetrieveUpdateDestroyAPIView):
class TextUploadAPI(APIView): class TextUploadAPI(APIView):
parser_classes = (MultiPartParser,) parser_classes = (MultiPartParser,)
permission_classes = (IsAuthenticated, IsProjectAdmin)
permission_classes = [IsAuthenticated & IsProjectAdmin]
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'file' not in request.data: if 'file' not in request.data:
@ -295,7 +290,7 @@ class CloudUploadAPI(APIView):
class TextDownloadAPI(APIView): class TextDownloadAPI(APIView):
permission_classes = (IsAuthenticated, IsProjectAdmin)
permission_classes = TextUploadAPI.permission_classes
renderer_classes = (CSVRenderer, JSONLRenderer) renderer_classes = (CSVRenderer, JSONLRenderer)
@ -324,7 +319,7 @@ class TextDownloadAPI(APIView):
class Users(APIView): class Users(APIView):
permission_classes = (IsAuthenticated, IsProjectAdmin)
permission_classes = [IsAuthenticated & IsProjectAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
queryset = User.objects.all() queryset = User.objects.all()
@ -335,14 +330,14 @@ class Users(APIView):
class Roles(generics.ListCreateAPIView): class Roles(generics.ListCreateAPIView):
serializer_class = RoleSerializer serializer_class = RoleSerializer
pagination_class = None pagination_class = None
permission_classes = (IsAuthenticated, IsProjectAdmin)
permission_classes = [IsAuthenticated & IsProjectAdmin]
queryset = Role.objects.all() queryset = Role.objects.all()
class RoleMappingList(generics.ListCreateAPIView): class RoleMappingList(generics.ListCreateAPIView):
serializer_class = RoleMappingSerializer serializer_class = RoleMappingSerializer
pagination_class = None pagination_class = None
permission_classes = (IsAuthenticated, IsProjectAdmin)
permission_classes = [IsAuthenticated & IsProjectAdmin]
def get_queryset(self): def get_queryset(self):
project = get_object_or_404(Project, pk=self.kwargs['project_id']) project = get_object_or_404(Project, pk=self.kwargs['project_id'])
@ -357,4 +352,4 @@ class RoleMappingDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = RoleMapping.objects.all() queryset = RoleMapping.objects.all()
serializer_class = RoleMappingSerializer serializer_class = RoleMappingSerializer
lookup_url_kwarg = 'rolemapping_id' lookup_url_kwarg = 'rolemapping_id'
permission_classes = (IsAuthenticated, IsProjectAdmin)
permission_classes = [IsAuthenticated & IsProjectAdmin]

2
app/server/static/components/annotation.pug

@ -114,7 +114,7 @@ div.columns(v-cloak="")
div.column.is-1.has-text-right div.column.is-1.has-text-right
a.button.tooltip.is-tooltip-bottom( a.button.tooltip.is-tooltip-bottom(
v-if="isSuperuser"
v-if="isAnnotationApprover"
v-on:click="approveDocumentAnnotations" v-on:click="approveDocumentAnnotations"
v-bind:data-tooltip="documentAnnotationsApprovalTooltip" v-bind:data-tooltip="documentAnnotationsApprovalTooltip"
) )

9
app/server/static/components/annotationMixin.js

@ -1,7 +1,7 @@
import * as marked from 'marked'; import * as marked from 'marked';
import VueJsonPretty from 'vue-json-pretty'; import VueJsonPretty from 'vue-json-pretty';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash.isempty';
import HTTP, { defaultHttpClient } from './http';
import HTTP from './http';
import Preview from './preview.vue'; import Preview from './preview.vue';
const getOffsetFromUrl = (url) => { const getOffsetFromUrl = (url) => {
@ -91,7 +91,7 @@ export default {
prevLimit: 0, prevLimit: 0,
paginationPages: 0, paginationPages: 0,
paginationPage: 0, paginationPage: 0,
isSuperuser: false,
isAnnotationApprover: false,
isMetadataActive: false, isMetadataActive: false,
isAnnotationGuidelineActive: false, isAnnotationGuidelineActive: false,
}; };
@ -253,9 +253,8 @@ export default {
}); });
HTTP.get().then((response) => { HTTP.get().then((response) => {
this.guideline = response.data.guideline; this.guideline = response.data.guideline;
});
defaultHttpClient.get('/v1/me').then((response) => {
this.isSuperuser = response.data.is_superuser;
const roles = response.data.current_users_role;
this.isAnnotationApprover = roles.is_annotation_approver || roles.is_project_admin;
}); });
this.submit(); this.submit();
}, },

9
app/server/static/components/projects.vue

@ -122,10 +122,10 @@
td.is-vertical td.is-vertical
span.tag.is-normal {{ project.project_type }} span.tag.is-normal {{ project.project_type }}
td.is-vertical(v-if="isSuperuser")
td.is-vertical(v-if="isProjectAdmin.get(project.id)")
a(v-bind:href="'/projects/' + project.id + '/docs'") Edit a(v-bind:href="'/projects/' + project.id + '/docs'") Edit
td.is-vertical(v-if="isSuperuser")
td.is-vertical(v-if="isProjectAdmin.get(project.id)")
a.has-text-danger(v-on:click="setProject(project)") Delete a.has-text-danger(v-on:click="setProject(project)") Delete
</template> </template>
@ -152,6 +152,7 @@ export default {
isSuperuser: false, isSuperuser: false,
randomizeDocumentOrder: false, randomizeDocumentOrder: false,
collaborativeAnnotation: false, collaborativeAnnotation: false,
isProjectAdmin: null,
}), }),
computed: { computed: {
@ -168,6 +169,10 @@ export default {
this.items = projects.data; this.items = projects.data;
this.username = me.data.username; this.username = me.data.username;
this.isSuperuser = me.data.is_superuser; this.isSuperuser = me.data.is_superuser;
this.isProjectAdmin = new Map(this.items.map((project) => {
const isProjectAdmin = project.current_users_role.is_project_admin;
return [project.id, isProjectAdmin];
}));
}); });
}, },

2
app/server/templates/annotation.html

@ -5,7 +5,7 @@
<link rel="stylesheet" href="{% static 'assets/css/annotation.css' %}"> <link rel="stylesheet" href="{% static 'assets/css/annotation.css' %}">
{% endblock %} {% endblock %}
{% block navigation %} {% block navigation %}
{% if user.is_superuser and 'project_id' in view.kwargs %}
{% if is_project_admin and 'project_id' in view.kwargs %}
<a class="navbar-item" href="{% url 'dataset' view.kwargs.project_id %}"> <a class="navbar-item" href="{% url 'dataset' view.kwargs.project_id %}">
<span class="icon"> <span class="icon">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>

7
app/server/views.py

@ -9,7 +9,7 @@ from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from api.permissions import SuperUserMixin from api.permissions import SuperUserMixin
from api.models import Project
from api.models import Project, RoleMapping
from app import settings from app import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -25,6 +25,11 @@ class ProjectView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
project = get_object_or_404(Project, pk=self.kwargs['project_id']) project = get_object_or_404(Project, pk=self.kwargs['project_id'])
context['is_project_admin'] = RoleMapping.objects.filter(
role_id__name=settings.ROLE_PROJECT_ADMIN,
project=project.id,
user=self.request.user.id
).exists()
context['bundle_name'] = project.get_bundle_name() context['bundle_name'] = project.get_bundle_name()
return context return context

2
requirements.txt

@ -11,7 +11,7 @@ django-widget-tweaks==1.4.2
django-polymorphic==2.0.3 django-polymorphic==2.0.3
django-pyodbc-azure==2.1.0.0 django-pyodbc-azure==2.1.0.0
django-rest-polymorphic==0.1.8 django-rest-polymorphic==0.1.8
djangorestframework==3.8.2
djangorestframework==3.10
djangorestframework-csv==2.1.0 djangorestframework-csv==2.1.0
djangorestframework-filters==0.10.2 djangorestframework-filters==0.10.2
environs==4.1.0 environs==4.1.0

Loading…
Cancel
Save