Pull to refresh

Загрузка нескольких файлов в Django — Multifile upload Django SWFUploader

Reading time 9 min
Views 7.6K
Django предоставляет отличные средства для быстрого построения back-end без лишних телодвижений. Однако создание на его основе галлереи связано с множеством ручной работы по загрузке каждого файла в отдельности.

Решений у этой проблемы несколько:
  1. загрузка архива и его распаковка на сервере,
  2. использование специального поля,
  3. имитация множественных post-запросов.

Я выбрал последний вариант, так как в этом случае нужно внести минимум изменений. Все операции выполняются на клиенте. Использовать для этого будем swfuploader.

Задумка проста, модифицируем стандартный интерфейс добавления, так что бы перехватывать событие сохранения объекта. Заполненные поля будем использовать в качестве шаблона для создания множества объектов для выбранных файлов.

Итак выполним следующие действия.

  1. Выкачиваем набор примеров swfupload: http://swfupload.googlecode.com/files/SWFUpload%20v2.2.0.1%20Samples.zip.
  2. из него нам падобятся следующие файлы:

  3. Кладем их в медиа директорию djangosite/media/js/swfupload.
  4. Прописываем js в djangosite/gallery/admin.py:
    from django.contrib import admin
    from djangosite.gallery.models import Image

    class ImageAdmin(admin.ModelAdmin):
      class Media:
        js = (
           '/media/js/jquery.min.js',
           '/media/js/swfupload/swfupload.js',
           '/media/js/swfupload/swfupload.queue.js',
           '/media/js/swfupload/swfupload.cookies.js',
           '/media/js/multiupload.js',
        )
      
    admin.site.register(Image,ImageAdmin)


    * This source code was highlighted with Source Code Highlighter.
  5. Создаем управляющий js-код djangosite/media/js/multiupload.js

    jQuery().ready(function(e){<br>  DjangoMultiUpload.init();<br>});<br><br><br>var DjangoMultiUpload = new function(){<br>  <br>  // Общие переменные<br>  this.swfu = null;<br>  this.gui_submit = null;<br>  this.old_upload = null;<br>  this.busy = false;<br>  <br>  // Параметры для swfupload<br>  this.debug = false;<br>  this.flash_url = "/media/js/swfupload/swfupload.swf";<br>  this.upload_url = "";<br><br>  this.file_size_limit = "100 MB";<br>  this.file_types = "*.jpg;*.gif;*.jpeg";<br>  this.file_types_description = "Файлы изображений";<br>  this.file_upload_limit = 100;<br>  this.file_queue_limit = 0;<br>  this.custom_settings = {<br>    progressTarget : "fsUploadProgress",<br>    cancelButtonId : "btnCancel"<br>  };<br>  this.button_image_url = "/media/js/swfupload/XPButtonUploadText_61x22.png",<br>  this.button_width = "61",<br>  this.button_height = "22",<br>  this.button_placeholder_id = "spanButtonPlaceHolder",  <br>  this.file_post_name = null;<br><br>  //  Проверяем ос на Linux (проблемы совместимости).<br>  //  Проверяем количество input file, <br>  //    Должен быть один. <br>  //    деолжен быть обязательным<br>  //  Проверяем не редактирование ли это?<br>  //  Готовим Ui.<br>  //  Инициализируем swfupload<br>  this.init = function(e){<br><br>    if (navigator.appVersion.indexOf("Linux")!=-1)<br>      return false;<br>    <br>    this.old_upload = jQuery('input[type=file]');<br>    <br>    if (this.old_upload.size() != 1)<br>      return false;<br>    <br>    if (!jQuery("label[for=" + this.old_upload.attr('id') + "]").hasClass('required'))<br>      return false;<br><br>    if (this.old_upload.prev().get(0).tagName == "BR")<br>      return false;<br>    <br>    this.gui();<br>    <br>    this.swfu = new SWFUpload(this);<br>    <br>  }<br>  <br>  // Скрываем оригинальный input file<br>  // Добавляем холдер для контрола swfuploader<br>  // Добавляем перехватчик отправки форм<br>  // Скрываем лишние кнопки<br>  this.gui = function(){<br>    <br>    this.file_post_name = this.old_upload.attr('name');<br>    <br>    jQuery("<span id='spanButtonPlaceHolder'></span>").insertAfter(this.old_upload);<br>    <br>    <br>    <br>    jQuery('form').submit(function(e){<br>      DjangoMultiUpload.submit(e);<br>      return false;<br>    });<br>    <br>    this.old_upload.hide();<br>    jQuery('input[name=_continue]').hide();<br>    jQuery('input[name=_addanother]').hide();<br>    <br>  }<br>  <br>  // Проверка на занятость<br>  // Проверка обязательные поля<br>  // Запуск закачки<br>  this.submit = function(e){<br>    <br>    if (this.busy){<br>      alert('Uploading. Please wait.');<br>      return false;<br>    }<br>  <br>    if (this.prepare_post_data()){<br>      <br>      jQuery('input[name=_save]').attr('value',0);<br>          <br>      this.busy = true;<br>          <br>      this.swfu.startUpload();<br>    }<br>    <br>    return false;<br>    <br>  }<br>  <br>  // Получаем все лейблы<br>  // По ним находим поля и прописываем их в postdata swfuploader<br>  // Если поле обязательное и не зполнено меняем цвет и возварщаем false<br>  // При переборе игнорируем input-file<br>  // На случай linux плеера передаем имя поля с файлом<br>  this.prepare_post_data = function (){<br>    <br><br>    <br>    var labels = jQuery('label');<br>    <br>    if (!labels.size())<br>      return 0;<br>    <br>    var field = null;<br>    var label = null;<br>    var success = true;<br>    <br>    for (i = 0 ; i < labels.size();i++ )<br>    {<br>      <br>      label = jQuery(labels.get(i))<br>      <br>      if (label.attr('for') == this.old_upload.attr('id'))<br>        continue;<br><br>      field = jQuery('#' + label.attr('for'));<br>      <br>      if (label.hasClass('required') && !field.attr('value')){<br>        label.css('color','red !important');<br>        success = false;<br>      }<br><br>      this.swfu.addPostParam(field.attr('name'), field.attr('value'));<br>      <br>    }<br>    <br>    this.swfu.addPostParam('file_post_name', this.file_post_name)<br>    <br>    return success;<br>    <br>  }<br>  <br>  // По завершении загрузки файла<br>  this.upload_complete_handler = function(file)<br>  {<br>    jQuery('input[name=_save]').attr('value', parseInt(jQuery('input[type=submit]').attr('value'))+1)<br>  };<br>  <br>  // По завершении загрузки всех файлов идем к списку объектов<br>  this.queue_complete_handler = function(numFilesUploaded)<br>  {<br>    <br>    this.busy = false;<br>    <br>    if (!this.settings.debug){<br>      window.location="../";<br>    }<br>  };  <br>  <br>  // Прочие события<br>  this.file_queued_handler = function(file){};<br>  this.file_queue_error_handler = function(file, errorCode, message){};<br>  this.file_dialog_complete_handler = function(numFilesSelected, numFilesQueued){};<br>  this.upload_start_handler = function(file){};<br>  this.upload_progress_handler = function(file, bytesLoaded, bytesTotal){};<br>  this.upload_success_handler = function(file, serverData){};<br>  this.upload_error_handler = function(file, errorCode, message){};<br>  <br>};<br><br>      <br><br>      <br><br>* This source code was highlighted with Source Code Highlighter.


  6. у меня повсюду в админке используется jQuery. Если у вас его нет, так же кладем djangosite/media/js/jquery.min.js.
  7. А теперь немного грязнохакерства. В djangosite/gallery/middleware.py прописываем:

    from django.conf import settings

    class MultiUploadHacksMiddleware(object):
      def process_request(self, request):

        # Некоторые flash плееры не отправляют или отправляют неправильный sessionid
        # Берем его из POST
        if request.POST.has_key(settings.SESSION_COOKIE_NAME):
          request.COOKIES[settings.SESSION_COOKIE_NAME] = \
            request.POST[settings.SESSION_COOKIE_NAME]

        # Другие не умеют менять название POST переменной с файлом
        # Возьмем его из заранее подготовленной
        if request.POST.has_key("file_post_name") and request.FILES.has_key("Filedata"):
          request.FILES[request.POST["file_post_name"]] = request.FILES["Filedata"]
          del request.FILES["Filedata"]


    * This source code was highlighted with Source Code Highlighter.


  8. Добавляем наши хаки в settings.py

    MIDDLEWARE_CLASSES = (
      'djangosite.gallery.middleware.MultiUploadHacksMiddleware',
      'django.middleware.common.CommonMiddleware',
      'django.contrib.sessions.middleware.SessionMiddleware',
      'django.contrib.auth.middleware.AuthenticationMiddleware',
    )


    * This source code was highlighted with Source Code Highlighter.



Готово, теперь наши пользователи могут грузить фотки пачками. Можно пойти дальше — добавить прогресс бар и прочие навороты, но в моем случае в этом не было необходимости.

Ограничения:
  • у модели должно быть только одно поле — файл,
  • оно должно быть обязательным,
  • прочие поля (кроме id, разумеется) не должны быть уникальными,


В идеале хотелось что бы:
  • flash-плеер сразу брал правильные cookie (django все равно ругается но файлы создает),
  • linux flash-плеер поддерживал flie_post_name,
  • был метод openDialog() в swfupload для использования собственных контролов.


На всякий случай демка.
Tags:
Hubs:
+23
Comments 21
Comments Comments 21

Articles