Task display API with permissions working, added test cases

pull/32/head
Piero Toffanin 2016-10-13 12:21:12 -04:00
rodzic d25556ceae
commit 8c6da354b1
8 zmienionych plików z 105 dodań i 15 usunięć

Wyświetl plik

@ -21,14 +21,7 @@ class ProjectViewSet(viewsets.ModelViewSet):
access to view, add, change or delete them.<br/><br/>
- /api/projects/&lt;projectId&gt;/tasks : list all tasks belonging to a project<br/>
- /api/projects/&lt;projectId&gt;/tasks/&lt;taskId&gt; : get task details
"""
filter_fields = ('id', 'owner', 'name')
serializer_class = ProjectSerializer
queryset = models.Project.objects.all()
@detail_route(methods=['get'])
def tasks(self, request, pk=None):
tasks = self.get_object().tasks()
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)

Wyświetl plik

@ -1,5 +1,7 @@
from django.contrib.auth.models import User
from rest_framework import serializers, viewsets, filters
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers, viewsets, filters, exceptions, permissions
from rest_framework.response import Response
from app import models
from nodeodm.models import ProcessingNode
@ -14,3 +16,40 @@ class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = models.Task
class TaskViewSet(viewsets.ViewSet):
"""
TODO: permissions!
"""
queryset = models.Task.objects.all()
# We don't use object level permissions on tasks, relying on
# project's object permissions instead (but standard model permissions still apply)
permission_classes = (permissions.DjangoModelPermissions, )
def get_and_check_project(self, request, project_pk):
'''
Retrieves a project and raises an exeption if the current user
has no access to it.
'''
try:
project = models.Project.objects.get(pk=project_pk)
if not request.user.has_perm('view_project', project): raise ObjectDoesNotExist()
except ObjectDoesNotExist:
raise exceptions.NotFound()
return project
def list(self, request, project_pk=None):
project = self.get_and_check_project(request, project_pk)
tasks = self.queryset.filter(project=project_pk)
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None, project_pk=None):
self.get_and_check_project(request, project_pk)
try:
task = self.queryset.get(pk=pk, project=project_pk)
except ObjectDoesNotExist:
raise exceptions.NotFound()
serializer = TaskSerializer(task)
return Response(serializer.data)

Wyświetl plik

@ -1,11 +1,17 @@
from django.conf.urls import url, include
from .projects import ProjectViewSet
from rest_framework import routers
from .tasks import TaskViewSet
from rest_framework_nested import routers
router = routers.DefaultRouter()
router.register(r'projects', ProjectViewSet)
tasks_router = routers.NestedSimpleRouter(router, r'projects', lookup='project')
tasks_router.register(r'tasks', TaskViewSet, base_name='projects-tasks')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^', include(tasks_router.urls)),
url(r'^auth/', include('rest_framework.urls')),
]

Wyświetl plik

@ -15,7 +15,7 @@ def boot():
logger.info("Created default group")
# Add default permissions (view_project, change_project, delete_project, etc.)
for permission in ('_project'):
for permission in ('_project', '_task'):
default_group.permissions.add(
*list(Permission.objects.filter(codename__endswith=permission))
)

Wyświetl plik

@ -25,7 +25,7 @@ class Project(models.Model):
def __str__(self):
return self.name
def tasks(self):
def tasks(self, pk=None):
return Task.objects.filter(project=self);
class Meta:
@ -63,7 +63,7 @@ class Task(models.Model):
(50, 'CANCELED')
)
uuid = models.CharField(max_length=255, null=True, blank=True, unique=True, help_text="Unique identifier of the task (as returned by OpenDroneMap's REST API)")
uuid = models.CharField(max_length=255, null=True, blank=True, help_text="Identifier of the task (as returned by OpenDroneMap's REST API)")
project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text="Project that this task belongs to")
name = models.CharField(max_length=255, null=True, blank=True, help_text="A label for the task")
processing_time = models.IntegerField(default=-1, help_text="Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)")
@ -81,6 +81,11 @@ class Task(models.Model):
def __str__(self):
return '{} {}'.format(self.name, self.uuid)
class Meta:
permissions = (
('view_task', 'Can view task'),
)
def image_directory_path(task, filename):
return assets_directory_path(imageUpload.task.id, imageUpload.task.project.id, filename)

Wyświetl plik

@ -2,7 +2,7 @@ from .classes import BootTestCase
from rest_framework.test import APIClient
from rest_framework import status
from app.models import Project
from app.models import Project, Task
from django.contrib.auth.models import User
class TestApi(BootTestCase):
@ -12,7 +12,7 @@ class TestApi(BootTestCase):
def tearDown(self):
pass
def test_project_list(self):
def test_projects(self):
client = APIClient()
user = User.objects.get(username="testuser")
@ -46,7 +46,7 @@ class TestApi(BootTestCase):
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Can filter
res = client.get('/api/projects/?owner=-1')
res = client.get('/api/projects/?owner=999')
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(len(res.data["results"]) == 0)
@ -54,3 +54,48 @@ class TestApi(BootTestCase):
res = client.get('/api/projects/?id={}'.format(other_project.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(len(res.data["results"]) == 0)
# Can access individual project
res = client.get('/api/projects/{}/'.format(project.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(res.data["id"] == project.id)
# Cannot access project for which we have no access to
res = client.get('/api/projects/{}/'.format(other_project.id))
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Create some tasks
task = Task.objects.create(project=project)
task2 = Task.objects.create(project=project)
other_task = Task.objects.create(project=other_project)
# Can list project tasks to a project we have access to
res = client.get('/api/projects/{}/tasks/'.format(project.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(len(res.data) == 2)
# Cannot list project tasks for a project we don't have access to
res = client.get('/api/projects/{}/tasks/'.format(other_project.id))
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Cannot list project tasks for a project that doesn't exist
res = client.get('/api/projects/999/tasks/')
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Can list task details for a task belonging to a project we have access to
res = client.get('/api/projects/{}/tasks/{}/'.format(project.id, task.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(res.data["id"] == task.id)
# Cannot list task details for a task belonging to a project we don't have access to
res = client.get('/api/projects/{}/tasks/{}/'.format(other_project.id, other_task.id))
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# As above, but by trying to trick the API by using a project we have access to
res = client.get('/api/projects/{}/tasks/{}/'.format(project.id, other_task.id))
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Cannot access task details for a task that doesn't exist
res = client.get('/api/projects/{}/tasks/999/'.format(project.id, other_task.id))
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)

Wyświetl plik

@ -11,6 +11,7 @@ django-filter==0.15.2
django-guardian==1.4.6
django-webpack-loader==0.3.3
djangorestframework==3.4.7
drf-nested-routers==0.11.1
enum34==1.1.6
fido==3.2.0
functools32==3.2.3.post2

Wyświetl plik

@ -40,6 +40,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'guardian',
'rest_framework',
'rest_framework_nested',
'webpack_loader',
'app',
'nodeodm',