mirror of https://github.com/doccano/doccano.git
16 changed files with 261 additions and 256 deletions
Split View
Diff Options
-
2Pipfile
-
20backend/api/admin.py
-
10backend/api/exceptions.py
-
27backend/api/serializers.py
-
116backend/api/tests/api/test_role.py
-
17backend/api/urls.py
-
61backend/api/views/role.py
-
1backend/app/settings.py
-
1backend/app/urls.py
-
18backend/roles/admin.py
-
12backend/roles/exceptions.py
-
3backend/roles/models.py
-
28backend/roles/serializers.py
-
118backend/roles/tests.py
-
21backend/roles/urls.py
-
62backend/roles/views.py
@ -1,116 +0,0 @@ |
|||
from django.conf import settings |
|||
from rest_framework import status |
|||
from rest_framework.reverse import reverse |
|||
|
|||
from ...models import Role, RoleMapping |
|||
from .utils import CRUDMixin, create_default_roles, make_user, prepare_project |
|||
|
|||
|
|||
class TestRoleAPI(CRUDMixin): |
|||
|
|||
@classmethod |
|||
def setUpTestData(cls): |
|||
create_default_roles() |
|||
cls.user = make_user() |
|||
cls.url = reverse(viewname='roles') |
|||
|
|||
def test_allows_authenticated_user_to_get_roles(self): |
|||
self.assert_fetch(self.user, status.HTTP_200_OK) |
|||
|
|||
def test_disallows_unauthenticated_user_to_get_roles(self): |
|||
self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
|
|||
class TestRoleMappingListAPI(CRUDMixin): |
|||
|
|||
def setUp(self): |
|||
self.project = prepare_project() |
|||
self.non_member = make_user() |
|||
admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN) |
|||
self.data = {'user': self.non_member.id, 'role': admin_role.id, 'project': self.project.item.id} |
|||
self.url = reverse(viewname='rolemapping_list', args=[self.project.item.id]) |
|||
|
|||
def test_allows_project_admin_to_get_mappings(self): |
|||
self.assert_fetch(self.project.users[0], status.HTTP_200_OK) |
|||
|
|||
def test_denies_non_project_admin_to_get_mappings(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_fetch(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_get_mappings(self): |
|||
self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_get_mappings(self): |
|||
self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_allows_project_admin_to_create_mapping(self): |
|||
self.assert_create(self.project.users[0], status.HTTP_201_CREATED) |
|||
|
|||
def test_denies_non_project_admin_to_create_mapping(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_create(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_create_mapping(self): |
|||
self.assert_create(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_create_mapping(self): |
|||
self.assert_create(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
def assert_bulk_delete(self, user=None, expected=status.HTTP_403_FORBIDDEN): |
|||
if user: |
|||
self.client.force_login(user) |
|||
ids = [item.id for item in self.project.item.role_mappings.all()] |
|||
response = self.client.delete(self.url, data={'ids': ids}, format='json') |
|||
self.assertEqual(response.status_code, expected) |
|||
|
|||
def test_allows_project_admin_to_bulk_delete(self): |
|||
self.assert_bulk_delete(self.project.users[0], status.HTTP_204_NO_CONTENT) |
|||
response = self.client.get(self.url) |
|||
self.assertEqual(len(response.data), 1) |
|||
|
|||
def test_denies_non_project_admin_to_bulk_delete(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_bulk_delete(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_bulk_delete(self): |
|||
self.assert_bulk_delete(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_bulk_delete(self): |
|||
self.assert_bulk_delete(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
|
|||
class TestRoleMappingDetailAPI(CRUDMixin): |
|||
|
|||
def setUp(self): |
|||
self.project = prepare_project() |
|||
self.non_member = make_user() |
|||
admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN) |
|||
mapping = RoleMapping.objects.get(user=self.project.users[1]) |
|||
self.url = reverse(viewname='rolemapping_detail', args=[self.project.item.id, mapping.id]) |
|||
self.data = {'role': admin_role.id} |
|||
|
|||
def test_allows_project_admin_to_get_mapping(self): |
|||
self.assert_fetch(self.project.users[0], status.HTTP_200_OK) |
|||
|
|||
def test_denies_non_project_admin_to_get_mapping(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_fetch(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_get_mapping(self): |
|||
self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_get_mapping(self): |
|||
self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_allows_project_admin_to_update_mapping(self): |
|||
self.assert_update(self.project.users[0], status.HTTP_200_OK) |
|||
|
|||
def test_denies_non_project_admin_to_update_mapping(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_update(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_update_mapping(self): |
|||
self.assert_update(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_update_mapping(self): |
|||
self.assert_update(expected=status.HTTP_403_FORBIDDEN) |
@ -1,61 +0,0 @@ |
|||
from django.db import IntegrityError |
|||
from django.shortcuts import get_object_or_404 |
|||
from rest_framework import generics, status |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.response import Response |
|||
|
|||
from ..exceptions import RoleAlreadyAssignedException, RoleConstraintException |
|||
from ..models import Project, Role, RoleMapping |
|||
from ..permissions import IsProjectAdmin |
|||
from ..serializers import RoleMappingSerializer, RoleSerializer |
|||
|
|||
|
|||
class Roles(generics.ListAPIView): |
|||
serializer_class = RoleSerializer |
|||
pagination_class = None |
|||
permission_classes = [IsAuthenticated] |
|||
queryset = Role.objects.all() |
|||
|
|||
|
|||
class RoleMappingList(generics.ListCreateAPIView): |
|||
serializer_class = RoleMappingSerializer |
|||
pagination_class = None |
|||
permission_classes = [IsAuthenticated & IsProjectAdmin] |
|||
|
|||
@property |
|||
def project(self): |
|||
return get_object_or_404(Project, pk=self.kwargs['project_id']) |
|||
|
|||
def get_queryset(self): |
|||
return self.project.role_mappings |
|||
|
|||
def perform_create(self, serializer): |
|||
try: |
|||
serializer.save(project=self.project) |
|||
except IntegrityError: |
|||
raise RoleAlreadyAssignedException |
|||
|
|||
def delete(self, request, *args, **kwargs): |
|||
delete_ids = request.data['ids'] |
|||
RoleMapping.objects.filter(project=self.project, pk__in=delete_ids)\ |
|||
.exclude(user=self.request.user)\ |
|||
.delete() |
|||
return Response(status=status.HTTP_204_NO_CONTENT) |
|||
|
|||
|
|||
class RoleMappingDetail(generics.RetrieveUpdateAPIView): |
|||
queryset = RoleMapping.objects.all() |
|||
serializer_class = RoleMappingSerializer |
|||
lookup_url_kwarg = 'rolemapping_id' |
|||
permission_classes = [IsAuthenticated & IsProjectAdmin] |
|||
|
|||
def perform_update(self, serializer): |
|||
project_id = self.kwargs['project_id'] |
|||
id = self.kwargs['rolemapping_id'] |
|||
role = serializer.validated_data['role'] |
|||
if not RoleMapping.objects.can_update(project_id, id, role.name): |
|||
raise RoleConstraintException |
|||
try: |
|||
super().perform_update(serializer) |
|||
except IntegrityError: |
|||
raise RoleAlreadyAssignedException |
@ -1,3 +1,19 @@ |
|||
from django.contrib import admin |
|||
|
|||
# Register your models here. |
|||
from api.models import Role, RoleMapping |
|||
|
|||
|
|||
class RoleAdmin(admin.ModelAdmin): |
|||
list_display = ('name', 'description') |
|||
ordering = ('name',) |
|||
search_fields = ('name',) |
|||
|
|||
|
|||
class RoleMappingAdmin(admin.ModelAdmin): |
|||
list_display = ('user', 'role', 'project', ) |
|||
ordering = ('user',) |
|||
search_fields = ('user__username',) |
|||
|
|||
|
|||
admin.site.register(Role, RoleAdmin) |
|||
admin.site.register(RoleMapping, RoleMappingAdmin) |
@ -0,0 +1,12 @@ |
|||
from rest_framework import status |
|||
from rest_framework.exceptions import APIException |
|||
|
|||
|
|||
class RoleConstraintException(APIException): |
|||
status_code = status.HTTP_400_BAD_REQUEST |
|||
default_detail = 'The project needs at least one administrator.' |
|||
|
|||
|
|||
class RoleAlreadyAssignedException(APIException): |
|||
status_code = status.HTTP_400_BAD_REQUEST |
|||
default_detail = 'This user is already assigned to a role in this project.' |
@ -1,3 +0,0 @@ |
|||
from django.db import models |
|||
|
|||
# Create your models here. |
@ -0,0 +1,28 @@ |
|||
from rest_framework import serializers |
|||
|
|||
from api.models import Role, RoleMapping |
|||
|
|||
|
|||
class RoleSerializer(serializers.ModelSerializer): |
|||
class Meta: |
|||
model = Role |
|||
fields = ('id', 'name') |
|||
|
|||
|
|||
class RoleMappingSerializer(serializers.ModelSerializer): |
|||
username = serializers.SerializerMethodField() |
|||
rolename = serializers.SerializerMethodField() |
|||
|
|||
@classmethod |
|||
def get_username(cls, instance): |
|||
user = instance.user |
|||
return user.username if user else None |
|||
|
|||
@classmethod |
|||
def get_rolename(cls, instance): |
|||
role = instance.role |
|||
return role.name if role else None |
|||
|
|||
class Meta: |
|||
model = RoleMapping |
|||
fields = ('id', 'user', 'role', 'username', 'rolename') |
@ -1,3 +1,117 @@ |
|||
from django.test import TestCase |
|||
from django.conf import settings |
|||
from rest_framework import status |
|||
from rest_framework.reverse import reverse |
|||
|
|||
# Create your tests here. |
|||
from api.models import Role, RoleMapping |
|||
from api.tests.api.utils import (CRUDMixin, create_default_roles, make_user, |
|||
prepare_project) |
|||
|
|||
|
|||
class TestRoleAPI(CRUDMixin): |
|||
|
|||
@classmethod |
|||
def setUpTestData(cls): |
|||
create_default_roles() |
|||
cls.user = make_user() |
|||
cls.url = reverse(viewname='roles') |
|||
|
|||
def test_allows_authenticated_user_to_get_roles(self): |
|||
self.assert_fetch(self.user, status.HTTP_200_OK) |
|||
|
|||
def test_disallows_unauthenticated_user_to_get_roles(self): |
|||
self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
|
|||
class TestRoleMappingListAPI(CRUDMixin): |
|||
|
|||
def setUp(self): |
|||
self.project = prepare_project() |
|||
self.non_member = make_user() |
|||
admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN) |
|||
self.data = {'user': self.non_member.id, 'role': admin_role.id, 'project': self.project.item.id} |
|||
self.url = reverse(viewname='rolemapping_list', args=[self.project.item.id]) |
|||
|
|||
def test_allows_project_admin_to_get_mappings(self): |
|||
self.assert_fetch(self.project.users[0], status.HTTP_200_OK) |
|||
|
|||
def test_denies_non_project_admin_to_get_mappings(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_fetch(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_get_mappings(self): |
|||
self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_get_mappings(self): |
|||
self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_allows_project_admin_to_create_mapping(self): |
|||
self.assert_create(self.project.users[0], status.HTTP_201_CREATED) |
|||
|
|||
def test_denies_non_project_admin_to_create_mapping(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_create(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_create_mapping(self): |
|||
self.assert_create(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_create_mapping(self): |
|||
self.assert_create(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
def assert_bulk_delete(self, user=None, expected=status.HTTP_403_FORBIDDEN): |
|||
if user: |
|||
self.client.force_login(user) |
|||
ids = [item.id for item in self.project.item.role_mappings.all()] |
|||
response = self.client.delete(self.url, data={'ids': ids}, format='json') |
|||
self.assertEqual(response.status_code, expected) |
|||
|
|||
def test_allows_project_admin_to_bulk_delete(self): |
|||
self.assert_bulk_delete(self.project.users[0], status.HTTP_204_NO_CONTENT) |
|||
response = self.client.get(self.url) |
|||
self.assertEqual(len(response.data), 1) |
|||
|
|||
def test_denies_non_project_admin_to_bulk_delete(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_bulk_delete(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_bulk_delete(self): |
|||
self.assert_bulk_delete(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_bulk_delete(self): |
|||
self.assert_bulk_delete(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
|
|||
class TestRoleMappingDetailAPI(CRUDMixin): |
|||
|
|||
def setUp(self): |
|||
self.project = prepare_project() |
|||
self.non_member = make_user() |
|||
admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN) |
|||
mapping = RoleMapping.objects.get(user=self.project.users[1]) |
|||
self.url = reverse(viewname='rolemapping_detail', args=[self.project.item.id, mapping.id]) |
|||
self.data = {'role': admin_role.id} |
|||
|
|||
def test_allows_project_admin_to_get_mapping(self): |
|||
self.assert_fetch(self.project.users[0], status.HTTP_200_OK) |
|||
|
|||
def test_denies_non_project_admin_to_get_mapping(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_fetch(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_get_mapping(self): |
|||
self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_get_mapping(self): |
|||
self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_allows_project_admin_to_update_mapping(self): |
|||
self.assert_update(self.project.users[0], status.HTTP_200_OK) |
|||
|
|||
def test_denies_non_project_admin_to_update_mapping(self): |
|||
for member in self.project.users[1:]: |
|||
self.assert_update(member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_non_project_member_to_update_mapping(self): |
|||
self.assert_update(self.non_member, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_denies_unauthenticated_user_to_update_mapping(self): |
|||
self.assert_update(expected=status.HTTP_403_FORBIDDEN) |
@ -0,0 +1,21 @@ |
|||
from django.urls import path |
|||
|
|||
from .views import RoleMappingDetail, RoleMappingList, Roles |
|||
|
|||
urlpatterns = [ |
|||
path( |
|||
route='roles', |
|||
view=Roles.as_view(), |
|||
name='roles' |
|||
), |
|||
path( |
|||
route='projects/<int:project_id>/roles', |
|||
view=RoleMappingList.as_view(), |
|||
name='rolemapping_list' |
|||
), |
|||
path( |
|||
route='projects/<int:project_id>/roles/<int:rolemapping_id>', |
|||
view=RoleMappingDetail.as_view(), |
|||
name='rolemapping_detail' |
|||
) |
|||
] |
@ -1,3 +1,61 @@ |
|||
from django.shortcuts import render |
|||
from django.db import IntegrityError |
|||
from django.shortcuts import get_object_or_404 |
|||
from rest_framework import generics, status |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.response import Response |
|||
|
|||
# Create your views here. |
|||
from api.models import Project, Role, RoleMapping |
|||
from api.permissions import IsProjectAdmin |
|||
from .serializers import RoleMappingSerializer, RoleSerializer |
|||
from .exceptions import RoleAlreadyAssignedException, RoleConstraintException |
|||
|
|||
|
|||
class Roles(generics.ListAPIView): |
|||
serializer_class = RoleSerializer |
|||
pagination_class = None |
|||
permission_classes = [IsAuthenticated] |
|||
queryset = Role.objects.all() |
|||
|
|||
|
|||
class RoleMappingList(generics.ListCreateAPIView): |
|||
serializer_class = RoleMappingSerializer |
|||
pagination_class = None |
|||
permission_classes = [IsAuthenticated & IsProjectAdmin] |
|||
|
|||
@property |
|||
def project(self): |
|||
return get_object_or_404(Project, pk=self.kwargs['project_id']) |
|||
|
|||
def get_queryset(self): |
|||
return self.project.role_mappings |
|||
|
|||
def perform_create(self, serializer): |
|||
try: |
|||
serializer.save(project=self.project) |
|||
except IntegrityError: |
|||
raise RoleAlreadyAssignedException |
|||
|
|||
def delete(self, request, *args, **kwargs): |
|||
delete_ids = request.data['ids'] |
|||
RoleMapping.objects.filter(project=self.project, pk__in=delete_ids)\ |
|||
.exclude(user=self.request.user)\ |
|||
.delete() |
|||
return Response(status=status.HTTP_204_NO_CONTENT) |
|||
|
|||
|
|||
class RoleMappingDetail(generics.RetrieveUpdateAPIView): |
|||
queryset = RoleMapping.objects.all() |
|||
serializer_class = RoleMappingSerializer |
|||
lookup_url_kwarg = 'rolemapping_id' |
|||
permission_classes = [IsAuthenticated & IsProjectAdmin] |
|||
|
|||
def perform_update(self, serializer): |
|||
project_id = self.kwargs['project_id'] |
|||
id = self.kwargs['rolemapping_id'] |
|||
role = serializer.validated_data['role'] |
|||
if not RoleMapping.objects.can_update(project_id, id, role.name): |
|||
raise RoleConstraintException |
|||
try: |
|||
super().perform_update(serializer) |
|||
except IntegrityError: |
|||
raise RoleAlreadyAssignedException |
Write
Preview
Loading…
Cancel
Save