E-Scribe : a programmer’s blog

About Me

PBX I'm Paul Bissex. I build web applications using open source software, especially Django. Backstory: In the 1990s I did graphic design for newspapers and magazines. Then I wrote technology commentary and reviews for Wired, Salon.com, Chicago Tribune, and lots of little places you've never heard of. Then I taught photographers how to create good websites. I co-wrote a book (see below) along the way. Current story: I am helping turn a giant media corporation into a digital enterprise. Feel free to email me.

Book

I'm co-author of "Python Web Development with Django", an excellent guide to my favorite web framework. Published by Addison-Wesley, it is available from Amazon and your favorite technical bookstore as well.

Colophon

Built using Django, served with gunicorn and nginx. The database is SQLite. Hosted on a FreeBSD VPS at Johncompanies.com. Comment-spam protection by Akismet.

Pile o'Tags

Stuff I Use

bitbucket, Django, Emacs, FreeBSD, Git, jQuery, LaunchBar, Markdown, Mercurial, OS X, Python, Review Board, S3, SQLite, Sublime Text, Ubuntu Linux

Spam Report

At least 231507 pieces of comment spam killed since 2008, mostly via Akismet.

Handling legacy URLs with Django

One of the great things about Django is its simple and flexible URL handling. If your Django work, like mine, includes converting existing sites, you'll probably be doing some URL cleanup along the way. Django's "generic views" system includes a view called "redirect_to" that handles cases like this. A rule might look like this:

urlpatterns = patterns('django.views.generic.simple',
    ('^foo/oldsite/BadOldUrl33247.blech$', 'redirect_to', {'url': '/bar/nice-new-url/'}),
    )

But because the URL pattern building happens in Python, if you have many of these you can do better than filling your urls.py with variants of that line. Here's the root urlconf for one of my sites:

urlpatterns = patterns('',
    # ...long list of patterns here
    (r'^admin/', include('django.contrib.admin.urls')),
    )

legacy_urls = (
    ('^kawasaki/zx750f', '/bikes/kawasaki/zx750f/'),
    ('^pages/ktm_2wd_interview', '/bikes/ktm/2wd/'),
    ('^gear/[0-9]+/$', '/gear/'),
    )

for urltuple in legacy_urls:
    oldurl, newurl = urltuple
    urlpatterns += patterns('', 
        (oldurl, 'django.views.generic.simple.redirect_to', {'url': newurl}))

The first block is my main set of patterns. The second is my list (OK, it's a tuple) of old/new URL pairs. The third block just extends the urlpatterns object with rules for these pairs, using the generic "redirect_to" view.

I haven't needed it for this project, but the documentation explains how the redirects can also be parameterized. For example, if you needed to translate all URLs like "/foo/99/" into "/bar/99/", you'd add this pair to legacy_urls above:

('^foo/(?P<id>\d+)/$', '/bar/%(id)s/'),

Overall this approach is just minor syntax twiddling, but I find the simple tuple style easier to read and maintain.

Techniques like this may be obvious to Django users who grasp the beauty and power of its pure-Python approach -- but I hope it can be helpful for some newcomers who are used to other frameworks where configuration files are brittle, fussy things.

Friday, September 29th, 2006
+ + +
3 comments

Comment from Simon Willison , later that day

There's an even neater way of doing this, thanks to a feature that recently went in to Django that allows you to provide views as callable objects instead of as strings:

def redirect(url):
  def inner(request):
    return HttpResponseRedirect(url)
  return inner

urlpatterns = patterns('',
  ('^kawasaki/zx750f', redirect('/bikes/kawasaki/zx750f/'),
  ('^pages/ktm_2wd_interview', redirect('/bikes/ktm/2wd/'),
  ('^gear/[0-9]+/$', redirect('/gear/')
)
Comment from Paul , later that day

Very cool, Simon, thanks! I'm actually using the callable-object syntax in most of my URLconfs, but I hadn't thought to take advantage of it in that way.

Comment from Max Battcher , later that day

Another suggestion, particularly for those with a lot of "dynamic" content (ie, converting from a major blog or cms application), is to use django.contrib.redirects. It installs just like Flatpages (add it to INSTALLED_APPS, add its middleware near flatpage's, and syncdb) and keeps your redirect information in the database. I was able to populate my redirects table with legacy URLs as a part of the database conversion of my blog. By having it in the database it makes it really easy for me to slowly phase out legacy URLs as search engines adapt and I weed out legacy links.

Comments are closed for this post.