File Upload using Dojo and Python

I was working on Azillia this past weekend and had trouble getting the File Uploader to work. Since I’m new to Python, debugging the errors were that much harder. However, I finally fixed it last night and everything seems to be working now. For those that are trying to do the same thing, here’s how I did it:


The HTML

<form action="/upload/" method="post" enctype="multipart/form-data" id="uploadForm">
    <input type="hidden" name="MAX_FILE_SIZE" value="10000000" />
    Choose a file to upload:<br />
    <input name="uploadFile" type="file" /><br />
    <input type="submit" id="uploadSubmit" value="Upload File" />
</form>

Pretty simple, really.  Just make sure you have an id on the form and the submit button.


The Javascript

<script type="text/javascript"
src="http://o.aolcdn.com/iamalpha/.resource/jssdk/dojo-0.2.2/dojo.js"></script>
<script type="text/javascript">
dojo.addOnLoad(function() {
    var handle = dojo.connect(dojo.byId('uploadSubmit'), 'onclick', function (evt) {
        // Prevent Default Behavior
        evt.preventDefault();

        dojo.xhrPost({
            url: "/upload/",

            load: function(response, ioArgs) {
                // do stuff

                return response;
            }, // end load

            error: function(response, ioArgs) {
                // do stuff

                return response;
            }, // end error

            form: 'uploadForm',
        }); // end xhrPost
    }); // end connect
}); // end addOnLoad
</script>

I’m adding a dojo connect to the “onclick” event of the submit button. Instead of submitting the form as usual, it will do an xhrPost to the specified URL. If the upload succeeds, I can do what I want within the “load” section of xhrPost (display a success message, hide the form, etc.). However, if it fails, the “error” section will be called and I can display a message or whatever else I want to do.

That’s all there is to it on the client side. On to the server side…


The Python

(r'^upload/$', 'common.views.upload'),

This gets added to my base urls.py, redirecting my POST call to the views file in my common directory.

from libs.Http import Http
import time

def upload(request):
    if request.method == 'POST':
        handle_file_upload(request.FILES['uploadedFile'])
	return HttpResponse('success')
    else:
        return HttpResponseBadRequest('error')

def handle_file_upload(f):
    fn = "".join(['uploads/', str(time.time()), '.jpg'])
    destination = open(fn, 'wb+')
    for chunk in f.chunks():
        destination.write(chunk)
    destination.close()

This will do the actual work of  the upload, basically taking the uploaded file and placing it in an uploads folder with the current timestamp as the filename.

NOTE:  This is a skimmed down version of the actual upload, not checking the filetype or anything of the sort. If you are going to use this, make sure you add those basic security checks into your code.

That’s all! Like I said, pretty easy stuff, but I got stuck on a few little things (I just didn’t know any better) and thought this might help someone else out. Let me know if you see any problems with this or have suggestions for ways to improve upon it!

Some Dojo Tips

I recently joined a project that uses Python, PostgreSQL, and Dojo. I have not used any of these to any extent worth mentioning, so I’m learning quite a bit. I spent way too much time working on very simple bugs and thought I’d post the solutions I found here – hopefully to avoid anyone else spending more time on them than is necessary.

1. Use dojo.byId(…)

Okay, so this isn’t one that gave me a hard time, but I read about the cross-browser compatibility of using dojo.byId instead of document.getElementById and thought it would be worth mentioning in here. According to DojoToolkit.org, bugs persist in Internet Explorer which make getElementById() unstable in some common scenarios. Below is an example of one of these bugs:

<script type="text/javascript">
var elem = document.getElementById('myDiv'); // Returns form element
var elem2 = dojo.byId('myDiv'); // Returns div
</script>

<form>
<div id="myDiv">TESTING</div>
</form>

One of my least favorite things to do is debug IE errors. Save yourself the heartache and just dojo.byId if you have it available.

2. Use dojo.addOnLoad(…)

This one did give me trouble. I was unaware of the existence of this function until I had already spent a few hours trying to find out why my function wasn’t working. Here’s a simplified example of what I was doing:

<script type="text/javascript">
var button = dojo.byId('myButton');
var handle = dojo.connect(button, 'onclick', function (evt) { alert('test'); });
</script>

<input id="myButton" type="button" />

Theoretically, when I click the button, I should get an alert with the text “test”. However, as the code is, I get the alert when I click anywhere on the page. The problem? dojo.byId(‘myButton’) was returning null, adding my onclick event to every object on the page! Why was dojo.byId returning null for an object that clearly exists? I even went to my Firebug console and typed “dojo.byId(‘myButton’)”, which of course, return my input button as expected. I finally realized (duh) that my javascript code is executing before the input element exists. Proper usage of the addOnLoad function fixed the problem.

<script type="text/javascript">
dojo.addOnLoad(function(){
var button = dojo.byId('myButton');
var handle = dojo.connect(button, 'onclick', function (evt) { alert('test'); });
});
</script>

<input id="myButton" type="button" />

Hopefully these tips will help save you some time that I wasted. Do you have any quick dojo tips that could save me time? Let me know in the comments section!