Advanced Templates

Although most of your interactions with Django’s template language will be in the role of template author, you may want to customize and extend the template engine – either to make it do something it doesn’t already do, or to make your job easier in some other way.

This chapter delves deep into the guts of Django’s template system. It covers what you need to know if you plan to extend the system or if you’re just curious about how it works. It also covers the auto-escaping feature, a security measure you’ll no doubt notice over time as you continue to use Django.

Template Language Review

First, let’s quickly review a number of terms introduced in Chapter 3:

  • A template is a text document, or a normal Python string, that is marked up using the Django template language. A template can contain template tags and variables.
  • A template tag is a symbol within a template that does something. This definition is deliberately vague. For example, a template tag can produce content, serve as a control structure (an if statement or for loop), grab content from a database, or enable access to other template tags.Template tags are surrounded by {% and %}:
      {% if is_logged_in %}           
      Thanks for logging in!
      {% else %}           
      Please log in.
      {% endif %}
    
  • A variable is a symbol within a template that outputs a value.Variable tags are surrounded by {{ and }}:
      My first name is {{ first_name }}. My last name is {{ last_name }}.
    
  • A context is a name->value mapping (similar to a Python dictionary) that is passed to a template.
  • A template renders a context by replacing the variable “holes” with values from the context and executing all template tags.

For more details about the basics of these terms, refer back to Chapter 3. The rest of this chapter discusses ways of extending the template engine. First, though, let’s take a quick look at a few internals left out of Chapter 3 for simplicity.

RequestContext and Context Processors

When rendering a template, you need a context. This can be an instance of django.template.Context, but Django also comes with a subclass, django.template.RequestContext, that acts slightly differently.

RequestContext adds a bunch of variables to your template context by default – things like the HttpRequest object or information about the currently logged-in user.

The render() shortcut creates a RequestContext unless it’s passed a different context instance explicitly. For example, consider these two views:

from django.template import loader, Context

def view_1(request):
    # ...
    t = loader.get_template('template1.html')
    c = Context({
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR'],
        'message': 'I am view 1.'
    })
    return t.render(c)

def view_2(request):
    # ...
    t = loader.get_template('template2.html')
    c = Context({
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR'],
        'message': 'I am the second view.'
    })
    return t.render(c)

(Note that I’m deliberately not using the render() shortcut in these examples – we’re manually loading the templates, constructing the context objects and rendering the templates. I’m “spelling out” all of the steps for the purpose of clarity.)

Each view passes the same three variables – app, user and ip_address – to its template. Wouldn’t it be nice if we could remove that redundancy? RequestContext and context processors were created to solve this problem. Context processors let you specify a number of variables that get set in each context automatically – without you having to specify the variables in each render() call.

The catch is that you have to use RequestContext instead of Context when you render a template. The most low-level way of using context processors is to create some processors and pass them to RequestContext. Here’s how the above example could be written with context processors:

from django.template import loader, RequestContext

def custom_proc(request):
    # A context processor that provides 'app', 'user' and 'ip_address'.
    return {
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR']
    }

def view_1(request):
    # ...
    t = loader.get_template('template1.html')
    c = RequestContext(request, 
                       {'message': 'I am view 1.'},  
                       processors=[custom_proc])
    return t.render(c)

def view_2(request):
    # ...
    t = loader.get_template('template2.html')
    c = RequestContext(request, 
                       {'message': 'I am the second view.'},  
                       processors=[custom_proc])
    return t.render(c)

Let’s step through this code:

  • First, we define a function custom_proc. This is a context processor – it takes an HttpRequest object and returns a dictionary of variables to use in the template context. That’s all it does.
  • We’ve changed the two view functions to use RequestContext instead of Context. There are two differences in how the context is constructed. One, RequestContext requires the first argument to be an HttpRequest object – the one that was passed into the view function in the first place (request). Two, RequestContext takes an optional processors argument, which is a list or tuple of context processor functions to use. Here, we pass in custom_proc, the custom processor we defined above.
  • Each view no longer has to include app, user or ip_address in its context construction, because those are provided by custom_proc.
  • Each view still has the flexibility to introduce any custom template variables it might need. In this example, the message template variable is set differently in each view.
    In Chapter 3, I introduced the render() shortcut, which saves you from having to call loader.get_template(), then create a Context, then call the render() method on the template.

In order to demonstrate the lower-level workings of context processors, the above examples didn’t use render(). But it’s possible – and preferable – to use context processors with render(). Do this with the context_instance argument, like so:

from django.shortcuts import render
from django.template import RequestContext

def custom_proc(request):
    # A context processor that provides 'app', 'user' and 'ip_address'.
    return {
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR']
    }

def view_1(request):
    # ...
    return render(request, 'template1.html',
                  {'message': 'I am view 1.'},
                  context_instance=RequestContext(
                  request, processors=[custom_proc]
                  )
    )

def view_2(request):
    # ...
    return render(request, 'template2.html', 
                  {'message': 'I am the second view.'},
                  context_instance=RequestContext(
                  request, processors=[custom_proc]
                  )
)

Here, we’ve trimmed down each view’s template rendering code to a single (wrapped) line. This is an improvement, but, evaluating the conciseness of this code, we have to admit we’re now almost overdosing on the other end of the spectrum. We’ve removed redundancy in data (our template variables) at the cost of adding redundancy in code (in the processors call).

Using context processors doesn’t save you much typing if you have to type processors all the time. For that reason, Django provides support for global context processors. The context_processors setting (in your settings.py) designates which context processors should always be applied to RequestContext. This removes the need to specify processors each time you use RequestContext.

By default, context_processors is set to the following:

'context_processors': [
            'django.template.context_processors.debug',
            'django.template.context_processors.request',
            'django.contrib.auth.context_processors.auth',
            'django.contrib.messages.context_processors.messages',
        ],

This setting is a list of callables that use the same interface as our custom_proc function above – functions that take a request object as their argument and return a dictionary of items to be merged into the context. Note that the values in context_processors are specified as strings, which means the processors are required to be somewhere on your Python path (so you can refer to them from the setting).

Each processor is applied in order. That is, if one processor adds a variable to the context and a second processor adds a variable with the same name, the second will override the first. Django provides a number of simple context processors, including the ones that are enabled by default:

auth

django.contrib.auth.context_processors.auth

If this processor is enabled, every RequestContext will contain these variables:

  • user – An auth.User instance representing the currently logged-in user (or an AnonymousUser instance, if the client isn’t logged in).
  • perms – An instance of django.contrib.auth.context_processors.PermWrapper, representing the permissions that the currently logged-in user has.

debug

django.template.context_processors.debug

If this processor is enabled, every RequestContext will contain these two variables – but only if your DEBUG setting is set to True and the request’s IP address (request.META['REMOTE_ADDR']) is in the INTERNAL_IPS setting:

  • debugTrue.
    You can use this in templates to test whether you’re in DEBUG mode.
  • sql_queries – A list of {'sql': ..., 'time': ...} dictionaries, representing every SQL query that has happened so far during the request and how long it took. The list is in order by query and lazily generated on access.

i18n

django.template.context_processors.i18n

If this processor is enabled, every RequestContext will contain these two variables:

  • LANGUAGES – The value of the LANGUAGES setting.
  • LANGUAGE_CODErequest.LANGUAGE_CODE, if it exists. Otherwise, the value of the LANGUAGE_CODE setting.

media

django.template.context_processors.media

If this processor is enabled, every RequestContext will contain a variable MEDIA_URL, providing the value of the MEDIA_URL setting.

static

django.template.context_processors.static

If this processor is enabled, every RequestContext will contain a variable STATIC_URL, providing the value of the STATIC_URL setting.

csrf

django.template.context_processors.csrf

This processor adds a token that is needed by the csrf_token template tag for protection against cross site request forgeries (see Chapter 19).

request

django.template.context_processors.request

If this processor is enabled, every RequestContext will contain a variable request, which is the current HttpRequest.

messages

django.contrib.messages.context_processors.messages

If this processor is enabled, every RequestContext will contain these two variables:

  • messages – A list of messages (as strings) that have been set via the messages framework.
  • DEFAULT_MESSAGE_LEVELS – A mapping of the message level names to their numeric value.