Silently failing include tag in Django

December 7, 2009 Tags: django

How do you handle third-party services integration with your websites? If you want to register it with Google's webmaster tools or add an external visit tracking tool, you'll always have to edit a piece of code on the production server. Here's my solution to make it less painful with Django.

So, you need to integrate a service with your website. That could be your web analytics tool, Typekit, Google's or Yahoo's webmaster tools, whatever. The thing is, you have to edit a small amount of HTML on the production server.

Generally you never do some coding on the production server. You fix things on your development machine and push the changes to the server. But sometimes you don't have the same setup on your local machine than on your server. That's why the local_settings.py trick is so useful: you keep it out of source control, and you never have any conflict between your different setups. And you try to import the local settings, silently failing if the module doesn't exist.

It's exactly the same case here: you just want to set up something on the server but not on your local machine. A simple solution would be to do {% include 'head.html %} and keep head.html out of source control, but:

  • You don't need it on the development machine
  • You'll get error pages if you have TEMPLATE_DEBUG=True, which is almost always the case during development
  • You might forget to add the (even empty) template on the production server and therefore get a few error emails at each visit

So, what about extending the local_settings thing to templates? You just need a template tag that would include another template and silently fail if it doesn't exist, whether you're on debug or production mode. Here you go:

from django import template

register = template.Library()

class IncludeNode(template.Node):
    def __init__(self, template_name):
        self.template_name = template_name

    def render(self, context):
        try:
            # Loading the template and rendering it
            included_template = template.loader.get_template(
                    self.template_name).render(context)
        except template.TemplateDoesNotExist:
            # Let's return nothing
            included_template = ''
        return included_template

@register.tag
def try_to_include(parser, token):
    """Usage: {% try_to_include "head.html" %}

    This will fail silently if the template doesn't exist. If it does, it will
    be rendered with the current context."""
    try:
        tag_name, template_name = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError, \
            "%r tag requires a single argument" % token.contents.split()[0]

    return IncludeNode(template_name[1:-1])

Then, in your templates (assuming you've called your module include_tags):

{% load include_tags %}
<head>
    ...
    {% try_to_include "head.html" %}
</head>

If the third-party service needs an extra <meta> tag, this would be the way to go. I have two of these inclusions on each page: one in the <head> section, and one at the end of the <body> section.

That's one of the rare cases when silent errors are convenient. I wonder if there are other (and better) approaches though.

Comments

June 10, 2010Philgo20

Thanks for the time saver, will try that for sure.

June 21, 2010Daniel

Great job :)

Thanks

Add a comment

Comments are closed for this entry.

Short URL

http://bruno.im/e2