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 guardian.admin import GuardedModelAdmin
|
||||
from .models import Project, Task
|
||||
from .models import Project, Task, ImageUpload
|
||||
|
||||
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.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.decorators import parser_classes, api_view
|
||||
from app import models
|
||||
from nodeodm.models import ProcessingNode
|
||||
|
||||
|
@ -19,22 +20,25 @@ class TaskSerializer(serializers.ModelSerializer):
|
|||
|
||||
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()
|
||||
|
||||
# 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, )
|
||||
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
|
||||
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()
|
||||
for perm in perms:
|
||||
if not request.user.has_perm(perm, project): raise ObjectDoesNotExist()
|
||||
except ObjectDoesNotExist:
|
||||
raise exceptions.NotFound()
|
||||
return project
|
||||
|
@ -52,4 +56,19 @@ class TaskViewSet(viewsets.ViewSet):
|
|||
except ObjectDoesNotExist:
|
||||
raise exceptions.NotFound()
|
||||
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.models import UserObjectPermissionBase
|
||||
from guardian.models import GroupObjectPermissionBase
|
||||
from django.db import transaction
|
||||
|
||||
def assets_directory_path(taskId, projectId, 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")
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
class ImageUpload(models.Model):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ProjectListItemPanel from './ProjectListItemPanel';
|
||||
import Dropzone from '../vendor/dropzone';
|
||||
import csrf from '../django/csrf';
|
||||
import $ from 'jquery';
|
||||
|
||||
class ProjectListItem extends React.Component {
|
||||
|
@ -20,7 +21,12 @@ class ProjectListItem extends React.Component {
|
|||
Dropzone.autoDiscover = false;
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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 './django/csrf';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Dashboard from './Dashboard';
|
||||
|
|
|
@ -191,7 +191,7 @@ LOGIN_REDIRECT_URL = '/dashboard/'
|
|||
LOGIN_URL = '/login/'
|
||||
|
||||
# File uploads
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'app', 'media')
|
||||
|
||||
# Store flash messages in cookies
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
|
|
Ładowanie…
Reference in New Issue