jeudi 11 avril 2013

[Web2py] Implement multiple files upload using AJAX with jQuery-file-upload plugin in less than 10 minutes

This is a translation of an original article in french

The objective of this article in less than 2 minutes

You want to offer on your website to upload multiple files. To make use of your service more transparently and faster, you want to use AJAX (sending data asynchronously).

A very effective plugin has been developed for this purpose:  jQuery-file-upload (a demonstration is available at this link). The list of features of this plugin is very complete:

    • Sending multiple files
    • Support Drag & Drop
    • Bar progession sending
    • Option to cancel the upload in progress
    • Ability to resume an upload
    • Large files can be uploaded in smaller chunks
    • Resizing images on the client side
    • Preview images sent
    • No browser plugin required (like Adobe Flash)
    • Supports sending files to a different domain
    • Compatible with any web application server side, regardless of the language

      This last point interest us for the following!

      Your favorite development language is Python (lucky you are!), And among the Python web development framework, you chose web2py (convenient, efficient, not too restrictive: good choice too!).

      We come to the existential question to which this article will attempt to answer:
      How to integrate jQuery-file-upload in a web2py application ? I only have 8 minutes to give you the solution!

      Finish his coffee that has cooled too much: 1 minute

      During the time we finish the coffe, we can download all the material necessary for our business and implement the necessary environment!
      1. We begin by  downloading the plugin and if it is not done web2py 
      2. We create a new web2py application (or upcoming changes are applied directly to the application if we are foolhardy). We do it quickly, creating an application web2py is not the purpose of this tutorial.

      The creation of the model that will receive uploaded files : 1 minute

      We continue operations by creating the model that represent the database files sent to the server using jQuery-file-upload.

      You do as you like, but basically, we need a field of type "upload" that I call "doc", as I want my users send images, I created another field of type "upload" called "thumb", you will understand that this is to store thumbnail image. We create a final field "sizeImg", which represents the size of the original image.

      The following options are available and are very useful:
      • autodelete, if a row in the database is deleted, the files on the disk will be automatically deleted at the same time
      • uploadseparate, files sent to the server are divided into several directories (and not stored in a single directory "uploads") to improve performance. Web2py documentation indicates that they are degraded beyond 1000 files in the same directory. Feel free to pass this parameter to "False" if you think that this number will remain below 1000, and thus simplify the architecture of your application.

      Finally, in the model, we should end up with something similar to:

      Files = db.define_table('files',
       Field('doc', 'upload', autodelete=True),
       Field('thumb', 'upload', autodelete=True),
       Field('sizeFile', 'float'),
       Field('sessionId', 'string'),)
      
      from smarthumb import SMARTHUMB
      box = (200, 200)
      Files.thumb.compute = lambda row: SMARTHUMB(row.doc, box)
      

      You'll notice the last 3 lines that contain smarthumb (which comes from a recipe web2py: Generate a thumbnail that fits in a box).

      This module is used to generate thumbnails for images. We place the file in the "modules" directory of your application. The advantage of this module is twofold: it resizes the image, but keep its proportions ("crop"). These lines under the model are used to automatically call the module when an image is sent and resize it.

      We could use the thumbnail of PIL (the famous library of image manipulation in Python). I'll explain it another day.

      Time flies! What should we do with the controller? In 2 minutes!

      In the controller, we need to generate functions that will be called by the upload plugin via AJAX.

      What we need to properly operate jQuery-file-upload:
      • The "handler" which receives each file sent by the plugin (so AJAX), and sends response as a string in JSON format, required by the plugin, that contains information such as the address of the file, its size, the address of the thumbnail image and the address to delete the uploaded file.
      • We need a function that also supports the deletion of a file sent.
      • Finally, a function that supports the download of the file or its thumbnail. And there happiness! Web2py has already planned with the "download" function in the controller.
      So we add the following lines to the controller:

      def upload_file():
              """
              File upload handler for the ajax form of the plugin jquery-file-upload
              Return the response in JSON required by the plugin
              """
              try:
                  # Get the file from the form
                  f = request.vars['files[]']
                  
                  # Store file
                  id = db.files.insert(doc = db.files.doc.store(f.file, f.filename))
                  
                  # Compute size of the file and update the record
                  record = db.files[id]
                  path_list = []
                  path_list.append(request.folder)
                  path_list.append('uploads')
                  path_list.append(record['doc'])
                  size =  shutil.os.path.getsize(shutil.os.path.join(*path_list))
                  File = db(db.files.id==id).select()[0]
                  db.files[id] = dict(sizeFile=size)
                  db.files[id] = dict(sessionId=response.session_id)
                  
                  res = dict(files=[{"name": str(f.filename), "size": size, "url": URL(f='download', args=[File['doc']]), "thumbnail_url": URL(f='download', args=[File['thumb']]), "delete_url": URL(f='delete_file', args=[File['doc']]), "delete_type": "DELETE" }])
                  
                  return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
      
              except:
                  return dict(message=T('Upload error'))
      
      
      def delete_file():
              """
              Delete an uploaded file
              """
              try:
                  name = request.args[0]
                  db(db.files.doc==name).delete()
                  return dict(message=T('File deleted'))
              except:
                  return dict(message=T('Deletion error'))
      
      
      def upload():
              return dict()
      
      
      Upload function is only there to show the view as an example.

      It remains us to create the view! 4 minutes should be enough!

      We just create the file /views/default/upload.html in connection with the upload controller for front-end form for sending files. The form is directly inspired by the model proposed by the demonstration of the plugin.
      Now let's see the static files.

      • A directory "jQuery-File-Upload" is created under the static directory of web2py, that will contains CSS, javascript and images of the plugin. Then, we copy the contents of directories js, css and img of the plugin to the directory that we just created.
      • We then modify any URLs referring to these static files in Upload.html with the web2py syntax ({{URL ...)
      The code for this part is too long, you will find it in my  repository bitbucket.

      Finally, we take care of main.js file in static files of the plugin:

      • We can delete the specific lines of the demo of the plugin.
      • And add the URL to the controller function that will receive the files from the form (The AJAX handler URL)

      We obtain the following file main.js:
      $(function () {
          'use strict';
      
          // Initialize the jQuery File Upload widget:
          $('#fileupload').fileupload({
              // Uncomment the following to send cross-domain cookies:
              //xhrFields: {withCredentials: true},
              url: 'http://127.0.0.1:8000/multiupload_module/default/upload_file',
              autoUpload: 'True',
              maxNumberOfFiles: 3,
              maxFileSize: 2500000,
              acceptFileTypes: '/(\.|\/)(gif|jpe?g|png)$/i',
          });
      
          // Enable iframe cross-domain access via redirect option:
          $('#fileupload').fileupload(
              'option',
              'redirect',
              window.location.href.replace(
                  /\/[^\/]*$/,
                  '/cors/result.html?%s'
              )
          );
      
              // Load existing files:
              $.ajax({
                      // Uncomment the following to send cross-domain cookies:
                      //xhrFields: {withCredentials: true},
                      url: $('#fileupload').fileupload('option', 'url'),
                      dataType: 'json',
                      context: $('#fileupload')[0]
              }).done(function (result) {
                      $(this).fileupload('option', 'done')
                              .call(this, null, {result: result});
              });
      
      });
      

      We then launched our server hosting web2py, we headed for the upload, and joy and happiness, it works!

      Epilogue (untimed)

      If it does not work (and even if it works), you can take a look at my repository that contains a web2py application complete and functional with all the changes that are described in this article. There is only these changes, so copy-paste the solution should not be too complicated!

      7 commentaires:

      1. Great walk through except that when copy the example and try it, nothing happens when i click start upload. I tried following it manually and same thing.

        RépondreSupprimer
      2. OK, I figured it out. The hotlinked js and css expired and bugged out, so I started seeing it properly, and played around with the main.js url and the files do get uploaded. So thanks a lot for this tutorial, it was helpful!

        RépondreSupprimer
        Réponses
        1. What did you change to make it work? i have been trying to make it work for the last 2 days with no success.

          I appreciated your help...thanks in advance

          Supprimer
      3. Hello. What is the link to download the plugin?

        RépondreSupprimer
      4. nice, i will use this in my next application

        RépondreSupprimer
      5. This example doesn't work. Don't waste your time, even tried 100% source.

        RépondreSupprimer
      6. There are broken links, please provide them, thanks.

        RépondreSupprimer