kopia lustrzana https://github.com/OpenDroneMap/WebODM
				
				
				
			Added /processingnodes/options/ API, unit tests
							rodzic
							
								
									2461e0701c
								
							
						
					
					
						commit
						e14fc574a6
					
				| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
import django_filters
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django_filters.rest_framework import FilterSet
 | 
			
		||||
from rest_framework import serializers, viewsets
 | 
			
		||||
from guardian.shortcuts import get_objects_for_user
 | 
			
		||||
from rest_framework import serializers, viewsets, exceptions
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.views import APIView
 | 
			
		||||
 | 
			
		||||
from nodeodm.models import ProcessingNode
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,4 +39,47 @@ class ProcessingNodeViewSet(viewsets.ModelViewSet):
 | 
			
		|||
 | 
			
		||||
    pagination_class = None
 | 
			
		||||
    serializer_class = ProcessingNodeSerializer
 | 
			
		||||
    queryset = ProcessingNode.objects.all()
 | 
			
		||||
    queryset = ProcessingNode.objects.all()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProcessingNodeOptionsView(APIView):
 | 
			
		||||
    """
 | 
			
		||||
    Display the intersection of all ProcessingNode's available_options fields.
 | 
			
		||||
    Each ProcessingNode has its own set of available_options. When a user relies on the
 | 
			
		||||
    automatic node selection feature, it's better to have a list of available_options that
 | 
			
		||||
    is common among all ProcessingNode.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    queryset = ProcessingNode.objects.all()
 | 
			
		||||
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
 | 
			
		||||
        nodes = get_objects_for_user(request.user, 'view_processingnode', ProcessingNode, accept_global_perms=False)
 | 
			
		||||
 | 
			
		||||
        common_options = []
 | 
			
		||||
 | 
			
		||||
        for node in nodes:
 | 
			
		||||
            # Skip offline nodes
 | 
			
		||||
            if not node.is_online():
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # First? Just populate
 | 
			
		||||
            if len(common_options) == 0 and len(node.available_options) > 0:
 | 
			
		||||
                common_options = node.available_options
 | 
			
		||||
            else:
 | 
			
		||||
                # Remove all options that are in common_options,
 | 
			
		||||
                # but that are not in node.available_options
 | 
			
		||||
                for common_option in common_options:
 | 
			
		||||
                    found = False
 | 
			
		||||
                    for option in node.available_options:
 | 
			
		||||
                        if common_option['name'] == option['name']:
 | 
			
		||||
                            found = True
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                    # Mark for deletion
 | 
			
		||||
                    if not found:
 | 
			
		||||
                        common_option['_delete'] = True
 | 
			
		||||
 | 
			
		||||
        common_options = [co for co in common_options if not '_delete' in co]
 | 
			
		||||
 | 
			
		||||
        return Response(common_options)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
from django.conf.urls import url, include
 | 
			
		||||
from .projects import ProjectViewSet
 | 
			
		||||
from .tasks import TaskViewSet, TaskTiles, TaskTilesJson, TaskDownloads, TaskAssets
 | 
			
		||||
from .processingnodes import ProcessingNodeViewSet
 | 
			
		||||
from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView
 | 
			
		||||
from rest_framework_nested import routers
 | 
			
		||||
from rest_framework_jwt.views import obtain_jwt_token
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,8 @@ tasks_router = routers.NestedSimpleRouter(router, r'projects', lookup='project')
 | 
			
		|||
tasks_router.register(r'tasks', TaskViewSet, base_name='projects-tasks')
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'processingnodes/options/$', ProcessingNodeOptionsView.as_view()),
 | 
			
		||||
 | 
			
		||||
    url(r'^', include(router.urls)),
 | 
			
		||||
    url(r'^', include(tasks_router.urls)),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,15 @@
 | 
			
		|||
import datetime
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from guardian.shortcuts import assign_perm
 | 
			
		||||
from guardian.shortcuts import assign_perm, get_objects_for_user
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from rest_framework import status
 | 
			
		||||
from rest_framework.test import APIClient
 | 
			
		||||
from rest_framework_jwt.settings import api_settings
 | 
			
		||||
 | 
			
		||||
from app import pending_actions
 | 
			
		||||
from app.models import Project, Task
 | 
			
		||||
from nodeodm.models import ProcessingNode
 | 
			
		||||
from nodeodm.models import ProcessingNode, OFFLINE_MINUTES
 | 
			
		||||
from .classes import BootTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -237,6 +238,10 @@ class TestApi(BootTestCase):
 | 
			
		|||
        res = client.get('/api/processingnodes/{}/'.format(pnode.id))
 | 
			
		||||
        self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)
 | 
			
		||||
 | 
			
		||||
        # Cannot get options as guest
 | 
			
		||||
        res = client.get('/api/processingnodes/options/')
 | 
			
		||||
        self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)
 | 
			
		||||
 | 
			
		||||
        client.login(username="testuser", password="test1234")
 | 
			
		||||
 | 
			
		||||
        # Cannot list processing nodes, unless permissions have been granted
 | 
			
		||||
| 
						 | 
				
			
			@ -307,6 +312,39 @@ class TestApi(BootTestCase):
 | 
			
		|||
        self.assertTrue(len(res.data) == 2)
 | 
			
		||||
        self.assertTrue(res.data[1]["port"] == 1000)
 | 
			
		||||
 | 
			
		||||
        # Test available_options intersection
 | 
			
		||||
        # (with normal user)
 | 
			
		||||
        client.login(username="testuser", password="test1234")
 | 
			
		||||
        user = User.objects.get(username="testuser")
 | 
			
		||||
        self.assertFalse(user.is_superuser)
 | 
			
		||||
 | 
			
		||||
        p1 = ProcessingNode.objects.create(hostname="invalid-host", port=11223,
 | 
			
		||||
                                           last_refreshed=timezone.now(),
 | 
			
		||||
                                           available_options=[{'name': 'a'}, {'name': 'b'}])
 | 
			
		||||
        p2 = ProcessingNode.objects.create(hostname="invalid-host-2", port=11223,
 | 
			
		||||
                                           last_refreshed=timezone.now(),
 | 
			
		||||
                                           available_options=[{'name': 'a'}, {'name': 'c'}])
 | 
			
		||||
        p3 = ProcessingNode.objects.create(hostname="invalid-host-3", port=11223,
 | 
			
		||||
                                           last_refreshed=timezone.now(),
 | 
			
		||||
                                           available_options=[{'name': 'd'}])
 | 
			
		||||
        p4 = ProcessingNode.objects.create(hostname="invalid-host-4", port=11223,
 | 
			
		||||
                                           last_refreshed=timezone.now() - datetime.timedelta(minutes=OFFLINE_MINUTES * 2),
 | 
			
		||||
                                           available_options=[{'name': 'd'}]) # offline
 | 
			
		||||
 | 
			
		||||
        assign_perm('view_processingnode', user, p1)
 | 
			
		||||
        assign_perm('view_processingnode', user, p2)
 | 
			
		||||
        assign_perm('view_processingnode', user, p4)
 | 
			
		||||
        self.assertFalse(user.has_perm('view_processingnode', p3))
 | 
			
		||||
 | 
			
		||||
        nodes_available = get_objects_for_user(user, 'view_processingnode', ProcessingNode, accept_global_perms=False).exclude(available_options=dict())
 | 
			
		||||
        self.assertTrue(len(nodes_available) == 3)
 | 
			
		||||
 | 
			
		||||
        res = client.get('/api/processingnodes/options/')
 | 
			
		||||
        self.assertEqual(res.status_code, status.HTTP_200_OK)
 | 
			
		||||
        self.assertTrue(len(res.data) == 1)
 | 
			
		||||
        self.assertTrue(res.data[0]['name'] == 'a')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_token_auth(self):
 | 
			
		||||
        client = APIClient()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ from django.db.models import signals
 | 
			
		|||
from datetime import datetime, timedelta
 | 
			
		||||
from .exceptions import ProcessingError, ProcessingTimeout
 | 
			
		||||
import simplejson
 | 
			
		||||
import django.utils.timezone
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def api(func):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue