Browse Source

Add project API

pull/110/head
Hironsan 6 years ago
parent
commit
a47b853c7c
5 changed files with 80 additions and 95 deletions
  1. 28
      app/server/api.py
  2. 4
      app/server/models.py
  3. 1
      app/server/serializers.py
  4. 139
      app/server/tests/test_api.py
  5. 3
      app/server/urls.py

28
app/server/api.py

@ -14,37 +14,23 @@ from .permissions import IsAdminUserAndWriteOnly, IsProjectUser, IsOwnAnnotation
from .serializers import ProjectSerializer, LabelSerializer, DocumentSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
pagination_class = None
permission_classes = (IsAuthenticated, IsAdminUserAndWriteOnly)
def get_queryset(self):
return self.request.user.projects
@action(methods=['get'], detail=True)
def progress(self, request, pk=None):
project = self.get_object()
return Response(project.get_progress(self.request.user))
class ProjectList(generics.ListCreateAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
pagination_class = None
permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
permission_classes = (IsAuthenticated, IsAdminUserAndWriteOnly)
def get_queryset(self):
return self.request.user.projects
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
request.data['users'] = [self.request.user.id]
return super().create(request, args, kwargs)
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Project.objects.all()
serializer_class = LabelSerializer
serializer_class = ProjectSerializer
lookup_url_kwarg = 'project_id'
permission_classes = (IsAuthenticated, IsProjectUser, IsAdminUserAndWriteOnly)
@ -110,10 +96,6 @@ class DocumentList(generics.ListCreateAPIView):
project = get_object_or_404(Project, pk=self.kwargs['project_id'])
serializer.save(project=project)
# def create(self, request, *args, **kwargs):
# request.data['project'] = kwargs.get('project_id')
# return super().create(request, args, kwargs)
class DocumentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Document.objects.all()

4
app/server/models.py

@ -28,6 +28,10 @@ class Project(models.Model):
def get_absolute_url(self):
return reverse('upload', args=[self.id])
@property
def image(self):
return staticfiles_storage.url('images/cats/text_classification.jpg')
def __str__(self):
return self.name

1
app/server/serializers.py

@ -23,6 +23,7 @@ class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('id', 'name', 'description', 'guideline', 'users', 'project_type', 'image', 'updated_at')
read_only_fields = ('image', 'updated_at')
class ProjectFilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):

139
app/server/tests/test_api.py

@ -2,73 +2,67 @@ from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from mixer.backend.django import mixer
from ..models import User, Project, Label
from ..models import User, Project
class TestProjects(APITestCase):
class TestProjectListAPI(APITestCase):
@classmethod
def setUpTestData(cls):
cls.username = 'user'
cls.password = 'pass'
cls.super_username = 'super'
cls.normal_user = User.objects.create_user(username=cls.username, password=cls.password)
cls.super_user = User.objects.create_superuser(username=cls.super_username,
password=cls.password, email='fizz@buzz.com')
cls.project1 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.normal_user, cls.super_user])
cls.project2 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.super_user])
cls.url = reverse('project-list')
def setUp(self):
self.client.login(username=self.username, password=self.password)
cls.project_member_name = 'project_member_name'
cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass'
cls.project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name,
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,
email='fizz@buzz.com')
cls.main_project = mixer.blend('server.Project')
cls.main_project.users.add(cls.project_member)
cls.main_project.users.add(super_user)
sub_project = mixer.blend('server.Project')
cls.url = reverse(viewname='project_list')
cls.post_data = {'name': 'example', 'project_type': 'Seq2seq',
'description': 'example', 'guideline': 'example'}
def test_get_projects(self):
"""
Ensure user can get project.
"""
def test_returns_projects_to_project_member(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['id'], self.project1.id)
def test_get_progress(self):
"""
Ensure user can get project's progress.
"""
url = '{}{}/progress/'.format(self.url, self.project1.id)
response = self.client.get(url, format='json')
def test_do_not_return_projects_to_non_project_member(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.data, [])
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('total', response.data)
self.assertIn('remaining', response.data)
self.assertIsInstance(response.data['total'], int)
self.assertIsInstance(response.data['remaining'], int)
def test_do_not_return_other_projects(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(len(response.data), self.project_member.projects.count())
def test_superuser_can_delete_project(self):
"""
Ensure superuser can delete a project.
"""
self.assertEqual(Project.objects.count(), 2)
self.client.login(username=self.super_username, password=self.password)
url = '{}{}/'.format(self.url, self.project2.id)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Project.objects.count(), 1)
def test_allows_superuser_to_create_project(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
response = self.client.post(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_normal_user_cannot_delete_project(self):
"""
Ensure normal user cannot delete a project.
"""
self.assertEqual(Project.objects.count(), 2)
url = '{}{}/'.format(self.url, self.project2.id)
response = self.client.delete(url, format='json')
def test_disallows_project_member_to_create_project(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.post(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(Project.objects.count(), 2)
class TestProjectListAPI(APITestCase):
class TestProjectDetailAPI(APITestCase):
@classmethod
def setUpTestData(cls):
@ -78,8 +72,8 @@ class TestProjectListAPI(APITestCase):
cls.non_project_member_pass = 'non_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)
cls.project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass)
non_project_member = User.objects.create_user(username=cls.non_project_member_name,
password=cls.non_project_member_pass)
# Todo: change super_user to project_admin.
@ -87,42 +81,47 @@ class TestProjectListAPI(APITestCase):
password=cls.super_user_pass,
email='fizz@buzz.com')
cls.main_project = mixer.blend('server.Project')
cls.main_project.users.add(project_member)
cls.main_project.users.add(cls.project_member)
cls.main_project.users.add(super_user)
mixer.blend('server.Label', project=cls.main_project)
sub_project = mixer.blend('server.Project')
mixer.blend('server.Label', project=sub_project)
cls.url = reverse(viewname='project_list')
cls.post_data = {'text': 'example'}
cls.url = reverse(viewname='project_detail', args=[cls.main_project.id])
cls.post_data = {'name': 'example', 'project_type': 'Seq2seq',
'description': 'example', 'guideline': 'example'}
def test_returns_projects_to_project_member(self):
def test_returns_project_to_project_member(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['id'], self.main_project.id)
def test_do_not_return_projects_to_non_project_member(self):
def test_do_not_return_project_to_non_project_member(self):
self.client.login(username=self.non_project_member_name,
password=self.non_project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_do_not_return_other_projects(self):
def test_allows_superuser_to_update_project(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
response = self.client.patch(self.url, format='json', data={'description': 'lorem'})
self.assertEqual(response.data['description'], 'lorem')
def test_disallows_project_member_to_update_project(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.get(self.url, format='json')
self.assertEqual(len(response.data), 100)
response = self.client.put(self.url, format='json', data={'description': 'lorem'})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_allows_superuser_to_create_project(self):
def test_allows_superuser_to_delete_project(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
response = self.client.post(self.url, format='json', data=self.post_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.delete(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
def test_disallows_project_member_to_create_project(self):
def test_disallows_project_member_to_delete_project(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
response = self.client.post(self.url, format='json', data=self.post_data)
response = self.client.delete(self.url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

3
app/server/urls.py

@ -5,11 +5,10 @@ from .views import IndexView
from .views import ProjectView, DatasetView, DataUpload, LabelView, StatsView, GuidelineView
from .views import ProjectsView, DataDownload, DataDownloadFile
from .views import DemoTextClassification, DemoNamedEntityRecognition, DemoTranslation
from .api import ProjectViewSet, LabelList, StatisticsAPI, LabelDetail, \
from .api import LabelList, StatisticsAPI, LabelDetail, \
AnnotationList, AnnotationDetail, DocumentList, DocumentDetail, EntityList, EntityDetail, ProjectList, ProjectDetail
router = routers.DefaultRouter()
#router.register(r'projects', ProjectViewSet)
urlpatterns = [

Loading…
Cancel
Save