From 980ee89501ad6013ad0a37a42aabffb482557955 Mon Sep 17 00:00:00 2001 From: Owen Kaluza Date: Fri, 18 Mar 2022 17:51:55 +1100 Subject: [PATCH 1/2] Update Django to 2.2.27 and drf to 3.13.1 Required some fixes: - detail_route replaced with action(detail=True) - base_name replaced with basename - handler404 requires exception arg - DjangoObjectPermissionsFilter deprecated and moved to djangorestframework-guardian, now called ObjectPermissionsFilter --- app/api/projects.py | 10 +++++----- app/api/tasks.py | 16 ++++++++-------- app/api/urls.py | 8 ++++---- app/views/app.py | 4 ++-- requirements.txt | 10 +++++----- webodm/settings.py | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/api/projects.py b/app/api/projects.py index 48c25623..6673ad13 100644 --- a/app/api/projects.py +++ b/app/api/projects.py @@ -1,6 +1,6 @@ from guardian.shortcuts import get_perms, get_users_with_perms, assign_perm, remove_perm from rest_framework import serializers, viewsets -from rest_framework.decorators import detail_route +from rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status from django.db import transaction @@ -53,7 +53,7 @@ class ProjectViewSet(viewsets.ModelViewSet): return None return super().paginate_queryset(queryset) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def duplicate(self, request, pk=None): """ Duplicate a task @@ -66,7 +66,7 @@ class ProjectViewSet(viewsets.ModelViewSet): else: return Response({'error': _("Cannot duplicate project")}, status=status.HTTP_200_OK) - @detail_route(methods=['get']) + @action(detail=True, methods=['get']) def permissions(self, request, pk=None): project = get_and_check_project(request, pk, ('change_project', )) @@ -81,7 +81,7 @@ class ProjectViewSet(viewsets.ModelViewSet): result.sort(key=lambda r: r['owner'], reverse=True) return Response(result, status=status.HTTP_200_OK) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def edit(self, request, pk=None): project = get_and_check_project(request, pk, ('change_project', )) @@ -135,4 +135,4 @@ class ProjectViewSet(viewsets.ModelViewSet): except AttributeError as e: return Response({'error': _("Invalid permissions")}, status=status.HTTP_400_BAD_REQUEST) - return Response({'success': True}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({'success': True}, status=status.HTTP_200_OK) diff --git a/app/api/tasks.py b/app/api/tasks.py index bb36f1b6..e1b8d3ba 100644 --- a/app/api/tasks.py +++ b/app/api/tasks.py @@ -10,7 +10,7 @@ from django.db import transaction from django.http import FileResponse from django.http import HttpResponse from rest_framework import status, serializers, viewsets, filters, exceptions, permissions, parsers -from rest_framework.decorators import detail_route +from rest_framework.decorators import action from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView @@ -117,19 +117,19 @@ class TaskViewSet(viewsets.ViewSet): return Response({'success': True}) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def cancel(self, *args, **kwargs): return self.set_pending_action(pending_actions.CANCEL, *args, **kwargs) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def restart(self, *args, **kwargs): return self.set_pending_action(pending_actions.RESTART, *args, **kwargs) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def remove(self, *args, **kwargs): return self.set_pending_action(pending_actions.REMOVE, *args, perms=('delete_project', ), **kwargs) - @detail_route(methods=['get']) + @action(detail=True, methods=['get']) def output(self, request, pk=None, project_pk=None): """ Retrieve the console output for this task. @@ -165,7 +165,7 @@ class TaskViewSet(viewsets.ViewSet): serializer = TaskSerializer(task) return Response(serializer.data) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def commit(self, request, pk=None, project_pk=None): """ Commit a task after all images have been uploaded @@ -188,7 +188,7 @@ class TaskViewSet(viewsets.ViewSet): serializer = TaskSerializer(task) return Response(serializer.data, status=status.HTTP_200_OK) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def upload(self, request, pk=None, project_pk=None): """ Add images to a task @@ -216,7 +216,7 @@ class TaskViewSet(viewsets.ViewSet): return Response({'success': True}, status=status.HTTP_200_OK) - @detail_route(methods=['post']) + @action(detail=True, methods=['post']) def duplicate(self, request, pk=None, project_pk=None): """ Duplicate a task diff --git a/app/api/urls.py b/app/api/urls.py index 8f274a92..f8c34f3f 100644 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -18,14 +18,14 @@ from webodm import settings router = routers.DefaultRouter() router.register(r'projects', ProjectViewSet) router.register(r'processingnodes', ProcessingNodeViewSet) -router.register(r'presets', PresetViewSet, base_name='presets') +router.register(r'presets', PresetViewSet, basename='presets') tasks_router = routers.NestedSimpleRouter(router, r'projects', lookup='project') -tasks_router.register(r'tasks', TaskViewSet, base_name='projects-tasks') +tasks_router.register(r'tasks', TaskViewSet, basename='projects-tasks') admin_router = routers.DefaultRouter() -admin_router.register(r'admin/users', AdminUserViewSet, base_name='admin-users') -admin_router.register(r'admin/groups', AdminGroupViewSet, base_name='admin-groups') +admin_router.register(r'admin/users', AdminUserViewSet, basename='admin-users') +admin_router.register(r'admin/groups', AdminGroupViewSet, basename='admin-groups') urlpatterns = [ url(r'processingnodes/options/$', ProcessingNodeOptionsView.as_view()), diff --git a/app/views/app.py b/app/views/app.py index f9aeff69..58dbc907 100644 --- a/app/views/app.py +++ b/app/views/app.py @@ -151,8 +151,8 @@ def welcome(request): }) -def handler404(request): +def handler404(request, exception): return render(request, '404.html', status=404) def handler500(request): - return render(request, '500.html', status=500) \ No newline at end of file + return render(request, '500.html', status=500) diff --git a/requirements.txt b/requirements.txt index 0665e223..9620e183 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,20 +3,21 @@ appdirs==1.4.0 APScheduler==3.2.0 billiard==3.6.3.0 celery==4.4.0 -coreapi==2.0.9 -Django==2.1.15 +coreapi>=2.3.3 +Django==2.2.27 django-appconf==1.0.2 django-codemirror2==0.2 django-colorfield==0.1.15 django-compressor==2.2 django-cors-headers==3.0.2 -django-filter==2.0.0 +django-filter==2.4.0 django-guardian==1.4.9 django-imagekit==4.0.1 django-libsass==0.7 django-webpack-loader==0.6.0 -djangorestframework==3.9.1 +djangorestframework==3.13.1 djangorestframework-jwt==1.9.0 +djangorestframework-guardian==0.3.0 drf-nested-routers==0.11.1 funcsigs==1.0.2 futures==3.1.1 @@ -33,7 +34,6 @@ piexif==1.0.13 pilkit==2.0 Pillow==8.3.2 pip-autoremove==0.9.0 -psycopg2==2.8.6 psycopg2-binary==2.8.6 PyJWT==1.5.3 pyodm==1.5.9 diff --git a/webodm/settings.py b/webodm/settings.py index f9674a19..bf4f7e27 100644 --- a/webodm/settings.py +++ b/webodm/settings.py @@ -311,7 +311,7 @@ REST_FRAMEWORK = { 'rest_framework.permissions.DjangoObjectPermissions', ], 'DEFAULT_FILTER_BACKENDS': [ - 'rest_framework.filters.DjangoObjectPermissionsFilter', + 'rest_framework_guardian.filters.ObjectPermissionsFilter', 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter', ], From 6000c674f5b5fc6d52e2a6c365ee0fe5490bb251 Mon Sep 17 00:00:00 2001 From: Owen Kaluza Date: Fri, 18 Mar 2022 17:54:46 +1100 Subject: [PATCH 2/2] Use drf-yasg to generate swagger openapi 2.0 API docs https://drf-yasg.readthedocs.io/en/stable/readme.html --- requirements.txt | 4 +++- webodm/settings.py | 1 + webodm/urls.py | 22 +++++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9620e183..f6419b7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,4 +61,6 @@ https://github.com/OpenDroneMap/WebODM/releases/download/v1.9.7/GDAL-3.3.3-cp39- Shapely==1.7.0 ; sys_platform == "win32" eventlet==0.32.0 ; sys_platform == "win32" pyopenssl==19.1.0 ; sys_platform == "win32" -numpy==1.21.1 \ No newline at end of file +numpy==1.21.1 +drf-yasg==1.20.0 + diff --git a/webodm/settings.py b/webodm/settings.py index bf4f7e27..273d20e7 100644 --- a/webodm/settings.py +++ b/webodm/settings.py @@ -105,6 +105,7 @@ INSTALLED_APPS = [ 'guardian', 'rest_framework', 'rest_framework_nested', + 'drf_yasg', 'webpack_loader', 'corsheaders', 'colorfield', diff --git a/webodm/urls.py b/webodm/urls.py index dbcf82f8..d92938ff 100644 --- a/webodm/urls.py +++ b/webodm/urls.py @@ -16,16 +16,36 @@ Including another URLconf import os from django.conf.urls import include, url +from django.urls import re_path from django.contrib import admin from . import settings from django.views.static import serve +from rest_framework import permissions +from drf_yasg.views import get_schema_view +from drf_yasg import openapi + admin.site.site_header = 'WebODM Administration' +schema_view = get_schema_view( + openapi.Info( + title="WebODM API", + default_version='v1.0.0', + description="WebODM API", + #terms_of_service="", + #contact=openapi.Contact(email=""), + ), + public=True, + permission_classes=[permissions.AllowAny], +) + urlpatterns = [ url(r'^', include('app.urls')), url(r'^', include('django.contrib.auth.urls')), url(r'^admin/', admin.site.urls), + re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), + re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ] if settings.DEBUG or settings.FORCE_MEDIA_STATICFILES: @@ -40,6 +60,6 @@ if settings.DEBUG or settings.FORCE_MEDIA_STATICFILES: ] - #from django.contrib.staticfiles.urls import staticfiles_urlpatterns #urlpatterns += staticfiles_urlpatterns() +