Upload un fichier en Ajax avec Django

Voici un tutoriel Django pour apprendre à uploader un fichier en AJAX. Dans notre exemple ce sera l'upload d'une image associé à un produit existant. A chaque upload le nom est remplacé par l'id du produit.

Il nous faudra tout d'abord installer la librarie ng-file-upload:

bower install ng-file-upload

Créons ensuite la partie HTML qui traite l'upload:

<div ng-controller="UploadProductImage">
<div 
style="border:1px dashed #ccc;width:100%;padding:2px;" 
ngf-drop ngf-select ng-model="files" class="drop-box" 
ngf-drag-over-class="dragover" ngf-multiple="true" ngf-allow-dir="true"
accept="image/*,application/pdf">
<img style="height:100px;" class="img-responsive" src="~{product.image}~" />
</div>
<div ngf-no-file-drop>Erreur navigateur</div>   
</div>

Puis les classes AngularJS:

class UploadProductImage

  @$inject: ['$scope', 'Upload'] 

  constructor: (@scope, Upload) ->

    @Upload = Upload
    @scope.$watch 'files', () =>
      console.log "Un fichier a été ajouté"
      @upload @scope.files
      true
    true

  upload : (files) =>

    if files and files.length
      file = files[0]
      @Upload.upload(
        url: '/backoffice/upload_product_image',
        fields: {'id_product': @scope.product.id},
        file: file
      ).progress( (evt) ->
        progressPercentage = parseInt(100.0 * evt.loaded / evt.total)
        console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name)
      ).success( (data, status, headers, config) =>
        console.log('file ' + config.file.name + 'uploaded. Response: ' + data)
        @scope.product.image = "/media/" + data.filename
        console.log @scope.customer.image
      )

app.controller 'UploadProductImage', UploadProductImage

Le routage:

class DashboardController

  @$inject: ['$scope']

  constructor: (@scope) ->
    @scope.product = {'id' : 1, 'name' : "iMac 2015"}

app.controller 'DashboardController', DashboardController
from django.conf.urls import patterns, include, url
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    url(r'^$', LoginView.as_view()),
    url(r'^backoffice/upload_product_image$', login_required(UploadImage.as_view())),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

La vue:

from django.views.generic import TemplateView

class UploadImage(TemplateView):

  def post(self, request, **kwargs):

    id_product = int(request.POST['id_product'])
    product = Product.objects.get(id=id_product)
    product.image = request.FILES['file']
    product.save()
    data = { "filename" : product.image.name }
    return JsonResponse(data)

Et enfin configurons le modèle:

import os
from django.db import models
from django.utils.deconstruct import deconstructible
from django.core.files.storage import FileSystemStorage

class OverwriteStorage(FileSystemStorage):

  def _save(self, name, content):
    if self.exists(name):
        self.delete(name)
    return super(OverwriteStorage, self)._save(name, content)

  def get_available_name(self, name):
    return name


@deconstructible
class PathAndRename(object):

  def __init__(self, sub_path):
    self.path = sub_path

  def __call__(self, instance, filename):
    f, ext = os.path.splitext(filename)
    print f, ext
    if ext not in ['.jpg', '.png', '.jpeg']:
      raise NameError('Format interdit')
    new_filename = "{0}{1}".format( instance.id, ext )
    return '/'.join(['product', new_filename])

path_and_rename = PathAndRename("")


class Product(models.Model):

  date_add = models.DateTimeField(auto_now_add=True)
  name     = models.CharField(max_length=255)
  code     = models.CharField(max_length=100, null=True)
  price    = models.FloatField()
  supplier = models.ForeignKey('Supplier', null=True)
  image    = models.ImageField(upload_to=path_and_rename, storage=OverwriteStorage())

  def __unicode__(self):
    return "{0}".format(self.code, )