Jul 12, 2011

Django: Creating multi upload form without using Flash

Hi there! Today I want to tell you about my experience of adding Multiple files upload form to my Django project Photoblog. I searched google for lots of plugins, but found only Flash usage examples. I dislike Flash technology, as for my personal usage, and want to build some more quickly working UI. Personally I have flash blocker installed on my browser. So that's main ideas pf my jQuery plugin selection. I found IMHO the best for my case plugin by Sebastian Tschan. It had only one problem - No available Django examples. Let's try to fill the gap here... So my experience on befriending Sebastian Tschan's jQuery File Upload Plugin with Django is the topic of this article. Let's finally get started. :)

Sebastian has built an important plugin IMHO. I want to thank him for his noble work of helping people with their tasks. It uses only jQuery UI and no flash, making it work more programmers way... So enough with the lyrics.

Let's try to adapt his example to a django Project. I used new one, built with Eclipse using Django 1.3. So fell free to create yours and Download Sebastian's plugin. After that we will get existing project using PHP as main gear. Let's try to adapt it.

Main problems I met creating it:
    - jQuery UI uses similar templates language with Django.
    Problem solved by changing Django code to generate '{{' scopes instead variables '{{ open_tv }}' and '}}' instead '{{ close_tv }}' accordingly. You can watch it in template example of this demo project.
    - write view to get files from AJAX form and return proper JSON in response. You can examine mine in app multiuploader.
    - write view to delete uploaded photo. Mine is multiuploader_delete.
    - generate thumbnail for showing uploaded image. I used sorl-thumbnails that uses PIL.
    - generate JSON response to form like this:

{"name":"picture1.jpg","size":902604,"url":"//example.org/files/picture1.jpg","thumbnail_url":"//example.org/thumbnails/picture1.jpg","delete_url":"//example.org/dlete/1/","delete_type":"POST"},
{"name":"picture2.jpg","size":841946,"url":"//example.org/files/picture2.jpg","thumbnail_url":"//example.org/thumbnails/picture2.jpg","delete_url":"/example.org/delete/2/","delete_type":"POST"}


Let's get straight to business. So imagine this simple model. It has only image and title (used image's file name for title):

#models.py
from django.db import models

class Image(models.Model):
    title = models.CharField(max_length=60, blank=True, null=True)
    image = models.FileField(upload_to="images/")
    
    def __unicode__(self):
        return self.image.name

Now let's write urls as we need for main images view, multiuploader form and file deletion like so:
#urls.py
from django.conf.urls.defaults import *

from multiuploader.views import multiuploader_delete

urlpatterns = patterns('',
    (r'^delete/(\d+)/$', multiuploader_delete),
    url(r'^$', 'django_multiuploader_demo.multiuploader.views.image_view', name='main'),
    url(r'^multi/$', 'django_multiuploader_demo.multiuploader.views.multiuploader', name='multi'),
    
)
Main view is quite simple. It's just for handy demo that photos exist after upload. It will look like so:

#views.py
def image_view(request):
    items = Image.objects.all()
    return render_to_response('images.html', {'items':items})

It will pass our model directly to template. It will render our model like so:

#multiuploader_main.html
{% load thumbnail %}
<h1><a href="{% url multi %}">Multiuploader</a></h1>
<br />
<h2>Images:</h2>
<hr>
<div class="photos">
{% for item in items %}
{% thumbnail item "80x80" crop="10px 10px" as im %}
    <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
{% endfor %}
</div>
<hr>

Final result is simple images model:
Now that we have application to upload photos to let's proceed.
Let's create a a main view that will return our multiuploader template in cese of GET and receive POST requests with photos. It might look like so:

#views.py
def multiuploader(request):
    if request.method == 'POST':
        log.info('received POST to main multiuploader view')
        if request.FILES == None:
            return HttpResponseBadRequest('Must have files attached!')

        #getting file data for farther manipulations
        file = request.FILES[u'files[]']
        wrapped_file = UploadedFile(file)
        filename = wrapped_file.name
        file_size = wrapped_file.file.size
        log.info ('Got file: "'+str(filename)+'"')

        #writing file manually into model
        #because we don't need form of any type.
        image = Image()
        image.title=str(filename)
        image.image=file
        image.save()
        log.info('File saving done')

        #getting url for photo deletion
        file_delete_url = '/delete/'
        
        #getting file url here
        file_url = '/'

        #getting thumbnail url using sorl-thumbnail
        im = get_thumbnail(image, "80x80", quality=50)
        thumb_url = im.url

        #generating json response array
        result = []
        result.append({"name":filename, 
                       "size":file_size, 
                       "url":file_url, 
                       "thumbnail_url":thumb_url,
                       "delete_url":file_delete_url+str(image.pk)+'/', 
                       "delete_type":"POST",})
        response_data = simplejson.dumps(result)
        return HttpResponse(response_data, mimetype='application/json')
    else: #GET
        return render_to_response('multiuploader_main.html', 
                                  {'static_url':settings.MEDIA_URL,
                                   'open_tv':u'{{',
                                   'close_tv':u'}}'}, 
                                  )


Now let's add the view that will delete photos for us. It might look like so:

#views.py
def multiuploader_delete(request, pk):
    if request.method == 'POST':
        image = get_object_or_404(Image, pk=pk)
        image.delete()
        return HttpResponse(str(pk))
    else:
        return HttpResponseBadRequest('Only POST accepted')

Finally main Form would look similar to this:


I will not provide main template of uploader, because it's quite big. You can fork/download it from my django_multiuploader_demo at github.

Feel free to use this demo anyhow in your project, but please drop me a comment here if you find it useful. 

Ideas, Suggestions, Critics? Welcome to comments...

7 comments:

  1. hello, I get this error.



    TemplateDoesNotExist at /multi/multiuploader_main.html
    Request Method: GET
    Request URL: http://127.0.0.1:8000/multi/
    Django Version: 1.3.1
    Exception Type: TemplateDoesNotExist
    Exception Value:
    multiuploader_main.html


    Could you help out? And by the way, I don't see where you have referenced to jquery.js ????
    maybe that's the problem

    ReplyDelete
  2. kourosh, make sure that template directory is listed in the TEMPLATE_DIRS tuple in your project's settings file. this error is hitting you before jQuery becomes relevant

    ReplyDelete
  3. Подскажите пожалуйста, как сделать, чтоб при удалении картинок они удалялись из самой папки?

    ReplyDelete
    Replies
    1. Это осбенность нового (после 1.2 помоему) джанго. Там нужно изменить fields.ImageField на какойнить кастомный филд который стирает и сам файл. Их в сети множество нынче.

      Delete
  4. Nice man. You saved me some time. Thanks!

    ReplyDelete
  5. Hello !

    First thanks for you package.

    Second, I have a problem I just cannot resolve. I downloaded https://github.com/garmoncheg/django_multiuploader_example_usage/tree/master/django_multiuploader_example_usage/app
    and I tried to use it...
    The result is totally ugly and I'm pretty sure that there are no AJAX, js or anything. Here is the result: http://img248.imageshack.us/img248/5580/multiuploader.jpg

    I have no error only warnings :
    Quit the server with CONTROL-C.
    /opt/scyld/python/2.6.5/lib/python2.6/site-packages/django/conf/__init__.py:75: DeprecationWarning: The ADMIN_MEDIA_PREFIX setting has been removed; use STATIC_URL instead.
    "use STATIC_URL instead.", DeprecationWarning)
    Validating models...

    0 errors found
    Django version 1.4.4, using settings 'prestationdb.settings'
    Development server is running at http://130.79.101.76:8181/
    Quit the server with CONTROL-C.
    /opt/scyld/python/2.6.5/lib/python2.6/site-packages/django/template/defaulttags.py:1235: DeprecationWarning: The syntax for the url template tag is changing. Load the `url` tag from the `future` tag library to start using the new behavior.
    category=DeprecationWarning)


    If you could help that would be wonderful :)

    Have a nice day !

    ReplyDelete
  6. Подскажите, а каким образом можна сохранять изображения после отправки формы... Тоисть аналог инлайна в админке, но используя этот плагин...

    ReplyDelete