Effectively re-using AJAX views in Django

The goal

Have you ever felt that “browser-within-a-browser” moment when surfing in all those cool web applications like Twitter or Google+? The initial page loads a frame with the basic navigation shortcuts and while navigating, the whole page doesn’t refresh like the web pages of old times, but the main area simply loads it’s new contents. Finally, after years of waiting, they feel like web applications and not simple documents that link to each other. After getting used to this kind of interaction, it’s kind of hard to go back to normal web-browsing.

In this tutorial, we will see how to easily replicate this kind of behaviour in Django.  Our use-case is the following: suppose we have a blog (how original) and we want to list our posts, but have dynamic filters for it. Specifically, we will present a list of the 5 most-used tags as links. Clicking on one of those links will toggle it’s state (selected/not-selected). Whenever a tag’s state is changed, all posts that match the current selection (boolean AND of all selected tags) will be fetched via AJAX. A ‘clear selection’ button will de-select all tags. We will not use pagination but rather limit the results to the 10 most recent posts that match the current tag selection. So, let’s begin:

The UI:

I know, you usually leave the UI for last, but one of the most amazing things when developing in Django is that you can have your designer work in parallel creating templates with dummy placeholder data and later use them to generate the final page.

So, we ask our extremely talented web-designer to write the following template:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Blog list</title>
</head>
<body>
  <ul>
    <li id="clear_tags">clear all</li>
    <li class="tag">sports</li>
    <li class="tag selected">fashion</li>
    <li class="tag">economy</li>
    <li class="tag">baseball</li>
    <li class="tag selected">asteroids</li>
  </ul>
  <div id="blog_list">
      <div class="blog">
          <h1>The moon is shiny</h1>
          <p>I love how the moon is shiny! It's so white and round...</p>
          <p class="blog_tags"><strong>Tags: </strong>sports, economy</p>
      </div>
      <div class="blog">
          <h1>The moon is shiny</h1>
          <p>I love how the moon is shiny! It's so white and round...</p>
          <p class="blog_tags"><strong>Tags: </strong>sports, economy</p>
      </div>
      <div class="blog">
          <h1>The moon is shiny</h1>
          <p>I love how the moon is shiny! It's so white and round...</p>
          <p class="blog_tags"><strong>Tags: </strong>sports, economy</p>
      </div>
  </div>
</body>
</html>

After some spectacular CSS, the end result appears here:

(I know, it looks lousy, but I didn’t actually use a web-designer for this tutorial and I’m no web-designer)

So far so good

The model

No big surprizes here, just some basic models for blog posts and tags and some utility methods:

import re
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Count

class TagManager(models.Manager):
    def most_popular(self, count=5):
        return self.annotate(num_posts=Count("post")).
                    order_by("-num_posts")[:count]

tag_pattern = re.compile(r'^w+$')
def validate_tag_name(name):
    if not tag_pattern.search(name):
        raise ValidationError("not a valid tag name")

class Tag(models.Model):
    name = models.CharField(max_length=20, validators=[validate_tag_name])

    objects = TagManager()

    def __unicode__(self):
        return self.name

class PostManager(models.Manager):
    def filter_by_tags(self, tag_list=[], count=10):
        qs = self.all()
        for tag in tag_list:
            qs = qs.filter(tags__name=tag)
        if count:
            qs = qs[:count]
        return qs

class Post(models.Model):
    title = models.CharField(max_length=80)
    body = models.TextField()
    tags = models.ManyToManyField(Tag, null=True, blank=true)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = PostManager()

    class Meta:
        ordering = ('-created_at', )

    def __unicode__(self):
        return self.title

    def summary(self):
        if len(self.body) < 300:
            return self.body
        else:
            return "%s..." % self.body[:297]

    def tag_list(self):
        qs = self.tags.values_list("name", flat=True)
        if qs:
            return ", ".join(qs)
        else:
            return ""

The basic view

Just to get it out of the way, here’s the ‘urls.py’ module we will be using:

from django.conf.urls.defaults import *
from views import posts, posts_ajax

urlpatterns = patterns('',
    url(regex   =   r'^posts/$',
        view    =   posts,
        name    =   "posts"),

    url(regex   =   r'^ajax/posts/$',
        view    =   posts_ajax,
        name    =   "posts_ajax"),
)

We’ll start off simple. No AJAX, no filtering. Just a view that renders the 5 most popular tags and 10 most recent blog posts:

from django.shortcuts import render_to_response
from django.template.context import RequestContext
from models import Tag, Post

def posts(request):
    tags = Tag.objects.most_popular()
    posts = Post.objects.filter_by_tags()
    return render_to_response("posts.html",
                              {'tags': tags, 'posts': posts},
                              context_instance=RequestContext(request)

Placing all context variables in-place in the templated provided by our web-designer (and having populated the database via the admin panel) yields this result:

The AJAX view

Supposing the AJAX request has a GET variable that contains the tags to filter with, comma-separated, the view that renders the blog list should go as follows:

def posts_ajax(request):
    tag_string = request.GET.get("tags", None)
    if tag_string:
        tag_list = tag_string.split(",")
    else:
        tag_list = []
    posts = Post.objects.filter_by_tags(tag_list)
    return render_to_response("posts_ajax.html",
                              {'posts': posts},
                              context_instance=RequestContext(request)

The ‘posts_ajax.html’ template is simply a chunk from ‘posts.html’ that contains the blog-list. A render of the blog list follows (looks a bit more lousy because no CSS is applied to it):

The Javascript part

I know that JS coding can get ugly, but I’ll do my best to keep everything nice, structured and cute. We will, of course, be useing jQuery:

$(document).ready(function() {
  // current state variables
  var selected_tags = [];

  // various jQuery selectors
  var tag_selector = $('.tag');
  var blog_list_selector = $('#blog_list');

  var update_blog_list = function() {
    // perform the AJAX request, supply the 'selected_tags' array as a GET
    // variable and use the resulting HTML snippet to replace the 'blog list'
    // area with
    $.get(
      '/ajax/posts/',
      {tags: selected_tags.join(',')},
      function(data) {
        blog_list_selector.html(data);
      }
    );
  };

  tag_selector.click(function(event) {
    event.preventDefault();

    // initialize some local variables
    var jq = $(this);
    var tag_name = jq.html();

    // handle the visual part
    jq.toggleClass("selected");

    // update the 'selected_tags' variable

    // if tag_name in selected_tags:
    var index = selected_tags.indexOf(tag_name);
    if(index != -1) {
      // think of `del selected_tags[index]`
      selected_tags.splice(index, index);
    } else {
      // think of `selected_tags.append(tag_name)`
      selected_tags[selected_tags.length] = tag_name;
    }

    update_blog_list();
  });

  $('#clear_tags').click(function(event) {
    event.preventDefault();

    // handle the visual part
    tag_selector.removeClass('selected');

    // update current state
    selected_tags = [];

    update_blog_list();
  });
})

We just import the jQuery library and this js file in the ‘head’ section of our template.

The final touch

So far, the page does all it’s asked of. We will just add a finishing touch to the whole thing to make a big chunk of our code reusable. Follow along the following sentence. We’re going to call the AJAX view within the regular view to create an HTML snippet to include as a context variable in the regular template in the same place where the AJAX call will later replace other HTML snippets from the AJAX view. Confused? Don’t worry, just read-along the following code snippets.

First we’ll modify our AJAX view like this:

def posts_ajax(request, from_python=False):
    if from_python:
        tag_list = []
    else:
        tag_string = request.GET.get("tags", None)
        if tag_string:
            tag_list = tag_string.split(",")
        else:
            tag_list = []
    posts = Post.objects.filter_by_tags(tag_list)
    return render_to_response("posts_ajax.html",
                              {'posts': posts},
                              context_instance=RequestContext(request))

The ‘from_python’ argument specifies whether we called this view from another view, as a python function. If we call it from python, the latest 10 blog posts will be chosen, no filtering. If Django’s URL-resolver calls this view however, it won’t supply the ‘from_python’ argument so the default method of using the GET variable will be used.

In any case, this function returns an HttpResponse object. If Django’s URL-resolver gets this returned value, it will use it to provide the response to the brower(’s AJAX request). If we call this from python however, we can access the ‘content’ attribute of this object to get the HTML snippet we want. Using this knowlege, we can modify our regular view like this:

def posts(request):
    tags = Tag.objects.most_popular()
    posts_snippet = posts_ajax(request, from_python=True).content
    return render_to_response("posts.html",
                              {'tags': tags, 'posts_snippet': posts_snippet},
                              context_instance=RequestContext(request))

Now we need to make a final replacement. In the regular template, the boring for-loop that renders the posts, which is used in the AJAX template anyway, can be simply replaced with the following statement:

<div id="blog_list">
  {{ posts_snippet|safe }}
</div>

That’s it folks! if you want to review the whole source for this tutorial, head over to https://bitbucket.org/kbairak/dynamic-tag-filters. Happy coding.

What are you waiting for? Sign up for your 30-day free trial now.

TRY IT FOR FREE
REQUEST DEMO

Request a Demo

Tell us a bit about yourself and we’ll be in touch soon!