Add a Context Processor for your Django app using Sites

Published on 2009-01-05 19:29:00+00:00
Programming   Python   django  

I've recently refactored a significant number of my Django Apps so that they include the "sites" framework. Essentially, this allows me to use the same code (and database) for multiple sites.

For Example, if I was was building a CMS (and I am!), I might have a model that defines a "page":

from django.db import models
from django.contrib.auth.models import User
from django.contrib.sites.models import Site

class Page(models.Model):
    title = models.CharField('title', max_length=255)
    content = models.TextField()
    author = models.ForeignKey(User)
    sites = models.ManyToManyField(Site,
                  help_text="This page will be displayed on the selected Sites")

Note that the Page class has a ManyToManyField relation ship to a Django Site, which allows a page to be associated with one or more Sites. The neat thing about this, is that when I publish content on a Page, it can be published to one or more sites!

While working with this, I discovered I often wanted to write template code that knew which which Site it was associated. For example, I might have a base template containing a block inside html head elements so templates that inherit it can include external javascript or CSS files. For example, base.html might include the following:

<html>
<head>
<title>
    {% block title %}{% endblock %}
</title>
    {% block head %} {% endblock %}
</head>

Then, in any template that inherits from base.html, I could do something like the following:


{% extends "base.html" %}
{% block head %}
    {% ifequal current_site.domain "www.whatever.com" %}
        <link rel="stylesheet" type="text/css" href="/media/whatever.css" />
    {% endifequal %}
{% endblock %}

There trick here, though, is "How is my template going to know what site is being requested?"

I could put something like the following in every view I write...

current_site = Site.objects.get_current()

BUT, that's a lot of extra stuff to type. Especially if you have a lot of views.

The clever thing to do, would be to write code so that a Site object containing the currently requested site is automatically added to the current Context. We can do that by writing our own Context Processor!

And that's just what I did! The following code is fairly simple. It just retrieves the current Site from given request object (using Site.objects.get_current()), then returns a dictionary mapping the current site to the variable name current_site.

from django.conf import settings
from django.contrib.sites.models import Site

def current_site(request):
'''
A context processor to add the "current site" to the current Context
'''
    try:
        current_site = Site.objects.get_current()
        return {
            'current_site': current_site,
        }
    except Site.DoesNotExist:
        # always return a dict, no matter what!
        return {'current_site':''} # an empty string

For this to work, we've got to add the function above to the list of TEMPLATE_CONTEXT_PROCESSORS in our project settings file. My project directory is called "mysite", so I created a folder called "context_processors", and in it, I created a file called "current_site.py". That file contains the function current_site defined above.

To get my new context processor working, I've got to edit mysite/settings.py, which now looks something like the following:

TEMPLATE_CONTEXT_PROCESSORS = (
    "mysite.context_processors.current_site.current_site",
    "django.core.context_processors.auth",
    "django.core.context_processors.debug",
    "django.core.context_processors.i18n",
    "django.core.context_processors.media",
)

Voila! Now, while writing template code, I can always access the current_site variable!