Append slashes to URLs in Django

Quick Django pop quiz. Can anyone spot the deliberate mistake in the following url definition? We’re trying to define a view called log_viewer and instructing a specific url pattern to render it.

pre. urlpatterns = patterns(“, (r’^log/?$‘, log_viewer), )

In this case our regex matches /log or /log/ using the /? optional pattern. This is because even if we only link to one format we know people will probably visit both, either by entering the URL manually or by linking from an external source.

As far as HTTP is concerned though /log and /log/ are separate URLs, even if they display the same content. The main reason this matters for public facing websites is that our friendly search engine spiders are likely to index both separately, leading to splitting the page rank as well as accusations of duplicate content which might see further erosion of rankings.

The solution is generally to issue a 301 redirect from one format to the other. This tells search engines and people alike that the canonical location for the requested content is elsewhere. You could specify the redirect manually, but this is going to get irritating quickly once you have a few more definitions.

pre. urlpatterns = patterns(“, (r’^log$‘, redirect_to, {‘url’: ‘/log/’}), (r’^log/$‘, log_viewer), )

Handily Django provides a mechanism to do exactly what we want to do by setting APPEND_SLASH to True in your settings file. Even better it’s switched on by default. So if you don’t know much about the intricacies of HTTP you still get the correct behavior. That is unless you specify your URL patterns in the format above.

You see APPEND_SLASH only works if the URL doesn’t match a specified pattern. If no pattern match is found it appends a trailing slash and checks for a match again. Because the above pattern matches the pattern without the trailing slash (/log) the desired behavior is never triggered, and the view is rendered at both URLs. So although we want to catch /log and /log/ on the front end, our urls.py definition should actually be:

pre. urlpatterns = patterns(“, (r’^log/$‘, log_viewer), )

Django has lots of useful bits of magic for doing the right thing, but unless you know what they actually do you either end up recreating functionality yourself, or find features don’t work in quite the way you thought. It’s a good argument for keeping frameworks small whenever possible, and for developers to at least know their way around the code of their respective framework.