Django CSRF ajax support, file upload example

pull/32/head
Piero Toffanin 2016-10-13 16:28:32 -04:00
rodzic 8c6da354b1
commit 49e7c5dfa5
8 zmienionych plików z 99 dodań i 11 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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.")

1
app/media/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1 @@
*

Wyświetl plik

@ -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):

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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
};

Wyświetl plik

@ -1,4 +1,5 @@
import '../css/main.scss';
import './django/csrf';
import React from 'react';
import ReactDOM from 'react-dom';
import Dashboard from './Dashboard';

Wyświetl plik

@ -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'