RkBlog

Hardware, programming and astronomy tutorials and reviews.

Making Django and Javascript work nicely together

Solutions for efficient backend - frontend cooperation in Django projects.

Backend and frontend cooperation may not be an easy task. For example in javascript code you may need some URLs to views or to icons required for some widgets. You can hardcode it or cleverly pass required data from backend to frontend. Also AJAX requests often want batches of data in JSON format. There is a way to make it quickly and clean. In this article I'll show some Django applications and solutions for much easier backend - frontend cooperation.

django-javascript-settings

django-javascript-settings is a Django application that can pass variables from backend to JavaScript. You can install it with:
pip install django-javascript-settings

Next add 'javascript_settings', to INSTALLED_APPS.

In your main HTML template add:
{% load javascript_settings_tags %}
<script type="text/javascript">{% javascript_settings %}</script>
In result we will get:
<script type="text/javascript">var configuration = {};</script>

We got a "configuration" variable with an empty array.

To add something to that array you need to define a javascript_settings function in urls.py of your django application. This function has to return a dictionary:
def javascript_settings():
    js_conf = {
        'page_title': 'Home',
    }
    return js_conf
And that will result in:
<script type="text/javascript">var configuration = {"jsdemo": {"page_title": "Home"}};</script>
I placed the code in "jsdemo" application so I got configuration['jsdemo']['page_title'].

django-javascript-settings can be used to pass view URLs (using reverse function), urls to needed graphics, or rendered templates and so on.

AJAX Requests

Making AJAX requests with jQuery is very easy. For this example I created a main page template:
{% load javascript_settings_tags %}
<!DOCTYPE html>
<html> 
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script type="text/javascript">{% javascript_settings %}</script>
    <script src="{{ STATIC_URL }}scripts.js"></script>
</head> 
<body> 
    <h1>Demo</h1>
</body>
</html>

In the "static" folder of my application I created scripts.js file in which I'll write some jQuery code that will make an AJAX request to a given view.

The views look like so:
from django.views.generic import TemplateView, View
from django.http import HttpResponse

class HomeView(TemplateView):
    template_name = "home.html"

home = HomeView.as_view()


class AjaxView(View):
    def get(self, request, **kwargs):
        return HttpResponse('ok')

ajax_view = AjaxView.as_view()
We have the main page view and second one for the AJAX request. In application urls.py I map the view to an URL and pass it to JavaScript:
from django.conf.urls import patterns, url
from django.core.urlresolvers import reverse

urlpatterns = patterns('jsdemo.views',
    url(r'^ajax/$', 'ajax_view', name='ajax-view'),
)

def javascript_settings():
    js_conf = {
        'ajax_view': reverse('ajax-view'),
    }
    return js_conf
The application urls.py file must be added in the main urls.py file:
url(r'^demo/', include('jsdemo.urls')),
And the AJAX request in scripts.js:
$(document).ready(function(){
    $.ajax({
	url: configuration['jsdemo']['ajax_view'],
	cache: false,
	type: "GET",
	success: function(data){
	}
    });
});

This covers all the elements. The request should now work and you can check it in Firebug or other browser developers console. The request should return "ok" from the view.

This simple code makes a GET request. Quite often POST requests are needed - like when some data is sent. jQuery part is very easy:

$(document).ready(function(){
    $.ajax({
	url: configuration['jsdemo']['ajax_view'],
	cache: false,
	type: "POST",
	data: {"key": "value"}, 
	success: function(data){
	}
    });
});

In "AjaxView" we change "get" to "post" and reload the page. The AJAX request should fail - 403, "CSRF verification failed. Request aborted.". We made a POST request without the csrf token (what we put in forms). You could use csrf_exempt on the view but that isn't the best solution for this problem.

In csrf Django documentation we can find a solution. We have to set X-CSRFToken header with the csrf cookie value. Using jQuery Cookie it will be easy:

$(document).ready(function(){
    $.ajax({
	url: configuration['jsdemo']['ajax_view'],
	cache: false,
	type: "POST",
	data: {"key": "value"}, 
	success: function(data){
	},
	beforeSend: function(xhr, settings){
	    xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
	}
    });
});
Using beforeSend we can set the X-CSRFToken token. It's almost done. If your browser has the "csrftoken" cookie the request will work, but if it's not there it will still fail. Django won't make the cookie if it's not needed. To ensure cookie existence add 'django.views.decorators.csrf._EnsureCsrfCookie', to MIDDLEWARE_CLASSES.

We have a fully working AJAX request. Now it's time to use JSON to get a batch of data from the view.

Just import "json" module and modify the view to:
class AjaxView(View):
    def post(self, request, **kwargs):
        data = {'user_id': self.request.user.id,
                'name': self.request.user.username}
        data = json.dumps(data)
        return HttpResponse(data)

ajax_view = AjaxView.as_view()
That will work, but we can do it even better.

Django-annoying

django-annoying is a set of decorators and helpers shortening some common tasks. We can use the ajax_request decorator to improve our view. If we use this decorator on a view it has to return a dictionary. The decorator will encode it to JSON and will return a JsonResponse (like HttpResponse but with correct JSON mimetype).

We can instal django-annoying the usuall way:
pip install django-annoying
Nothing for INSTALLED_APPS, just use the decorator:
from annoying.decorators import ajax_request
from django.views.generic import TemplateView, View

class HomeView(TemplateView):
    template_name = "home.html"

home = HomeView.as_view()


class AjaxView(View):
    def post(self, request, **kwargs):
        return {'user_id': self.request.user.id,
                'name': self.request.user.username}

ajax_view = ajax_request(AjaxView.as_view())
And that's it. Quick and clean.
RkBlog

9 July 2012;

Comment article