Author avatar

Gaurav Singhal

How to Work with AJAX in Django

Gaurav Singhal

  • Nov 18, 2019
  • 18 Min read
  • 62 Views
  • Nov 18, 2019
  • 18 Min read
  • 62 Views
Languages Frameworks and Tools
Django

Introduction

AJAX stands for Asynchronous JavaScript And XML, which allows web pages to update asynchronously by exchanging data to and from the server. This means you can update parts of a web page without reloading the complete web page. It involves a combination of a browser built-in XMLHttpRequest object, JavaScript, and HTML DOM.

How AJAX Works

  1. An event occurs on a web page, such as an initial page load, form submission, link or button click, etc.
  2. An XMLHttpRequest object is created and sends the request to the server .
  3. The server responds to the request.
  4. The response is captured and then server respond back with response data.

There are many scenarios where you may want to make GET and POST requests to load and post data from the server asynchronously, back and forth. Additionally, this enables web applications to be more dynamic and reduces page load time.

Initial Setup

For this guide, we will use the jQuery library to easily implement JavaScript; moreover, we'll also use Bootstrap 4 to make the application look good.

Below is the base.html template, which includes the jQuery library and bootstrap framework. Make sure you include this link and script correctly. Also, note the content and javascript blocks, which are used later in this guide.

base.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{% block title %}{% endblock title %}</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
    {% block style %}
    {% endblock style %}
</head>

<body>
    {% block content %}
    {% endblock content %}

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
    {% block javascript %}
    {% endblock javascript %}
</body>

</html>
django

I am assuming that you already know how to do Django setup. If not, or if you are new to Django, please follow the Django documentation for initial setup.

Also, note that I have used Django version 2.2 for this guide.

Jump to Code

To make the guide more interactive, we will use a real-time example to demonstrate the POST and GET AJAX requests in Django.

We'll use a ScrapBook scenario in which a user can create a friend and the app will show it dynamically. It will also check if the nickname is already taken or not by sending a GET request to the server.

To get excited about what we're building, check out this screenshot. At the end of this guide, you'll be able to build the following app.

screenshot

Let's start by creating a Django app called "my_app" with the startapp command. Be sure to run the following manage.py commands where your manage.py lives, i.e., in your project folder.

1
$ python manage.py startapp my_app
shell

After creating the Django app, make sure you add it in INSTALLED_APPS in settings.py .

settings.py

1
2
3
INSTALLED_APPS += [
    'my_app',
]
python

Creating Models

Let's create an example model for a Friend with a minimal number of attributes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.db import models

# Create your models here.
class Friend(models.Model):
    # NICK NAME should be unique
    nick_name = models.CharField(max_length=100, unique =  True)
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    likes = models.CharField(max_length = 250)
    dob = models.DateField(auto_now=False, auto_now_add=False)
    lives_in = models.CharField(max_length=150, null = True, blank = True)

    def __str__(self):
        return self.nick_name
python

After creating the models, perform makemigrations and migrate by running the following commands.

1
2
$ python manage.py makemigrations
$ python manage.py migrate
shell

Then, run the Django server.

1
$ python manage.py runserver
shell

POST Request

To submit the form, we need to make a POST request to the server with all the form values filled by the user.

1. Creating Forms

Let's create the Django form by inheriting ModelForm. In the FriendForm, I have changed the dob field and enabled a widget of DateField with some changes in years. And also note that in __init__ method, I have updated an HTML class attribute with form-control to every field of the form so that Bootstrap gets enabled on every field.

Finally, in the Meta subclass, I have included the modal class and fields that are likely to be displayed.

Note that I have created a new file called forms.py in my app folder.

forms.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from .models import Friend
from django import forms
import datetime

class FriendForm(forms.ModelForm):
    ## change the widget of the date field.
    dob = forms.DateField(
        label='What is your birth date?', 
        # change the range of the years from 1980 to currentYear - 5
        widget=forms.SelectDateWidget(years=range(1980, datetime.date.today().year-5))
    )
    
    def __init__(self, *args, **kwargs):
        super(FriendForm, self).__init__(*args, **kwargs)
        ## add a "form-control" class to each form input
        ## for enabling bootstrap
        for name in self.fields.keys():
            self.fields[name].widget.attrs.update({
                'class': 'form-control',
            })

    class Meta:
        model = Friend
        fields = ("__all__")
python

2. Creating Views

After creating the form, let's import FriendForm in the views. There are two views that need to be discussed in this section, and they are indexView and postFriend.

  • indexView creates the FriendForm object, takes all the friends objects from the database, and sends them to the index.html template, which we will discuss later.
  • postFriend is AJAX POST view, which handles the POST request. You will notice that it is similar to a regular view, but with some changes, such as JsonResponse and serialize. We have used these methods because it is an AJAX view, so we need to deal with JSON only.

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django.http import JsonResponse
from django.core import serializers
from .forms import FriendForm
from .models import Friend

def indexView(request):
    form = FriendForm()
    friends = Friend.objects.all()
    return render(request, "index.html", {"form": form, "friends": friends})

def postFriend(request):
    # request should be ajax and method should be POST.
    if request.is_ajax and request.method == "POST":
        # get the form data
        form = FriendForm(request.POST)
        # save the data and after fetch the object in instance
        if form.is_valid():
            instance = form.save()
            # serialize in new friend object in json
            ser_instance = serializers.serialize('json', [ instance, ])
            # send to client side.
            return JsonResponse({"instance": ser_instance}, status=200)
        else:
            # some form errors occured.
            return JsonResponse({"error": form.errors}, status=400)

    # some error occured
    return JsonResponse({"error": ""}, status=400)
python

3. Creating URLs

For the above views, let's create a URL path for each view. Note the name given to the postFriend path, which will be used in the template discussed later in this guide.

urls.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.urls import path
from my_app.views import (
    indexView,
    postFriend, 
)

urlpatterns = [
    # ... other urls
    path('', indexView),
    path('post/ajax/friend', postFriend, name = "post_friend"),
    # ...
]
python

4. Creating Templates

Now that you've created the backend, let's move to the frontend part of this guide.

In the index.html, we will first extend our base.html, which is being discussed earlier in this guide. Moreover, will write the content between the blocks.

The template is divided into two parts. The first part renders the form, and the second displays the previous stored friends objects in the table.

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{% extends "base.html" %}

{% block content %}

<div class="container-fluid">
    <form id="friend-form">
        <div class="row">
            {% csrf_token %}
            {% for field in form %}
            <div class="form-group col-4">
                <label class="col-12">{{ field.label }}</label>
                {{ field }}
            </div>
            {% endfor %}
            <input type="submit" class="btn btn-primary" value="Create Friend" />
        </div>
    <form>
</div>
<hr />

<div class="container-fluid">
    <table class="table table-striped table-sm" id="my_friends">
        <thead>
            <tr>
                <th>Nick name</th>
                <th>First name</th>
                <th>Last name</th>
                <th>Likes</th>
                <th>DOB</th>
                <th>lives in</th>
            </tr>
        </thead>
        <tbody>
        {% for friend in friends %}
        <tr>
            <td>{{friend.nick_name}}</td>
            <td>{{friend.first_name}}</td>
            <td>{{friend.last_name}}</td>
            <td>{{friend.likes}}</td>
            <td>{{friend.dob | date:"Y-m-d"}}</td>
            <td>{{friend.lives_in}}</td>
        </tr>
        {% endfor %}
        </tbody>
    </table>

</div>
{% endblock content %}
django

Now let's move to the JavaScript part of this guide.

On submitting the form, serialize the form data and create an AJAX POST request, then send it to the server.

On successful request, append the row to the table.

Note we have used the revered URL, which is discussed in the urls.py section.This helps you not to write the URL path in a hardcoded way.

You can put this reverse URL tag in the HTML attribute and then fetch the attribute afterwards. So put this JavaScript code in the js file.

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{% block javascript %}
<script>
    /*
        On submiting the form, send the POST ajax
        request to server and after successfull submission
        display the object.
    */
    $("#friend-form").submit(function (e) {
        // preventing from page reload and default actions
        e.preventDefault();
        // serialize the data for sending the form data.
        var serializedData = $(this).serialize();
        // make POST ajax call
        $.ajax({
            type: 'POST',
            url: "{% url 'post_friend' %}",
            data: serializedData,
            success: function (response) {
                // on successfull creating object
                // 1. clear the form.
                $("#friend-form").trigger('reset');
                // 2. focus to nickname input 
                $("#id_nick_name").focus();

                // display the newly friend to table.
                var instance = JSON.parse(response["instance"]);
                var fields = instance[0]["fields"];
                $("#my_friends tbody").prepend(
                    `<tr>
                    <td>${fields["nick_name"]||""}</td>
                    <td>${fields["first_name"]||""}</td>
                    <td>${fields["last_name"]||""}</td>
                    <td>${fields["likes"]||""}</td>
                    <td>${fields["dob"]||""}</td>
                    <td>${fields["lives_in"]||""}</td>
                    </tr>`
                )
            },
            error: function (response) {
                // alert the error if any error occured
                alert(response["responseJSON"]["error"]);
            }
        })
    })
</script>
{% endblock javascript %}
django

GET Request

Now let's move on to the GET request. In our current scenario, before submitting the form, we can check if a nickname already exists in the database or not by sending the currently entered nickname back to the server.

Following is the screenshot of what we are to going to build in this section.

screenshot

1. Creating Views

Let's create the view for the following scenario. In the checkNickName view, we first take the nickname which has been sent by the AJAX request and then check whether any friend has this nickname in the database. If it already exists, then we return with valid as False, else True.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.http import JsonResponse
from .models import Friend

def checkNickName(request):
    # request should be ajax and method should be GET.
    if request.is_ajax and request.method == "GET":
        # get the nick name from the client side.
        nick_name = request.GET.get("nick_name", None)
        # check for the nick name in the database.
        if Friend.objects.filter(nick_name = nick_name).exists():
            # if nick_name found return not valid new friend
            return JsonResponse({"valid":False}, status = 200)
        else:
            # if nick_name not found, then user can create a new friend.
            return JsonResponse({"valid":True}, status = 200)

    return JsonResponse({}, status = 400)
python

2. Creating URLs

For the above view, let's create a URL route path named validate_nickname.

1
2
3
4
5
6
7
8
9
10
from django.urls import path
from my_app.views import (
    checkNickName
)

urlpatterns = [
    # ...other urls
    path('get/ajax/validate/nickname', checkNickName, name = "validate_nickname")
    # ...
]
python

3. Creating Templates

Now let's write the AJAX GET request on the focusout event on the nick_name input by grabbing the current nick_name value and sending it to the server.

After a successful GET request, notify whether the nick_name is taken or not.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{% block javascript %}
<script>
    /*
    On focus out on input nickname,
    call AJAX get request to check if the nickName
    already exists or not.
    */
    $("#id_nick_name").focusout(function (e) {
        e.preventDefault();
        // get the nickname
        var nick_name = $(this).val();
        // GET AJAX request
        $.ajax({
            type: 'GET',
            url: "{% url 'validate_nickname' %}",
            data: {"nick_name": nick_name},
            success: function (response) {
                // if not valid user, alert the user
                if(!response["valid"]){
                    alert("You cannot create a friend with same nick name");
                    var nickName = $("#id_nick_name");
                    nickName.val("")
                    nickName.focus()
                }
            },
            error: function (response) {
                console.log(response)
            }
        })
    })
</script>
{% endblock javascript %}
django

BONUS: Using Class Bases Views

If you have some experience with Django, then you probably know that you can create views by function and by Class. Most developers get confused about which to use and when. So in this short guide, let's convert the above FBV code to CBV code.

For this guide, I have combined the indexView and postFriend functions into a single class called FriendView, which inherits the View class and has two methods called get and post, respectively.

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from django.shortcuts import render
from django.http import JsonResponse
from django.core import serializers
from .forms import FriendForm
from .models import Friend

from django.views import View

class FriendView(View):
    form_class = FriendForm
    template_name = "index.html"

    def get(self, *args, **kwargs):
        form = self.form_class()
        friends = Friend.objects.all()
        return render(self.request, self.template_name, 
            {"form": form, "friends": friends})

    def post(self, *args, **kwargs):
        if self.request.is_ajax and self.request.method == "POST":
            form = self.form_class(self.request.POST)
            if form.is_valid():
                instance = form.save()
                ser_instance = serializers.serialize('json', [ instance, ])
                # send to client side.
                return JsonResponse({"instance": ser_instance}, status=200)
            else:
                return JsonResponse({"error": form.errors}, status=400)

        return JsonResponse({"error": ""}, status=400)
python

urls.py

Let's write the urlpattern for the above discussed CBV.

1
2
3
4
5
6
7
8
9
10
from django.urls import path
from my_app.views import (
    FriendView
)

urlpatterns = [
    # ... other urls
    path("", FriendView.as_view(), name = "friend_cbv"),
    # ...
]
python

To transform from FBV to CBV, you need to change the reverse URL pattern.

index.js

1
2
3
4
5
6
// other previous stuff
$.ajax({
    type: 'POST',
    url: "{% url 'friend_cbv' %}", // CHANGE the POST url
    // ... continues
// ...
js

Conclusion

AJAX is the best way to perform asynchronous tasks in Django, at least on a small scale. If you want to do an asynchronous task on a bigger scale, you could do socket programming in Django or use front-end JavaScript libraries such as Angular, Vue, or React.

If you have any difficulty following this guide, you can refer to this Github Repository or the other resources below.

2