Performing AJAX POST Requests in Django

11, Jun 2014

Contents

A common pitfall that shows up when developing a Django application is when you try and make your first POST request to your server from AJAX. As a response you receive a helpful 403 FORBIDDEN notice, and not much other information. There's a fairly simple way of handling this issue in a seamless fashion, which I'll cover here, and then we can take it a little bit further.

Firstly, let's discuss the actual problem that is causing this. Django comes with a security feature called Cross Site Request Forgery protection. A CSRF attack is when some external malicious site contains a link with some JavaScript that is intended to perform an action on your web site using the credentials of a logged-in-user who visited the malicious site in their browser. To protect against this, Django adds a CSRF token to every request that must be included with every unsafe HTTP request method (POST, PUT, and DELETE). This random string is verified upon every request, and if it is not valid (or not present) the server will respond with 403 FORBIDDEN.

Let's Get Started

So, assuming you already have a Django project all setup and ready to go, we're going to create a view and a template to show the POST request in action. Just to keep things simple, we're going to do this in a separate app, so go ahead and create a new app and add it to your INSTALLED_APPS list.

Inside of that app let's modify the views file and make it look like this:

from django.views.generic import TemplateView
from django.http import HttpResponse

import json

class PostExample(TemplateView):
    template_name = 'start.html'

    def post(self, request):
        return HttpResponse(json.dumps({'key': 'value'}), mimetype="application/json")

What we're doing here is creating an incredibly basic view that on a GET request will respond with the 'start.html' template and on a post request will respond with a hard coded JSON dictionary. Now we need to add the URL for this view to the project:

url(r'^$', PostExample.as_view(), name='test-start'),

Template Starter

The template we're going to develop here will be equally simple, let's start with the following:

<!DOCTYPE html>
<html>
<head>
<script src="{{ STATIC_URL }}js/jquery-1.11.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
    $("#post").click(function(e) {
        e.preventDefault();
        var data = {
            'foo': 'bar'
        }

        $.ajax({
            "type": "POST",
            "dataType": "json",
            "url": "/test/",
            "data": data,
            "success": function(result) {
                console.log(result);
            },
        });
    });
});
</script>
</head>
<body>
    <h1>PostExample</h1>
    <p><a href="" id="post">Post Request</a></p>
</body>
</html>

jQuery Magic

If you were to go to that page and click the link, you instead of a lovely JSON response you would see the 403 FORBIDDEN notice. Let's take care of that.

The way to solve this is by overriding the jQuery beforeSend method on an AJAX query and grabbing the CSRF token embedded in the request and including it in the POST headers. Create a new JavaScript file and add the following to it, and make sure to include it into your template:

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // 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;
}

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
            // Only send the token to relative URLs i.e. locally.
            xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
        }
    }
});

With that being done, all you have to do is add the CSRF token into the template like this:

{% csrf_token %}

That takes care of it! You can now make AJAX POST requests from within your application, without doing any specific work on a per instance basis.

Next Steps

Locally Modifying beforeSend

Due to the way $.ajaxSetup works, this beforeSend custom method is now applied to every AJAX request ($.get, $.post, and $.ajax included). What happens if you want to do some custom work in the beforeSend method on a particular request? You can call an instance of beforeSend, do your custom work, and then call the global ajaxSettings.beforeSend method. This looks something like this:

$.ajax({
    "type": "POST",
    "dataType": "json",
    "url": /test/",
    "data": data,
    "beforeSend": function(xhr, settings) {
        console.log("Before Send");
        $.ajaxSettings.beforeSend(xhr, settings);
    },
    "success": function(result) {
        console.log(result);
    }
})

Setting Other Global AJAX Options

We're not limited to just beforeSend. in $.ajaxSetup you can configure all the options that are available on $.ajax, and these defaults will be applied to all AJAX requests. A useful example that comes to mind immediately is error handling. Having AJAX requests all behave the same way when an error occurs is greatly preferred to having to individually setup error handling. We can do this by adding the following to the code:

$.ajaxSetup({
    error: function(xhr, textStatus, error) {
        console.log(error);
    }
});

Finished

That's all there is to it. With this bit of code you can have seamless POST requests, and consistent error handling of AJAX requests throughout your application. You can see all the other options you can set in $.ajaxSetup on the jQuery documentation.

Comments

This space intentionally left blank.

Have Something to Say?

Questions? Comments? Concerns? Let me know what you’re thinking.

  • You can use Markdown formatting here.