Add AJAX interactivity to Django, without writing Javascript!

Picture of me

Written by Peter Curet

Published on March 09th, 2017

Over the past few years we have witnessed a big bang of Javascript frameworks such as React and Angular. These are great for web applications but do add complexity to your Django project.

Personally I’m a big fan of adding dynamic AJAX interactions in places where they really matter and add value to your MVP,  without slowing down development. I'll show you how by using Intercooler and declarative HTML attributes.

What we’ll be making

In this article I will show you how to progressively enhance your application with AJAX using only a few lines of code. Without writing any Javascript!

As an example, I’ll be adding autocomplete search functionality to a basic search form:

This is an interactive example. Try it out!

The strength of this method is that it leverages Django’s server-side template rendering and will gracefully fall back if Javascript is disabled. In contrast to most Javascript frameworks, which require you to rewrite the templating and port them to Javascript.

Getting the dependencies ready

We'll be making use of the Intercooler library, which depends on jQuery or the super lightweight Zepto.

If you are using Bower, the package name is intercooler-js. If you want to load it off a CDN, add these lines to your HTML:

<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://intercoolerreleases-leaddynocom.netdna-ssl.com/intercooler-1.1.1.min.js"></script>

A basic search form

Normally your Django form and it’s view will look like this:

<form id="form" role="form" action="{% url 'search-submit' %}" method="post">
	{% csrf_token %}
	<input name="search" id="search" type="search" placeholder="Search"/>
	<button type="submit">Submit</button>
</form>
from django.views.generic.base import View
from django.http import HttpResponse
from django.template import loader

class SearchSubmitView(View):
    template = 'search_submit.html'
    response_message = 'This is the response'

    def post(self, request):
        template = loader.get_template(self.template)
        query = request.POST.get('search', '')

        # A simple query for Item objects whose title contain 'query'
        items = Item.objects.filter(title__icontains=query)

        context = {'title': self.response_message, 'query': query, 'items': items}

        rendered_template = template.render(context, request)
        return HttpResponse(rendered_template, content_type='text/html')

SearchSubmitView does a simple query to look for Item objects which contain the search term, and renders the results in search_submit.html and it’s search_results.html sub-template:

<!-- This is the search_submit.html template -->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Search Results</title>
	</head>
	<body>
		{% if query %}
			{% include 'search_results.html' %}
		{% else %}
			<h1 class="h3">Nothing found. <br>Try entering an actual search term.</h1>
		{% endif %}
	</body>
</html>
<!-- This is the search_results.html sub-template -->
{% if query %}
	<h3>You searched for: <i>{{ query }}</i></h3>
	<p>{{ title }}</p>
	{% if items %}
		<ul>
		{% for item in items %}
			<li>{{ item.title }}</li>
		{% endfor %}
		</ul>
	{% endif %}
{% endif %}

Adding dynamic behaviour to your form

Let’s start by updating our form with the Intercooler declarative attributes. In the following snippet, I updated the input element with additional attributes:

  • ic-post-to contains the url we'll be sending AJAX queries to, more on this later
  • ic-trigger-on is the event that triggers the query
  • ic-trigger-delay adds a slight delay between requests
  • ic-target is the ID of the element in which Intercooler injects the search results

Notice how the form still contains the original post functionality, and that a span with a 'search-result-container' ID has been added:

<form id="form" role="form" action="{% url 'search-submit' %}" method="post">
	{% csrf_token %}
	<input name="search"
	       id="search"
	       type="search"
	       placeholder="Start typing here"
	       ic-post-to="{% url 'search-ajax-submit' %}"
	       ic-trigger-on="keyup changed"
	       ic-trigger-delay="300ms"
	       ic-target="#search-result-container"/>
	<button type="submit">Search</button>
</form>
<span id="search-result-container"></span>

Now we add another view, which inherits from the original SearchSubmitView. The subtle difference here is that it overrides the original template with the sub-template 'search_results.html', which only renders the result and not the entire result page.

Remember to add url-routing for this SearchAjaxSubmitView in your urls.py.In this case I'm using the'search-ajax-submit' name (see the ic-post-to attribute).

class SearchAjaxSubmitView(SearchSubmitView):
    template = 'search_results.html'
    response_message = 'This is the AJAX response'

That’s it!

Your search results will now be displayed dynamically as you type! The beauty of this method is that it requires minimal rewriting of your existing code by using object inheritance and re-use of existing templating.

This is just the tip of the iceberg of Intercooler's functionality, so make sure to check out the documentation.

Thanks for reading! Feedback is much appreciated, so don’t hesitate to contact me.

Get the latest updates

Stay tuned, I’ll be sharing exciting stuff soon!