AJAX + Django (Part 1)

Asynchronous JavaScript and XML: getting data asynchronously, without page reloads… It’s not a technology but a technique that requires the interaction of several technologies. What technologies? A dash of a decent web framework (Django), a nice JavaScript library (jQuery) and some understanding of web requests and responses. Optionally, you might want to use some kind of Web UI framework such as Twitter Bootstrap (I do), which allows you to focus on what’s really important: system design and development.

So this is how I AJAXified an application…

Requests

First, a little on the basics of how a typical web request works:

  1. Client requests a resource from the server (new URL entered or form submitted),
  2. Server receives the request and builds a response (fetches a document or builds it dynamically),
  3. Server returns a response with the resulting document (which is opened by the browser).

That’s all there is to a synchronous request, and if everything goes well all this happens in a few milliseconds. And asynchronous requests? Almost the same, with a minor difference in the first and last steps: the URL request and form submits are triggered by JavaScript, and document updates (if any) are done by JavaScript as well, without loading a new page.

The Garden of the Forking Paths

So how to do it? First, let’s consider that the only difference is that instead of returning a whole document, we need to return only a portion that will be used to update the document. This document is an HTML page, so we need to replace some portion of HTML by another (updated) portion of HTML. Nothing new here.

Since we’re using Django, we’ll build the response with a view, of course (read controller for other web frameworks). So, the view must return an HTML string that the JavaScript code will use to replace another one, right? Not necessarily: we can return any string, we could even use JavaScript to format it before pasting it on the document.

So that means that the question that bares the core of the issue is where will the HTML code be constructed? But this question can arise at two different level:

  • Formatted vs. Raw: server-side vs. client-side,
  • Python vs. Django: view vs. template.

The first classification is clear enough: will the server return the resulting HTML or will it only return raw data that will be formatted (with JavaScript)? The second one might seem confusing at first sight, since Django is a Python library. To understand the difference you need to think how loyal to the Django way it is: will it be built directly in the view (in pure Python) or will it use the render function to do so in a template?

So these are in fact the three possible paths:

  1. The Python Way
  2. The Django Way
  3. The JavaScript Way

Anyone who has any experience in Django will automatically discard the first, more intuitive option. Building an HTML string (with all the tags we might forget) in a Python function is a horrible idea. In fact, it defeats the very purpose of using a nicely layered framework with a powerful templating language such as Django. Exeunt the Python Way.

Isn’t the third option also uglier? Perhaps… until we consider two things about the data: transmission and versatility. On the first angle, this option can be the best when transmission speed is a major issue, because a JSON-formatted string is more compact than an equal HTML string (emphasis on equal, I’ll explain why on the second part!). On the usefulness side, returning pure JSON data will allow you to use this to build a RESTful API that could be used by any service to fetch that data (mobile apps, websites, email, you-name-it), regardless of the implementation of those services… which is very, very awesome.

The Application

To show the implementation I will use a small accounting application I built to track accounts, transactions and provide useful financial stats and projections. Its model is rather simple, so adapting it to other models should be very easy.

Filtering accounts by type

We’ll focus on one page only: the Accounts page, which consists (mostly) of a table of accounts with their relevant information (code, total credit, total debit, currency, balance, etc.) There are also a set of multi-select filters used to update the list of accounts displayed: one allows to filters by currencies, another by account type, and another one contains several options to show or hide totals and the like. The idea is to apply every filter asynchronously in each click instead of on every submit.

The Django Way

The easiest implementation (and nicer, if you will) is done using Django’s templating to render the response. There are three things to consider: the URLs mapping, the view definitions and the templates. Let’s see each in detail.

URLs

Being very RESTful about it, we need to make sure that URLs only define resources and behaviour is defined by methods, so using class-based views is called for because they make it so much easier. The urlpatterns in the urls.py file includes these lines:

url(r'^accounts/$', AccountsView.as_view(), name='accounts'),
url(r'^accounts/(?P<code>new)/$', AccountView.as_view(), name='create_account'),
url(r'^accounts/(?P<code>[^/.]+)/$', AccountView.as_view(), name='show_account'),
url(r'^accounts/(?P<code>[^/.]+)/edit/$', AccountView.as_view(), {'edit': True}, name='edit_account'),

The first URL maps to the AccountsView class (plural), which accepts GET and POST requests (to show the list or add a new account). The remaining three URLs map to the AccountView class (singular), which accepts GET, PUT and DELETE to get one account (show or edit), update one account or delete one account.

You have probably noticed that the second and third lines could be just one, as whether the code is new or not will be checked in the view anyway, but I like to keep a different name to make the reverse look up in templates easier.

AccountView class

One of the nice things about class-based views is that they have one method for each web request method, so instead of checking the request.method in the view, you simply define the corresponding instance method for each web method: the method named get will be triggered if the request method is GET, post if it’s a POST, and so on—and when not found, a 405 Method Not Allowed response is given automatically.

Class-based views need to inherit from one of the django.views classes. In this case we’ll use the generic view, which provides the basic hooks.

from django.views.generic.base import View

class AccountsView(View):

And on to the method definitions…

GET Requests

  def get(self, request):
    a = Account.objects.all()
    form_f = AccountsFilterForm(request.GET)
    form_o = AccountsOptionsForm(request.GET)
    if form_f.is_valid():
      a = filter_accounts(a, form_f)
    if form_o.is_valid():
      opts = get_opts(form_o)
    c = {'accounts': a, 'filter_form': form_f, 'options_form': form_o, 'options': opts}
    return render(request, get_template('accounts', request.is_ajax()), c)

As you can see the get function uses two forms: AccountsFilterForm that picks up attribute-based filters for the list of accounts, and AccountsOptionsForm that simply determines some display options (which are mostly taken care of in the template). They are very simple, so you needn’t worry about the form definitions.

I am sure you can guess that the function filter_accounts filters its first argument (queryset of Account objects) according to the selected fields in its second argument (a form which consists of checkboxes of account attributes, such currency and type.) The function get_opts builds a list of selected options (booleans) that the template will use to display optional data (balances, subtotals, etc.)

Then it builds the context, including both forms, the list of accounts and the dictionary of options and renders all that on a template. Which template? That decision is made by the get_template function, that takes up to three parameters to select the correct one based on the resource name, the type of request (sync vs. async) and whether the view is for editing objects.

def get_template(resource, partial, edit=False):
  return 'accountant/{}{}{}.html'.format('partial/' if partial else '', resource, '-edit' if edit else '')

POST Requests

def post(self, request):
  na = AccountModelForm(parse_form(request.raw_post_data))
  c = {}
  s, c['alert'] = save_form(na)
  return render(request, get_template('alert', request.is_ajax()), c, status=s)

The post function defines a new account using the post data on the AccountModelForm, and tries to save it, saving the status code and a message with the result of the operation (save_form returns that tuple), then renders all that data on the correct template.

AccountView class

This class takes care of all requests to specific accounts, and accepts get, put and delete methods to act on them.

GET Requests

class AccountView(View):

  def get(self, request, code, edit=False):
    if code == 'new':
      c = {'form': AccountModelForm()}
    else:
      account = get_object_or_404(Account, code__iexact=code)
      c = {'account': account}
      if edit:
        c['form'] = AccountModelForm(instance=account)
    return render(request, get_template('account', request.is_ajax(), edit), c)

The get method checks for three possibilities: the view is to create a new account, in which case it includes an unbound AccountModelForm form; it is to display the data of one account, in which case it picks up the account object; or it’s to edit an existing one, in which it also includes an AccountModelForm, but using the account as instance. Then it just renders the context on the template defined by the get_template function.

POST and PUT Requests

def post(self, request, code):
  return self.put(request, code)

def put(self, request, code):
  account = get_object_or_404(Account, code__iexact=code)
  form = AccountModelForm(parse_form(request.raw_post_data), instance=account)
  c = {}
  s, c['alert'] = save_form(form)
  return render(request, get_template('alert', request.is_ajax()), c, status=s)

Why does the class define a post method? Shouldn’t updates use the PUT method? Yes, but HTML forms cannot use but GET or POST methods (for whatever reason), so if the request is made directly from a webpage (without JavaScript or another service), it cannot be a PUT request. But since it should still behave like a PUT request, it just forwards the request to the put method.

Edit account modal form

The put method picks up the account object, reads the form data (parse_form is used to abstract POST vs. PUT issues away), gets the status code and alert message, and returns the rendered response (with the correct status code). Simple.

DELETE Requests

def delete(self, request, code):
  account = get_object_or_404(Account, code__iexact=code)
  c = {}
  s, c['alert'] = delete_object(account) 
  return render(request, get_template('alert', request.is_ajax()), c, status=s)

Almost identical to the put method, nothing to explain.

Templates

This is where the key really is, I think. Not so much in the whole template idea, but on one specific tag, the include tag. That is the one templating weapon that makes AJAX extremely simple with Django.

Base

First let’s take a look at the base template…

  • <body>
      <div class="navbar">
        # navigation bar stuff
      </div>
      <div class="container">
        {% block header %}{% endblock %}
        {% block alerts %}{% endblock %}
        {% block modals %}{% endblock %}
        {% block content %}{% endblock %}
        <footer>
          # footer stuff
        </footer>
      </div>
      <script src="{{ STATIC_URL }}js/jquery.min.js" type="text/javascript"></script>
      <script src="{{ STATIC_URL }}js/bootstrap.min.js" type="text/javascript"></script>
      {% block scripts %}{% endblock %}
    </body>

So there are five blocks to consider, four inside the main container: header, alerts, modals and content; and one for the custom scripts (loaded after jQuery and Twitter Bootstrap libraries).

Accounts

This is the main template for the Accounts page, but as you’ll see, not the only one…

{% block header %}
  <h3 class="well well-small">Accounts</h3>
{% endblock %}
{% block alerts %}
  <div id='alerts'>
  {% if alert %}
    {% include "accountant/partial/alert.html" %}
  {% endif %}
  </div>
{% endblock %}
{% block modals %}
  <div id="modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
  </div>
{% endblock %}
{% block content %}
  <div id="data">
    {% include "accountant/partial/accounts.html" %}
  </div>
{% endblock %}
{% block scripts %}
  <script type="text/javascript" src="{{ STATIC_URL }}js/csrf.js"></script>
  <script type="text/javascript" src="{{ STATIC_URL }}js/scripts.js"></script>
{% endblock %}

So you see that there are three blocks for which the content is not directly there: alerts and data blocks are included, and the modals portion is missing altogether. This is actually quite logical, because the fact that they are displayed at the same time (on the same page) is irrelevant, they are indeed independent pieces of information.

On to these included and missing templates…

Alert

{% if alert %}
  <div class="alert alert-{{ alert.type }}">
    <button type="button" class="close" data-dismiss="alert">&times;</button>
    <strong>{{ alert.type|title }}:</strong> {{ alert.text }}
  </div>
{% endif %}

Very simple: it only displays a Bootstrap alert message with the correct colour to indicate success or failure, and a dismiss button to remove the alert dynamically. If there is an alert, otherwise it’s empty.

Alerting about saved items

Alerting about saved items

Accounts (Partial)

<table id="accounts" class="table table-striped">
{% if accounts %}
<thead>
  <tr>
    <th colspan="2">Name</th>
    <th>Code</th>
    <th>Credit</th>
    <th>Debit</th>
    {% if options.show_balance %}
      <th>Balance</th>
    {% endif %}
    <th>Curr</th>
  </tr>
</thead>
<tbody>
{% for account in accounts %}
  <tr>
    <td>
      <a title='Details' href="{% url show_account account.code %}"><i class="icon-search"></i></a>
      <a title='Edit' class="modal-trigger" href="{% url edit_account account.code %}"><i class="icon-edit"></i></a>
      <a title='Remove' class="deleter" href="{% url edit_account account.code %}"><i class="icon-remove"></i></a>
    </td>
    <td>{{ account.name }}</td>
    # Rest of the account data here... you get the picture
{% else %}
  <caption>No accounts with those attributes</caption>
{% endif %}
</table>

Nothing overly complex either, just display the table of accounts if there are accounts remaining after applying the filters; otherwise display a caption. But pay attention to the fact that every row includes three links as little buttons (show, delete and edit), which do link to the proper URL. This is key for two reasons: without JavaScript the button will still work (although linking to a full-page form instead of a modal window), and it will also make the JavaScript to handle it more generic.

Modals

The Add/Modify Account modal window is powered by Twitter Bootstrap…

<div class='modal-header'>
  <button type='button' class='close' data-dismiss='modal' aria-hidden='true'>&times;</button>
  <h3>Edit Account</h3>
{% if account %}
  <form id='modal-form' action='{% url show_account account.code %}' method='PUT'>
{% else %}
  <form id='modal-form' action='{% url accounts %}' method='POST'>
{% endif %}
  <div class='modal-body'>
    {% csrf_token %}
    {{ form.as_table }}
  </div>
  <div class='modal-footer'>
    <button type="button" class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
    <button type="submit" class="btn btn-primary">Save</button>
  </div>
</form>

This is not included in the Accounts template from the beginning because even though this modal will be pasted on the Accounts page, it responds to a different resource (URL).It’s the AccountView class that knows how to deal with this, the AccountsView’s get method doesn’t even know about any form, so it wouldn’t render anything on the template if included.

JavaScript

The main JavaScript is not very long (remember that we are using jQuery and Bootstrap), and it starts with the typical handlers attachers

$(document).ready(function(){
  $('#filters input').on('click', updateTable);
  $('#actions input').on('click', updateTable);
  $('body').on('click', '.modal-trigger', displayModal);
  $('body').on('click', '.deleter', deleteObject);
  $('body').on('submit', '#modal-form', saveForm);
 };

updateTable simply selects the correct filtering/updating function to run based on the id of main table on the page, so regardless of the page (accounts, transactions, entries) it always works as expected. In this case (the Accounts page) it simply calls the filterAccounts function passing the accounts table as parameter.

filterAccounts

function filterAccounts(t) {
  form = $('#filters');
  data = $(form).find('input:checked');
  qs = addFiltersData(data, '');
  form = $('#actions');
  data = $(form).find('input:checked');
  qs = addOptionsData(data, qs);
  qs = qs.substring(0, qs.length - 1);
  $(t).load("/accountant/accounts/?" + qs);
};

The function receives a parameter t (in this page the accounts table), it parses the forms data as one single querystring and then updates the table with the result of the asynchronous GET request triggered by jQuery’s load function, which in turn updates the html of the calling element with the response of the request sent to the URL. Yes, I know, this load method is really awesome!

Recapping a bit what goes on behind stage: whenever that function is called it sends a simple GET request to the server with the forms data, which uses to the AccountsView.get function to filter the accounts and return the template rendered as an HTML string, which is then used to update the content of the table with id accounts. All this in a few milliseconds, without refreshing the page.

And what about data modification? There are three sides to that: getting the add/edit form, submitting the new/modified data and deleting some data.

displayModal

function displayModal(e) {
  e.preventDefault();
  url = $(this).attr('href');
  $('#modal').load(url).modal('show');
}

Is that it? But it doesn’t even specify what it should display!

Remember that the edit links have the correct URL already. JavaScript is only preventing the browser from send a synchronous request (e.preventDefault()), sending instead an asynchronous request to the same URL to update the modal div, and then display it as a modal window.

Account add modal form

Account add modal form

saveForm

function saveForm(e) {
  e.preventDefault();
  $.ajax({
    url: $('#modal-form').attr('action'),
    type: $('#modal-form').attr('method'),
    data: $('#modal-form').serialize(),
    success: function(response, status, request) {
      $('#alerts').html(response);
    }
 });
 $('#modal').modal('hide');
 updateTable()
};

The function is quite generic and uses the $.ajax method because of its flexibility. Remember that the forms templates define the resource (action) and method? The function takes advantage of that and simply obeys the form’s directives (so the form works on any page). The response is used to update the content of the alerts div (because all modals do return an alert), and then the modal windows is hidden. Then the table is updated (by doing sending another GET request asynchronously.)

deleteObject

function deleteObject(e) {
  e.preventDefault();
  $.ajax({
    url: $(this).attr('href'),
    type: 'DELETE',
    success: function(response, status, request) {
      $('#alerts').html(response);
    }
  });
  updateTable();
}

Deleting an object is quite similar, with the difference that since no form is used, the method is set manually to DELETE. You should add a confirm alert as well… I’ve no clue why I hadn’t until now.

What’s Next?

Checking requests and responses

Checking requests and responses

In the next part, we’ll see how we can implement The JavaScript Way, and why would we want to…

Advertisements

6 thoughts on “AJAX + Django (Part 1)

    1. coutinhoelias

      Claudio good night.

      My name is Elias, I am Brazilian and I found very interesting your post, inclisive I’m following you.

      I am a beginner with development and really could not put together the project here on my computer.

      Would have a repository so I can study it?

      I am very grateful to answer me.

      Reply
  1. coutinhoelias

    Claudio good night.

    My name is Elias, I am Brazilian and I found very interesting your post, inclisive I’m following you.

    I am a beginner with development and really could not put together the project here on my computer.

    Would have a repository so I can study it?

    I am very grateful to answer me.

    Reply

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s