I'm Ross. This is a post on my blog entitled Using Django's TemplateTags.

I've had a number of e-mails about how I include the listing of blog tags and archives by month on the side of my website from people who have obviously built up a blog and now want further integration with their website.

Well, it's ultra-simple thanks to a nifty Django feature called template tags. The concept behind template tags is simple - a quick snippet of code in your page templates calls some python code behind the scenes, which does some stuff, and returns either raw HTML code (yuck) or sets new variables in your template context, allowing you to manipulate and display them as you please (cool!)

To get started, in the application directory for your blog, create a directory named templatetags and place an empty file in it, named _init.py_. Now, create a file - in this case we'll call it blogmonths.py_. We need to do a few things in this file:

  1. Import the relevant models so we can access the data
  2. Create and register new template tag
  3. Write the function(s) for that tag so they add data to the template's context.

The contents of this file need to be:

from yourproject.blog.models import Tag,Post
from django.template import Library,Node

register = Library()

def build_month_list(parser, token):
    """
    {% get_month_list %}
    """
    return MonthMenuObject()

class MonthMenuObject(Node):
    def render(self, context):
        context['blog_months'] = Post.objects.dates("date", "month")
        return ''

register.tag('get_month_list', build_month_list)

The important bits of this code are the registration of the tag, and the MonthMenuObject updating context[] and then returning nothing at all.

The context that is set is another Django shortcut - it'll return a date object for each unique month that has a post in it, based on the 'date' column in our Post model. Neat.

Next, create a new file named, for example, blogtags.py_ and paste the following into it:

from yourproject.blog.models import Tag,Post
from django.template import Library,Node

register = Library()

def build_tag_list(parser, token):
    """
    {% get_tag_list %}
    """
    return TagMenuObject()

class TagMenuObject(Node):
    def render(self, context):
        output = ['']

        for blogtag in Tag.objects.all():
            number = blogtag.post_set.count()
            if number >= 1:
                output.append(blogtag)

        context['blog_tags'] = output
        return ''

register.tag('get_tag_list', build_tag_list)

As you can see, this is very similar to the months list - except it checks to see if each tag has any posts, and if it does it adds that tag to a list. That list is then set in the template context for later use.

And that's the hard bit!

Back in your base.html template, choose where you want your list of months to be, and drop in the following code:

<ul>{% load blog_months %}{% get_month_list %}
{% for month in blog_months %}<li><a href="/blog/{{ month|date:"Y/M"|lower }}/" title="{{ month|date:"M Y" }}">{{ month|date:"M Y" }}</a></li>
{% endfor %}</ul>

That'll load up your .py file, execute the tag registered as getmonth_list_ (which, according to the code above, will set a context variable called blog_months), then run through each month in the list and add a link to your page in an unordered list. We use the standard django template filter named date to format the date for valid use in the links and in the anchor text.

Next, choose where you want your tag list and add this (strikingly similar) block of code to the template:

{% load blog_tags %}{% get_tag_list %}
>ul<
{% for tag in blog_tags %}{% if tag.slug %}>a class="link" href="/tag/{{ tag.slug }}/" title="{{ tag.description|escape }}"<{{ tag.title }}>/a<>/li<
{% endif %}{% endfor %}>/ul<

Just like the month block, that runs your new template tag then turns the Python list into a nicely formatted list of tags.

And that's it - you've now got your blog integrated into your website in areas where the blog isn't actually being loaded via generic views - those links are available anywhere in your site!


That's All For That Post.

Last drinks. The end of the road. The last page. End credits.

Thanks for reading.

I'd love to hear your feedback and comments. E-mail me, ross@rossp.org, or get in touch on Twitter where I'm @RossPoulton. Chat soon!