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

Wyświetl plik

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

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

Wyświetl plik

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

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 '../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';

Wyświetl plik

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