kopia lustrzana https://github.com/OpenDroneMap/WebODM
Task display API with permissions working, added test cases
rodzic
d25556ceae
commit
8c6da354b1
|
@ -21,14 +21,7 @@ class ProjectViewSet(viewsets.ModelViewSet):
|
|||
access to view, add, change or delete them.<br/><br/>
|
||||
- /api/projects/<projectId>/tasks : list all tasks belonging to a project<br/>
|
||||
- /api/projects/<projectId>/tasks/<taskId> : 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)
|
||||
|
|
|
@ -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)
|
|
@ -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')),
|
||||
]
|
|
@ -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))
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -40,6 +40,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.staticfiles',
|
||||
'guardian',
|
||||
'rest_framework',
|
||||
'rest_framework_nested',
|
||||
'webpack_loader',
|
||||
'app',
|
||||
'nodeodm',
|
||||
|
|
Ładowanie…
Reference in New Issue