Using PJAX with Tornado

October 9, 2014

I love working with Tornado. Unlike Django its lightweight, supports async operations and is pretty straightforward to start off with. Tornado’s templating system is pretty nice too, and lot less verbose than Django’s. However, things start getting messy when you have to deal with conditional template inheritance.

Why would you want that? A prime example is when you use PJAX. When you PJAXify a link, you typically would want to render only the content of that page, and not the entire layout. In other words, the template inheritance check should be conditional:

{% if not is_pjax %}
  {% extend "layout.html" %}
{% end %}

In an ideal world, the above code would just work. Unfortunately, Tornado does not support conditional template inheritance. So we will need to figure out a way to work around that limitation. Thankfully, Tornado is very modular and allows us to plug in our custom rendering code.

Firstly, we need to identify if the request is a PJAX/AJAX request

import tornado.web
from tornado.web import RequestHandler

AJAX_HEADERS = ('X-PJAX','X-Requested-With',)

class BaseHandler(tornado.web.RequestHandler):

    def initialize(self):
        self.is_ajax = False

    def prepare(self):
        self.is_ajax = any(hdr in self.request.headers for hdr in AJAX_HEADERS)

Next, we define the template that we want to work with, say index.tmpl

{% block content %}
    <h1>Hey this is a PJAX response!</h1>
    <p>This template will extend from the layout when the request is not a PJAX request, else will render only this block!</p>
{% end %}

One thing to note is that we aren’t extending from the layout here. This will be handled in the render function. For the sake of clarity, I prefer to have a separate method pjax_render, so that it is clear that we are dealing with a PJAX request.

BASE_DIR = os.path.dirname(os.path.realpath(__file__))
PJAX_TEMPLATE = '''
{{ % extends "{0}/views/layout.html" % }}
{{ % include "{0}/views/{1}" % }}
'''

def _get_loader(self):
    template_path = self.get_template_path()
    with RequestHandler._template_loader_lock:
        if template_path not in RequestHandler._template_loaders:
            loader = self.create_template_loader(template_path)
            RequestHandler._template_loaders[template_path] = loader
        else:
            loader = RequestHandler._template_loaders[template_path]
    return loader

def render_pjax(self, template_name, **kwargs):
    if not self.is_ajax:
        loader = self._get_loader()
        template = PJAX_TEMPLATE.format(BASE_DIR, template_name)
        namespace = self.get_template_namespace()
        namespace.update(kwargs)
        self.write(tornado.template.Template(
            template, loader=loader).generate(**namespace)
        )
    else:
        self.write(self.render_string(template_name, **kwargs))

The _get_loader method returns the template loader that is being used. The render_pjax method has the same signature as RequestHandler.render and checks if the current request is a PJAX request. If yes, then it simply writes out the template content. If no, then we automatically extend from the layout. That template code is defined in PJAX_TEMPLATE and can be customized as required.

To use this, simply replace the render call in your handler to render_pjax.

class HomeController(BaseHandler):
    def get(self):
        self.render_pjax("index.tmpl")

And that’s it. You can now safely use AJAX/PJAX for any link in your app, thereby improving response time and saving those precious ms for your users.

comments powered by Disqus