April 16th, 2012

Django and AJAX: Dajaxice

Tutorial for: Dajaxice

Requirements:

A introductory tutorial to Django and AJAX. This tutorial will focus on just Dajaxice, and not Dajax. Dajaxice is portable between backend frameworks and does not depend on any JavaScript framework.

This tutorial is not related to the other Django tutorial, which is going through on how to develop a simple wiki. This tutorial is a tad more advanced than the simple wiki, but most developers should be-able to pick it up fairly easy.

In the wonderful world of Web 2.0, we have a technology which has been around for almost a decade. This technology is called AJAX or Asynchronous JavaScript and XML. It allows a website which has already loaded from the web server to request additional data. Every modern GUI desktop browser supports this technology. The only problem, is that it may differ on each platform how the object is created. Well, only Microsoft's Internet Explorer browser seems to be the only one which wants to be unique in this regard(talk about following web standards). Because of this hick-up and the hick-ups along the way of other browser vendors, it is now wise to use a framework of sort when using AJAX, to make sure it works cross-platform.

There are many ways to call AJAX from the user's browser, as many frameworks support it. You can use the ever so popular jQuery, or another JavaScript framework. Some server-side frameworks also provide their own bridge for AJAX, such as Ruby on Rails, and Microsoft's Web Forms. Django also supports this functionality through a 3rd party app, called Dajaxice. The good thing about Dajaxice, is that if you do use it, it is fairly easy to port the client-side code to a new backend framework, if you so desire. But why would you? It's the same reason why when a developer settles with Rails, they never feel the need to switch the entire backend framework. The only reason you would want to switch your entire backend, is if it is not being supported by the original developers any longer. Both Django and Rails are far too successful to see an end of life anytime soon. So it's pretty safe to use Dajaxice, if you feel you'll be sticking with Django for the lifetime of your application. I will even explain here how you would go about switching, if you so did desire.

Firstly, we will need a project which you want to AJAXify. Then you need to download and install Dajaxice, then you need to modify your settings.py:

DAJAXICE_MEDIA_PREFIX="dajaxice"
....
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
    'django.template.loaders.eggs.Loader',
)
...
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'dajaxice',
    .....
)

You can change the DAJAXICE_MEDIA_PREFIX to anything you desire, it is used as the virtual directory on your website which serves the AJAX-related views. Next, you need to modify your urls.py:

from django.conf.urls.defaults import patterns, include
from dajaxice.core import dajaxice_autodiscover
from django.conf import settings

dajaxice_autodiscover()

urlpatterns = patterns('',
    ...
    (r'^%s/' % settings.DAJAXICE_MEDIA_PREFIX, include('dajaxice.urls')),
    ...
)

This will use the prefix configured in your settings.py to tell Dajaxice where the views will be loaded from. Next up, we need to modify our base template to tell the browser where to load the Dajaxice JavaScript from:

{% load dajaxice_templatetags %}
...
<head>
...
{% dajaxice_js_import %}
...
</head>

In a production environment, you will want to host this file statically, as it will not change in production. Read the Dajaxice documentation for information this, this tutorial will focus in development mode only.

In order to use Django forms, which will be covered in this tutorial, please download the following file: jquery.ba-serializeobject.js. This jQuery add-in can directly serialize a Django form and send it to your application, which can then natively process it, as if the data was directly posted to it without AJAX. In saying that, go ahead and install jQuery into your application as well. jquery.js should proceed jquery.ba-serializeobject.js.

When doing any AJAX programming, with or without Dajaxice, you need to make sure your applications sends the standard CSRFTOKEN cookie at some point before AJAX is called, or it will fail. I made this mistake before, and since I log into the admin interface, I never realized that end-users were unable to view the site, because they never had a CSRF Cookie to guide them. There are many ways to force Django to spit out a CSRF cookie to the browser, the easiest way is to use the csrf_token templatetag. Our first example will be a simple contact form, so this will do just fine. First, in your app directory, create a module called forms.py and put in the follow:

from django import forms

class ContactForm(forms.Form):
    email = forms.EmailField(label='Your Email')
    subject = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

A very simple re-usable contact form. Feel free to add new fields or custom validation. Next, we need to write a very simple view to merely display the form:

from django.shortcuts import render
from ajaxsite.blog.forms import ContactForm

def contact_form(req):
    return render(req, "blog/contact_form.html", {'form':ContactForm()})

I did not use Class-based views in this example, as this will add more code than there needs to be. However, I will writing a separate AJAX tutorial, which does not use Dajaxice, but uses Class-based views to deliver the AJAX responses via a Mixin. Stay tuned for that tutorial.

Bind the above view to a URL, and place the following code into a template which extends the base page:

<div id="contact_errors"></div>
<form action="" method="post" id="contact_form" accept-charset="utf-8">{% csrf_token %}
  <table>
  {{form}}
  </table>
  <p><input value="Send Message" id="send_msg" onclick="send_message();" type="button" /></p>
</form>

Here is the JavaScript to make it all work:

function message_callback(data){
  if (data.status == 'Success!'){
    $('#contact_errors').html(data.status);
  }else{
    for (message in data.status){
      $('#contact_errors').append("<p><b>" + message + ":</b>" + data.status["message"] + "</p>");
    }
  }
}
function send_message(){
  data = $('#contact_form').serializeObject();
  Dajaxice.blog.send_message(message_callback, {'form':data});
  return false;
}

Now it's time to code in Python again, in your app directory create a file called ajax.py, this contains the views which Dajaxice calls. Here is what it should contain:

from dajaxice.decorators import dajaxice_register
from ajaxsite.blog.forms import ContactForm
from django.utils import simplejson

@dajaxice_register
def send_message(req, form):
    f = ContactForm(form)
    if f.is_valid():
        # Use mail_admin or something to send off the data like you normally would.
        return simplejson.dumps({'status':'Success!'})
    return simplejson.dumps({'status':f.errors})

There we have a very simple contact form. Other examples you can do using Dajaxice is poll the server every few minutes for new messages, and display them dynamically for the user.

My next tutorial in the set will explore using Class-based views to generate AJAX content, and will utilize jQuery's methods for this. The tutorial after that in the set will explore Dajax, and how you can use it's services to code your AJAX callbacks in pure Python.

Disclaimer: The code in this tutorial is not optimized or checked properly for runtime errors, it is merely here to provide a bases on how this functionality works. I normally do not use table with forms, but for the sake of simplicity, it is used in the tutorial above.

Porting your Dajaxice routine to a new backend

In this very short section, I will briefly explain how Dajaxice works behind the scenes so that you know you are not locked in, if you move to a new framework. Here is how the Dajaxice JavaScript from the above tutorial should look:

var Dajaxice = {
    
        blog: {
            
            send_message: function(callback_function, argv, custom_settings){
                Dajaxice.call('blog.send_message', callback_function, argv, custom_settings);
            }
    
        },
        
        ajaxsite: {
    
        blog: {
    
            send_message: function(callback_function, argv, custom_settings){
                Dajaxice.call('ajaxsite.blog.send_message', callback_function, argv, custom_settings);
            }

        }

        }
        ,

    get_cookie: function(name)
    {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = cookies[i].toString().replace(/^\s+/, "").replace(/\s+$/, "");
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    },

    call: function(dajaxice_function, dajaxice_callback, argv, custom_settings)
    {
        var send_data = [];
        var is_callback_a_function = (typeof(dajaxice_callback) == 'function');

        if(!is_callback_a_function){
            alert("dajaxice_callback should be a function since dajaxice 0.2")
        }

        if(custom_settings == undefined){
            custom_settings = {};
        }

        var error_callback = this.get_setting('default_exception_callback');
        if('error_callback' in custom_settings && typeof(custom_settings['error_callback']) == 'function'){
            error_callback = custom_settings['error_callback'];
        }

        send_data.push('argv='+encodeURIComponent(JSON.stringify(argv)));
        send_data = send_data.join('&');
        var oXMLHttpRequest = new XMLHttpRequest;
        oXMLHttpRequest.open('POST', '/dajaxice/'+dajaxice_function+'/');
        oXMLHttpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        oXMLHttpRequest.setRequestHeader("X-CSRFToken",Dajaxice.get_cookie('csrftoken'));
        oXMLHttpRequest.onreadystatechange = function() {
            if (this.readyState == XMLHttpRequest.DONE) {
                if(this.responseText == Dajaxice.EXCEPTION || !(this.status in Dajaxice.valid_http_responses())){
                    error_callback();
                }
                else{
                    var response;
                    try {
                        response = JSON.parse(this.responseText);
                    }
                    catch (exception) {
                        response = this.responseText;
                    }
                    dajaxice_callback(response);
                }
            }
        }
        oXMLHttpRequest.send(send_data);
    },

    setup: function(settings)
    {
        this.settings = settings;
    },

    get_setting: function(key){
        if(this.settings == undefined || this.settings[key] == undefined){
            return this.default_settings[key];
        }
        return this.settings[key];
    },

    default_exception_callback: function(data){
        alert('Something goes wrong');
    },

    valid_http_responses: function(){
        return {200: null, 301: null, 302: null, 304: null}
    }
};

Dajaxice.EXCEPTION = 'DAJAXICE_EXCEPTION';
Dajaxice.default_settings = {'default_exception_callback': Dajaxice.default_exception_callback}

window['Dajaxice'] = Dajaxice;

I removed the minified code at the bottom which is basically both the XML and JSON serializers. The above code should be pretty straightforward. To port your code to your new backend is really easy, you can keep all your existing JavaScript everywhere in your application, and just alter this file. Convenient, is it not? In this file, just change the Dajaxice prototype above to call the new $.ajax method for your new backend framework. Here is a simple example of how you would change the send_message function:

            send_message: function(callback_function, argv, custom_settings){
                  $.ajax({
                     type: "POST",
                     url: "some.php",
                     data: argv
                  }).done(callback_function);
            }

See easy-peasy, better than this, would be to modify the Dajaxice.call method, which would minimize the effort required:

    call: function(dajaxice_function, dajaxice_callback, argv, custom_settings)
    {
        $.ajax({
            type: "POST",
            url: "some.php?dispatch="+dajaxice_function,
            data: argv
        }).done(dajaxice_callback);
    }

There are many ways to change over from Dajaxice to a new backend framework, so don't be afraid to use Dajaxice. It won't byte, I promise you that.

April 19, 2012, 3:38 p.m. - g1ra

thank you for this tutorial. actually how are using dajax/dajaxice in production?

April 20, 2012, 10:06 a.m. - g1ra

thank you for this tutorial. actually who are using dajax/dajaxice in production?

June 5, 2012, 4:22 a.m. - Kevin Veroneau

Yes, I am using it on this website, and an internal tool I built at my workplace, and it works wonders. It is really easy to add new components to the website which utilize AJAX calls.

July 4, 2012, 9:08 a.m. - Pablo

Good article. It was very useful for me.

Aug. 1, 2012, 8:36 a.m. - Maksymilian Pachucki

Is it possible to run Dajax and Dajaxice with simple AJAX ? Does it interfere with each other in a bad way ? I have few functions in Dajax and one app called EndlessPagination when custom AJAX occurs. Half of my dajax functions didnt work correctly while endless is working. In other requests of views.py everything is alright. Do you have any ideas how can I solve this problem ? Mayby it is Dajax fault and I should not use it anymore ?

About Me

My Photo
Names Kevin, hugely into UNIX technologies, not just Linux. I've dabbled with the demons, played with the Sun, and now with the Penguins.




Kevin Veroneau Consulting Services
Do you require the services of a Django contractor? Do you need both a website and hosting services? Perhaps I can help.

If you like what you read, please consider donating to help with hosting costs, and to fund future books to review.

Python Powered | © 2012-2014 Kevin Veroneau