April 13th, 2012

Making Django Templates work for you

Tutorial for: Django

There are many complains about how limited the Django Template engine is, I thought this originally. After using the engine more and learning about it's powerful tag creation system, now I see otherwise. I now view template engines which embed an actual scripting language into the template as a bad programming practice and embrace how templates are built in Django. Learn why in this tutorial.

As you may know, all the actual content on this website, including this here tutorial is build using a Django template. The template for this tutorial is stored in a Database table and rendered out when it is displayed. Of course I cache the result to limit the amount of times the database is queried. I will talk about caching in templates in my next tutorial, but this one will focus on how to custom the template engine for your particular requirements.

Depending on what type of web application you are creating, you may use templates to embed snippets of HTML from another file with a special context, use a tag to grab some extra data about an object from the database for the user, or perhaps use it to render special forms in specific places on your website. Whatever your web application may be, it will more than likely make your task a lot easier if you created tags for most used components on the site. Here is a very simple mock template of a base page, what this blogs base page is, without all the extra formatting. I separated each component of the site into different files to more easily edit them when I need to. Since they are static data, which rarely ever changes, it's safe to put them into a separate HTML file.

<html>
  <head>
    <title>Python Diary | {% block title %}{{title|default:"Untitled page"}}{% endblock %}</title>
  </head>
  <body>
    {% include "site-nav.html" %}
    ....
    {% block body %}
    {% endblock %}
    ....
    {% include "aboutme.html" %}
    {% include "tags.html" %}
    {% include "archive.html" %}
  </body>
</html>

Pretty straightforward? You will notice the title section is able to take a title from either a subclassed block, or a context variable. I know sub-templates are not subclassed templates, but that's how I like to picture them, as in essence that's really how an extended template works. Here is a very simple entry template:

{% extends "base.html" %}

{% block body %}
....
{% endblock %}

A better method of building your base template, would be to add blocks in the head section, so that you can easily add new CSS or JavaScript blocks. This is extremely helpful for dynamic websites which use a lot of complex styling or jQuery functions.

Now lets talk about template tags, and how to build a custom one with a useful purpose. The first thing you should ask yourself, can I add a new method to my model to do the same function? I say because, adding a new method to a model is much more transparent and object-oriented. Here is a very simple template which uses the most widely used model function in Django:

<a href="{{article.get_absolute_url}}">{{article}}</a>

Remember, that all get_absolute_url is, is a model class method. Interesting enough, all of a model classes methods can be accessed from a Django template. It's main purpose is to access the fields of the model, rather than the methods. Accessing the methods is a most welcomed side-effect, that proves very useful when building applications. Lets create an Article model, and I will show you what else can be performed using it's methods in templates.

class Article(models.Model):
  title = models.CharField(max_length=80)
  slug = models.SlugField()
  content = models.TextField()
  published_on = models.DateTimeField()
  author = models.ForeignKey(User)
  categories = models.ManyToManyField(Category)
  @models.permalink
  def get_absolute_url(self):
    return ('view-article', [self.slug])
  @models.permalink
  def get_author_url(self):
    return ('view-author', [self.user.username])

This is a simple example which only generates URLs for the template or any other part of your application to use. A method can also do calculations, or return a True/False value which can be used in the template systems if block. An example of a calculation would be for a billing system, where it can tally up the price of all services on an invoice and add taxes. This data doesn't need to be stored in the database, as it can be calculated at runtime when needed. Here is a simple Invoice model:

class Invoice(models.Model):
  customer = models.ForeignKey(User)
  services = models.ManyToManyField(Service)
  def total(self):
    result = Decimal(0.00)
    for service in self.services.all():
      result += service.cost
    return result * 1.14

A template can easily place in invoice.total, even though it is not a field in the database, but rather a method on the model. You can add most of the model's Business logic directly in the model itself. This method can also be used anywhere in the application, for when you actually send the total to your credit card processor for payment. This example is very simple, and more than likely needs more fields in a real world scenario to actual work.

Okay, now that we got that out of the way. Let me explain how to add new template tags, which can easily extend what the Django template engine can do. Something you may wish to have in a template tag, is an easy way to render a form which is used in various sections of the site, but not everywhere. The Django comments framework uses a similar template tag to render the comments form.

from django import template
from forms import SimpleForm

register = template.Library()

register.inclusion_tag("simple_form.html")
def simpleform():
  return {'form':SimpleForm()}

This will essentially render a form where ever you place the tag simpleform in your template. Follow the Django documentation for how to create the template library and how to load it into a template.

This next example will take something I do on this blog a lot, create links to packages from inside my templates. You should notice links like: Django, all over this website. Here is how it's done:

from django import template
from pkglist.models import Package

register = template.Library()

@register.simple_tag
def package(slug):
  pkg = Package.objects.get(slug=slug)
  return '<a href="%s">%s</a><a href="%s" rel="tooltip" title="Direct link"><i class="icon-bookmark"></i></a>' % (pkg.get_absolute_url(),pkg,pkg.website)

I use caching on the actual template to make sure each lookup in the database is kept to a minimum. Also, there is no checking done here to see if the package exists, I left this out for simplicity. Here is a template tag which I used in a few of my tutorials, which may help others out there who are building Django sites with YouTube functionality:

@register.simple_tag
def youtube(slug):
  return '<iframe width="420" height="315" src="http://www.youtube.com/embed/%s" frameborder="0" allowfullscreen></iframe>' % slug

This can also be done using an inclusion tag. I will not dive into complex templates in this tutorial, ones which do not use the shortcuts. This YouTube example is a great example on how powerful the template system can be, if you just think of common tasks you normally perform, but want to simplify it's inclusion. One could also make this YouTube tag fetch meta data from a database to be displayed as well.

This ends the template tutorial for now. My next tutorial in this set will focus on urls.py and templates, and how to make every component in Django work together well to ease the development process. You really should never hardcode URLs into your templates, and my next tutorial will go through all the ways of properly adding URLs in templates for various purposes.

About Me

My Photo
Names Kevin, hugely into UNIX technologies, not just Linux. I've dabbled with the demons, played with the Sun, and now with the Penguins.




Kevin Veroneau Consulting Services
Do you require the services of a Django contractor? Do you need both a website and hosting services? Perhaps I can help.

If you like what you read, please consider donating to help with hosting costs, and to fund future books to review.

Python Powered | © 2012-2013 Kevin Veroneau