URLConf tips and tricks

Published November 6, 2007. Filed under: Django.

I’ve written a couple of things recently talking about useful things to be aware of when you’re putting together the URL configuration for a Django application — one covering a pitfall you should watch out for with regular expressions and one touching on the utility of the “url” tag and the “permalink” decorator, and the “reverse” utility — but you can never have too much useful information about URL configuration, because for a lot of people it seems to be one of the trickier parts of Django. So let’s look at a few more things you might not know about.

Be careful with module globals

Fairly regularly, someone pops up complaining that a value seems to be magically “cached” somehow, because a variable whose value is supposed to change over time isn’t changing at all. For example, someone might have a URLConf like this:

from django.conf.urls.defaults import *
from blog.models import Entry, Link

info_dict = {
    'queryset': Entry.objects.all(),
    'date_field': 'pub_date',
    'extra_context': { 'latest_link': Link.objects.latest() },
}

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.date_based.archive_index', info_dict),
)

In theory, this will set up an index of the latest entries, and also populate the latest link into the context of the generic view. Except it won’t, really; if you publish a new link you’ll find that it takes a while before the latest_link variable in the template context updates to reflect that. The explanation for this is the way it was assigned; as we saw last time, any variable assigned at the module level — including the items in a dictionary — is actually only calculated once per server process, and then stays in memory for the lifetime of that process.

In the cases we were looking at previously, that was useful behavior, because it provided a way to have something get calculated once and stick around for a long time. But in a case like this one, it’s the exact opposite of what’s wanted; instead, the latest_link is intended to update on each request, but because it’s being assigned at the module level that never happens.

The solution for things which need to be calculatd on a per-request basis is to move them into code that’s executed fresh on each request, like a view. If you’re using generic views, you can accomplish this by writing a short wrapper function around the generic view which calculates the extra context, and generally it’s only one or two lines of code.

As an aside, this also creeps up occasionally in model definitions; it’s the difference between this:

pub_date = models.DateTimeField(default=datetime.datetime.now())

which calls datetime.datetime.now() once, when the module is imported, and this:

pub_date = models.DateTimeField(default=datetime.datetime.now)

which passes the function as the default, ensuring Django will call it each time as needed (and when we looked at generic model-handling code the other day, we saw how you can test for this in your own code).

Use named URL patterns…

I’ve previously written about why reverse() and permalink() and the {% url %} tag are such handy helpers to have in a Django application (since they let you design truly decoupled and modular URLs), but in order to use them to their utmost you really ought to be using named URL patterns. These are currently only available in the development version of Django, but they’re so handy (and Django’s trunk is generally so stable) that they’re worth upgrading for. It’s ever so much simpler to write this:

url(r'^weblog/$', 'django.views.generic.date_based.archive_index', info_dict, name='weblog_index'),

and then be able to drop calls in your templates like

{% url weblog_index %}

than to have to write out the full name of the view each time (and, for cases where you have multiple URLs routing to the same view, as when you’re making heavy use of generic views, this is also the simplest way to avoid the conflicts which would arise from reverse lookups based on view function names).

…but make sure your views exist

Using Django’s reverse-URL lookups, does open up a potential pitfall, however, because Django needs to scan through your root URLConf each time it’s trying to construct a URL, which means that all of your URL patterns need to point to valid view functions. If Django is scanning your URLs, looking for the view function named in a call to reverse() or the url tag, and it runs into a pattern which resolves to a non-existent view function, you’ll end up getting a ViewDoesNotExist exception no matter what; Django needs to see what each URL pattern resolves to before it can come up with the correct match for a reverse lookup.

And if you have a typo in your pattern name, or in the name you passed to reverse() or {% url %}, you’ll similarly get the exception NoReverseMatch; in order to work, you need to give the URL resolver something that’s a spot-on match.

Using functions directly in the URLConf

This is a feature that’s been in Django since shortly after the 0.95 release, and was included in 0.96: instead of specifying a string which gives the path to your view function, you can import the view and use it directly. For example:

from django.conf.urls.defaults import *
from django.views.generic import date_based
from blog.models import Entry

info_dict = {
    'queryset': Entry.objects.all(),
    'pub_date': date_field,
}

urlpatterns = patterns('',
    url(r'^weblog/$', date_based.archive_index, info_dict, name='weblog_index'),
)

At first that might not seem very handy (aside from saving you some typing), but it opens up an extremely important use case: since you’re working directly with the view function, you can decorate it right there in the URLConf. For example, if you wanted to restrict the weblog in the example above to only logged-in users:

from django.conf.urls.defaults import *
from django.views.generic import date_based
from django.contrib.auth.decorators import login_required
from blog.models import Entry

info_dict = {
    'queryset': Entry.objects.all(),
    'pub_date': date_field,
}

urlpatterns = patterns('',
    url(r'^weblog/$', login_required(date_based.archive_index), info_dict, name='weblog_index'),
)

This is extremely powerful, for two reasons:

  1. It lets you decide, on a per-URL basis, whether to decorate a particular view function, which improves the reusability of your views.
  2. It provides an easy way to decorate generic views without having to write a decorated wrapper function around them.

Per-request URL configuration

This is another fairly new feature, though it was in the 0.96 release, and as far as I know it hasn’t yet been documented because there are still some issues revolving around reverse URL resolution when you use it, but Django will let you override the ROOT_URLCONF setting on a per-request basis. This is useful, for example, if you know you’ll have a lot of different “sub-sites” which aren’t different enough to warrant giving each one its own settings and Site object, but which still need somewhat varying URL configurations. All you have to do is assign a urlconf attribute to the request early on in processing (basically, in the “process_request()” method of a middleware class), using the same style as the ROOT_URLCONF setting, and Django will use that in place of the URLConf specified in the settings module. For example, you might have a middleware like this:

class URLSwitchingMiddleware(object):
    def process_request(self, request):
        request.urlconf = 'myproject.urls.%s' % request.META['HTTP_HOST'].replace('.', '_')

For a request with a Host header of “www.example.com”, this would set the root URLConf to myproject.urls.www_example_com (note the example here replaces dots with underscores, since dots will be interpreted as separators in a Python import path).

This isn’t something you’ll always need to do, and probably not something you’ll need to do very often, but when you need it it’s a lifesaver.