Skip to main content

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

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

Post a Comment

Popular posts from this blog

Pretty git Log

SO you dislike git log output in console like me and do not use it... Because it looks like so: How about this one? It's quite easy... Just type: git log - - graph - - pretty = format : '%Cred%h%Creset -%C ( yellow ) %d%Creset %s %Cgreen ( %cr) %C ( bold blue ) <%an>%Creset' - - abbrev - commit - - It may be hard to enter such an easy command every time. Let's make an alias instead... Copypaste this to your terminal: git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --" And use simple command to see this pretty log instead: git lg Now in case you want to see lines that changed use: git lg - p In order for this command to work remove  the -- from the end of the alias. May the code be with you! NOTE: this article is a rewritten copy of  http://coderwall.com/p/euwpig?i=3&p=1&t=git   and have b...

Django: Resetting Passwords (with internal tools)

I have had a task recently. It was about adding a forms/mechanism for resetting a password in our Django based project. We have had our own registration system ongoing... It's a corporate sector project. So you can not go and register yourself. Admins (probably via LDAP sync) will register your email/login in system. So you have to go there and only set yourself a password. For security reasons you can not register. One word. First I've tried to find standart decision. From reviewed by me were: django-registration and django password-reset . These are nice tools to install and give it a go. But I've needed a more complex decision. And the idea was that own bicycle is always better. So I've thought of django admin and that it has all the things you need to do this yourself in no time. (Actually it's django.contrib.auth part of django, but used out of the box in Admin UI) You can find views you need for this in there. they are: password_reset password_reset_...

Vagrant error: * Unknown configuration section 'hostmanager'.

Sometimes you get a vagrant environment or boilerplate with a Vagrantfile config in there and do a vagrant up command. And see some errors. like this: There are errors in the configuration of this machine . Please fix the following errors and try again : Vagrant: * Unknown configuration section 'hostmanager'. To fix this one needs: $ vagrant plugin install vagrant - hostmanager Installing the ' vagrant-hostmanager ' plugin . This can take a few minutes . . . Fetching : vagrant - hostmanager - 1.8 .6 . gem ( 100 % ) Installed the plugin ' vagrant-hostmanager (1.8.6) ' ! So command to fix this as follows: vagrant plugin install vagrant-hostmanager