Django – AJAX

This post is part of my Django series. You can see an overview of the series along with instruction on how to get all the source code here.

This article assumes you are comfortable creating a Django project, can create apps and register them into the INSTALLED_APPS list of the settings file. If not please read my Django HelloWorld article.

This article assumes you have a project called DjangoSandBox with an application called ajaxintroduction.

Overview

In these examples are we are going to create a page which uses AJAX to GET and POST data between the server.

The page is based around a ‘Quote’ object which contains the quote and the author. We will provide a single page which has the following functionality:

  • Get all quotes for an author
  • Get a random quote
  • Create a quote.

The following examples are split out but in the repository this is a single page. I strongly urge you to work from the source code within the repository.

Initial Page

So before we start on the AJAX calls we need an initial page up and running.

First lets create our model.

#models.py
from django.db import models
from django.db.models import Model

class Quote(Model):
    quote = models.TextField()
    author = models.CharField(max_length=50)

    def __str__(self):
        return "{0}: {1}".format(self.author, self.quote)

Make and run the migrations:

python manage.py makemigrations ajaxintroduction
python manage.py migrate

Next is our view, currently we simply want to return our template:

#views.py

from django.shortcuts import render

def get_quotes(request):
    return render(request, "ajaxintroduction/random_quotes.html")

Next our template; nothing more than a heading currently.

<!-- templates/ajaxintroduction/random_quotes.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Daily Quote!</title>

    <style>
        span {
            display: block;
        }
        span span {
            display: inline;
        }
    </style>
</head>
<body>

<h1>AJAX Examples</h1>
</body>

Then we hook in our view into our URL routing configuration.

# urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = 
    patterns('',
    url(r'^$', views.get_quotes, name="index"),
)

Now hook our application into our project URL routing configuration:

#DjangoSandBox/urls.py

from django.conf.urls import patterns, include, url

from ajaxintroduction import urls as ajax_urls

urlpatterns = 
    patterns('',
             url(r'^ajax/', include(ajax_urls, namespace="ajax")),
             )

To test run the development server and navigate to:

http://127.0.0.1:8000/ajax/

You should see the template page loaded with our heading.

Static Files

Next we are going to configure our static files:

  • JQuery which we use for performing AJAX calls in JavaScript
  • JQuery-Cookies which will use to grab the CSRF token from our cookie
  • A new js file which we will place all our required JavaScript code in

First create an empty text file in ajaxintroduction/static/ajaxintroduction/js/ called ajax-example.js

// ajaxintroduction/static/ajaxintroduction/js/ajax-example.js

// Currently empty

Next download JQuery and JQueryCookies. You can download them from the references below if you have not downloaded the repository code of my examples. Alternatively you can download the files directly from GitHub

Note: if you are not downloading the files from my repository ensure you file names match with regards to the versions throughout the examples.

Copy in jquery and jquery.cookie into and DjangoSandBox/static/js

We now need to register these files as being static files which can be served directly to the user.

Within the settings file register the paths of the static directory of the project as well as the application directory of our ajaxintroduction application.

# settings.py
STATIC_URL = '/static/'

STATICFILES_DIRS =  (
    os.path.join(PROJECT_ROOT, 'static'),
    os.path.join(PROJECT_ROOT, 'ajaxintroduction', 'static'),
)

Now are are going to reference all these static files in our html head.

  • Call “load staticfiles” to enable the use of static files in our template
  • Reference the file with the static template tag. Files are referenced relative to the composition of STATICFILES_DIRS/STATIC_URL:
    • For our project files we reference js/file.js
    • For our application files we reference ajaxintroduction/js/file.js

Note: similar to templates we duplicate the application name within the directory path of our application static files as a way of name spacing our files.

<!-- templates/ajaxintroduction/random_quotes.html -->
<head>
    {% load staticfiles %}

    <script src="{% static "js/jquery-2.1.4.js" %}"></script>
    <script src="{% static "js/jquery.cookie.js" %}"></script>
    <script src="{% static "ajaxintroduction/js/ajax-example.js" %}"></script>    
</head>

To test run the development server and navigate to:

http://127.0.0.1:8000/ajax/

You should be able to use FireBug or Developer Tools to view the contents of the JavaScript files which have been downloaded along side our generated HTML.

If you look at the HTML code you will see the file path of the static file has been completed as a relative URL from our website route prefixed with static; static/ajaxintroduction/js/ajax-example.js

Note: if the files have not been downloaded use FireBug or Developer Tools to check the network requests of these resource files. Check the URL of the request is as expected. Check the response. Try entering in the full URL of the resource file into the browser:

http://127.0.0.1:8000/static/ajaxintroduction/js/ajax-example.js

Get – A Random Quote

We are now going to create the functionality to get a random quote. First we create a view which gets a random Quote record. We return the record as JSON data using the inbuilt Python json library:

import json
context_data = {random_quote.author: random_quote.quote}
json.dumps(context_data)

We then return the JSON data via a HTTPResponse setting the content_type to “application/json”. Add the following view function:

#views.py
import json
from random import randint

from django.http import HttpResponse

from .models import Quote

def get_random_quote(request):
    random_number = randint(0, Quote.objects.all().count() - 1)

    random_quote = Quote.objects.all()[random_number]
    context_data = {random_quote.author: random_quote.quote}

    return HttpResponse(json.dumps(context_data), content_type="application/json")

We then create a simple button and a span element to display our random quote. Both require an id as this is how we will be finding the element via JQuery. Add the following into our template file.

<!-- templates/ajaxintroduction/random_quotes.html -->

<h2>Get Random Quote:</h2>
<span>
    <button type="button" id="getquote">Get:</button>
    <span id="quote"></span><span id="author"></span>
</span>

Note: the above should be added into our existing html template file and not replacing its contents.

We now hook up our button to our view which returns our random quote as JSON data within our JavaScript file:

// ajaxintroduction/js/ajax-example.js
$(document).ready(function () {
    $("#getquote").click(function () {
        getQuote();
    });
});

function getQuote() {
    $.getJSON("/ajax/random", function (data) {
        console.log("getQuote.response:");
        console.log(data);
        $.each(data, function (author, quote) {
            $('#quote').text(quote);
            $('#author').text(author);
        });
    });
}
  • The $(document).ready is a special JQuery function which only runs when the html page has finished loading within the browser. We use it to bind our getQuote function onto the click event of our button.
  • The getJSON function is used to make a call to our view via AJAX. The first parameter is a relative URL which we will configure up below in our URL routing configuration. The second parameter is a call back function which is passed our returned random quote as JSON data.
  • We use the JQuery each function to iterate and unpack our JSON data which we display within our span elements,

We now hook in our view to our URL routing configuration.

# urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = 
    patterns('',
    url(r'^random/$', views.get_random_quote, name="random"),
)

To test run the development server and navigate to:

http://127.0.0.1:8000/ajax/

Pressing the button will return and display a random quote.

Note: if the AJAX request does not appear to work use FireBug or Developer Tools to check the request. Check the URL, the parameters and it’s status. To see any errors which might be on the actual AJAX view navigate to the URL of the AJAX request.

You can also navigate directly to the new view function via:

http://127.0.0.1:8000/ajax/random

Get Multiple

In our next example we are going to allow the retrieval of all quotes for an author.

First we change our initial page view to return a list of all distinct authors.

Note: SQLite3 does not support the distinct SQL command so we use the Python set collection to create a distinct list of authors.

Secondly we create a new view which looks up all quotes for an author which we will pass in as a GET parameter. We return the quotes as a QuerySet.

The Django QuerySet is not serializable via the inbuilt Python json library. Django provides the serializers class which can.

#views.py
import json
from random import randint

from django.core import serializers
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render

from .models import Quote

def get_quotes(request):
    authors = set(r.author for r in Quote.objects.all())

    return render(request, "ajaxintroduction/random_quotes.html", context={"authors": authors})


def get_author_quotes(request):
    if "author" not in request.GET or request.GET["author"] == "all":
        quotes = Quote.objects.all()
    else:
        author_name = request.GET["author"]
        quotes = Quote.objects.filter(author=author_name)

    return HttpResponse(serializers.serialize('json', quotes), content_type="application/json", )

We next now create our UI. This will consist of a choice list of the authors, a button and a span element for displaying the results.

<!-- templates/ajaxintroduction/random_quotes.html -->

<h2>Get Quotes By Author:</h2>
<span>
    <select id="authors" title="Fpop dfic">
        <option value="all">All</option>
        {% for author in authors %}
            <option value="{{ author }}">
                {{ author }}
            </option>
        {% endfor %}
    </select>
    <button type="button" id="getAuthorQuotes">Get</button>
</span>
<span id="quotesbyauthor"></span>

Now our JavaScript; first hook our function onto our button within the JQuery document ready function.

The only real difference between our call to getJSON in the previous example is that we pass in the selected author from the select box as a GET parameter. GET parameters are constructed from key value pairs represented by a JSON string.

Again we use the JQuery function ‘each’ to loop through all quotes unpacking them into a function as key value pair. We construct a series of list elements and add them into our span element.

// ajaxintroduction/js/ajax-example.js
$(document).ready(function () {
    $("#getAuthorQuotes").click(function () {
        getAuthorQuotes();
    });
});

function getAuthorQuotes() {
    $.getJSON("/ajax/author/", {author: $('#authors').val()}, function (data) {
            console.log("getAuthorQuotes.response:");
            console.log(data);

            var $quotesbyauthor = $('#quotesbyauthor');
            $quotesbyauthor.text("");

            var items = [];

            $.each(data, function (key, value) {
                items.push("<ul>" + value.fields["quote"] + ". " + value.fields["author"] + "</ul>");
            });

            $("<ul/>", {
                "class": "quotes",
                html: items.join("")
            }).appendTo($quotesbyauthor);
        }
    );
}

Now we simply need to hook in our view into our URL routing configuration.

# urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = 
    patterns('',
    url(r'^author/$', views.get_author_quotes, )

To test run the development server and navigate to:

http://127.0.0.1:8000/sessionsandcookies/ajax/

You can now ask for all quotes for an author. If you cannot get this to work check the requests being made along with the results. Try navigating to the URL directly.

Post -Create A Quote

There is no real fundamental difference between a HTTP POST or a GET. The method is used as a status; both can contain URL parameters and both can contain a body.

It is advisable that GET requests should never perform CRUD operations. We will use a POST to create our Quote record.

We are going to allow the user to create a quote asynchronously from our page. We will create a new view function which takes our quote and author as strings, ensures they are set and then attempts to create the quote. We will use get_or_create to ensure we don’t create duplicate quotes.

We simply use request.POST[“XXX”] to get our form element value on the server side. For more complicated examples you should think about writing a dedicated Django model form.

We then return an indication of whether the quote was created, a validation message and also the new number of quotes in the database. We return the data as JSON but using the JsonResponse; this will automatically serialise the data to JSON as well as setting the content type of the response to JSON.

# views.py
import json
from random import randint

from django.core import serializers
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render

from .models import Quote

# @csrf_exempt # Use for debugging only.
def new_quote(request):
    message = ""

    is_valid = True
    created = False

    if "quote" not in request.POST or "author" not in request.POST:
        message = "Invalid post data"
        is_valid = False

    if request.POST["quote"] == "" or request.POST["author"] == "":
        message = "Please provide a quote and an author"
        is_valid = False

    if is_valid:
        quote, created = Quote.objects.get_or_create(quote=request.POST["quote"], author=request.POST["author"])

        if created:
            message = "Quote Created"
        else:
            message = "Quote already exists"

    data = {"count": Quote.objects.all().count(), "message": message, "created": created}

    return JsonResponse(data)

We now add a form onto our template which will be used to capture the data required to create our Quote record. We also add a button. We include a csrf_token in our form to prevent from cross site forgery attacks.

<!-- templates/ajaxintroduction/random_quotes.html -->

<h2>Create Quote:</h2>
<form method="post" action="">
    {% csrf_token %}
    <table>
        <tr>
            <th>Quote</th>
            <th>Author</th>
        </tr>
        <tr>
            <td><input type="text" id="txtquote"/></td>
            <td><input type="text" id="txtauthor"/></td>
        </tr>
    </table>
    <button type="button" id="newquote">New Quote</button>
</form>

We now hook in our JavaScript; this is where it starts to get a bit tricky as we need to post back our csrf_token as part of the POST data.

Django writes the token value to a cookie called csrftoken which we can access in JavaScipt via the JQuery-Cookies library. We hooked this file in above as a static file. We should only include this if our HTTP method is not one of GET, HEAD, OPTIONS or TRACE. We set the token in the X-CSRFToken HTTP request header parameter. We use the JQuery ajaxSetup function to do this.

If you want to test posting back without Django validating the presence and validity of the token you can decorate your view with the @csrf_exempt decorator. This should only be used for testing/development purposes.

We then call our new view URL passing in the data of the new quote we would like to create.

We then display our validation message and the new count of elements.

// ajaxintroduction/js/ajax-example.js
function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
        }
    }
});

$(document).ready(function () {
    $("#newquote").click(function () {
        createNewQuote();
    });
});

function createNewQuote() {
    $.ajax({
        type: "POST",
        url: "/ajax/new/",
        data: {
            'quote': $('#txtquote').val(),
            'author': $('#txtauthor').val()
        },
        success: function (data) {
            console.log("createNewQuote.response:");
            console.log(data);

            alert(data.message);

            if (data.created) {
                alert("There are now " + data.count + " quotes");
            }
        }
    });
}

The last thing we need to do is to hook our new view into our URL routing configuration.

# urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = 
    patterns('',
    url(r'^new/$', views.new_quote, name="new"),
)

To test run the development server and navigate to:

http://127.0.0.1:8000/sessionsandcookies/ajax

##References## {#References}

Advertisements

2 thoughts on “Django – AJAX

  1. Do you think this would be the best approach concerning something like cascading select boxes, parent-child dropdown boxes, etc (it’s worded so many ways when searching the web) where say a child queryset live updates based on the parent selection without reloading whole webpages(Car manufacturers dropdown causes a brand dropdown box to filter)? It seems to be a hot topic in searches with no clear solution and as a Django noob is quite daunting. Ty for any help or recommendations.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s