kopia lustrzana https://github.com/OpenDroneMap/WebODM
Django CSRF ajax support, file upload example
rodzic
8c6da354b1
commit
49e7c5dfa5
|
@ -1,6 +1,10 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from guardian.admin import GuardedModelAdmin
|
from guardian.admin import GuardedModelAdmin
|
||||||
from .models import Project, Task
|
from .models import Project, Task, ImageUpload
|
||||||
|
|
||||||
admin.site.register(Project, GuardedModelAdmin)
|
admin.site.register(Project, GuardedModelAdmin)
|
||||||
admin.site.register(Task, GuardedModelAdmin)
|
admin.site.register(Task, admin.ModelAdmin)
|
||||||
|
|
||||||
|
class ImageUploadAdmin(admin.ModelAdmin):
|
||||||
|
readonly_fields = ('image',)
|
||||||
|
admin.site.register(ImageUpload, ImageUploadAdmin)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from rest_framework import serializers, viewsets, filters, exceptions, permissions
|
from rest_framework import status, serializers, viewsets, filters, exceptions, permissions, parsers
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.decorators import parser_classes, api_view
|
||||||
from app import models
|
from app import models
|
||||||
from nodeodm.models import ProcessingNode
|
from nodeodm.models import ProcessingNode
|
||||||
|
|
||||||
|
@ -19,22 +20,25 @@ class TaskSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class TaskViewSet(viewsets.ViewSet):
|
class TaskViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
TODO: permissions!
|
A task represents a set of images and other input to be sent to a processing node.
|
||||||
|
Once a processing node completes processing, results are stored in the task.
|
||||||
"""
|
"""
|
||||||
queryset = models.Task.objects.all()
|
queryset = models.Task.objects.all()
|
||||||
|
|
||||||
# We don't use object level permissions on tasks, relying on
|
# We don't use object level permissions on tasks, relying on
|
||||||
# project's object permissions instead (but standard model permissions still apply)
|
# project's object permissions instead (but standard model permissions still apply)
|
||||||
permission_classes = (permissions.DjangoModelPermissions, )
|
permission_classes = (permissions.DjangoModelPermissions, )
|
||||||
|
parser_classes = (parsers.MultiPartParser, )
|
||||||
|
|
||||||
def get_and_check_project(self, request, project_pk):
|
def get_and_check_project(self, request, project_pk, perms = ('view_project', )):
|
||||||
'''
|
'''
|
||||||
Retrieves a project and raises an exeption if the current user
|
Retrieves a project and raises an exeption if the current user
|
||||||
has no access to it.
|
has no access to it.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
project = models.Project.objects.get(pk=project_pk)
|
project = models.Project.objects.get(pk=project_pk)
|
||||||
if not request.user.has_perm('view_project', project): raise ObjectDoesNotExist()
|
for perm in perms:
|
||||||
|
if not request.user.has_perm(perm, project): raise ObjectDoesNotExist()
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
return project
|
return project
|
||||||
|
@ -52,4 +56,19 @@ class TaskViewSet(viewsets.ViewSet):
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
serializer = TaskSerializer(task)
|
serializer = TaskSerializer(task)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def create(self, request, project_pk=None):
|
||||||
|
project = self.get_and_check_project(request, project_pk, ('change_project', ))
|
||||||
|
|
||||||
|
# MultiValueDict in, flat array of files out
|
||||||
|
files = [file for filesList in map(
|
||||||
|
lambda key: request.FILES.getlist(key),
|
||||||
|
[keys for keys in request.FILES])
|
||||||
|
for file in filesList]
|
||||||
|
|
||||||
|
task = models.Task.create_from_images(files, project)
|
||||||
|
if task != None:
|
||||||
|
return Response({"id": task.id}, status=status.HTTP_201_CREATED)
|
||||||
|
else:
|
||||||
|
raise exceptions.ValidationError(detail="Cannot create task, input provided is not valid.")
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*
|
|
@ -10,6 +10,7 @@ from django.dispatch import receiver
|
||||||
from guardian.shortcuts import get_perms_for_model, assign_perm
|
from guardian.shortcuts import get_perms_for_model, assign_perm
|
||||||
from guardian.models import UserObjectPermissionBase
|
from guardian.models import UserObjectPermissionBase
|
||||||
from guardian.models import GroupObjectPermissionBase
|
from guardian.models import GroupObjectPermissionBase
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
def assets_directory_path(taskId, projectId, filename):
|
def assets_directory_path(taskId, projectId, filename):
|
||||||
# files will be uploaded to MEDIA_ROOT/project_<id>/task_<id>/<filename>
|
# files will be uploaded to MEDIA_ROOT/project_<id>/task_<id>/<filename>
|
||||||
|
@ -79,7 +80,25 @@ class Task(models.Model):
|
||||||
created_at = models.DateTimeField(default=timezone.now, help_text="Creation date")
|
created_at = models.DateTimeField(default=timezone.now, help_text="Creation date")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} {}'.format(self.name, self.uuid)
|
return 'Task ID: {}'.format(self.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_from_images(images, project):
|
||||||
|
'''
|
||||||
|
Create a new task from a set of input images (such as the ones coming from request.FILES).
|
||||||
|
This will happen inside a transaction so if one of the images
|
||||||
|
fails to load, the task will not be created.
|
||||||
|
'''
|
||||||
|
with transaction.atomic():
|
||||||
|
task = Task.objects.create(project=project)
|
||||||
|
|
||||||
|
for image in images:
|
||||||
|
ImageUpload.objects.create(task=task, image=image)
|
||||||
|
|
||||||
|
return task
|
||||||
|
|
||||||
|
# In case of error
|
||||||
|
return None
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -87,7 +106,7 @@ class Task(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def image_directory_path(task, filename):
|
def image_directory_path(imageUpload, filename):
|
||||||
return assets_directory_path(imageUpload.task.id, imageUpload.task.project.id, filename)
|
return assets_directory_path(imageUpload.task.id, imageUpload.task.project.id, filename)
|
||||||
|
|
||||||
class ImageUpload(models.Model):
|
class ImageUpload(models.Model):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ProjectListItemPanel from './ProjectListItemPanel';
|
import ProjectListItemPanel from './ProjectListItemPanel';
|
||||||
import Dropzone from '../vendor/dropzone';
|
import Dropzone from '../vendor/dropzone';
|
||||||
|
import csrf from '../django/csrf';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
class ProjectListItem extends React.Component {
|
class ProjectListItem extends React.Component {
|
||||||
|
@ -20,7 +21,12 @@ class ProjectListItem extends React.Component {
|
||||||
Dropzone.autoDiscover = false;
|
Dropzone.autoDiscover = false;
|
||||||
|
|
||||||
let dropzone = new Dropzone(domNode, {
|
let dropzone = new Dropzone(domNode, {
|
||||||
url : '/api/upload'
|
url : `/api/projects/${this.props.data.id}/tasks/`,
|
||||||
|
parallelUploads: 9999999,
|
||||||
|
uploadMultiple: true,
|
||||||
|
headers: {
|
||||||
|
[csrf.header]: csrf.token
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzone.on("complete", function(file) {
|
dropzone.on("complete", function(file) {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var cookieValue = null;
|
||||||
|
if (document.cookie && document.cookie !== '') {
|
||||||
|
var cookies = document.cookie.split(';');
|
||||||
|
for (var i = 0; i < cookies.length; i++) {
|
||||||
|
var cookie = jQuery.trim(cookies[i]);
|
||||||
|
// Does this cookie string begin with the name we want?
|
||||||
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||||
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function csrfSafeMethod(method) {
|
||||||
|
// these HTTP methods do not require CSRF protection
|
||||||
|
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = "X-CSRFToken",
|
||||||
|
token = getCookie('csrftoken');
|
||||||
|
|
||||||
|
// Automatically setup jQuery to send a CSRF header
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader(header, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
header, token
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
import '../css/main.scss';
|
import '../css/main.scss';
|
||||||
|
import './django/csrf';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Dashboard from './Dashboard';
|
import Dashboard from './Dashboard';
|
||||||
|
|
|
@ -191,7 +191,7 @@ LOGIN_REDIRECT_URL = '/dashboard/'
|
||||||
LOGIN_URL = '/login/'
|
LOGIN_URL = '/login/'
|
||||||
|
|
||||||
# File uploads
|
# File uploads
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'app', 'media')
|
||||||
|
|
||||||
# Store flash messages in cookies
|
# Store flash messages in cookies
|
||||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||||
|
|
Ładowanie…
Reference in New Issue