Eugene's Blog

I can't believe it's blog!

Adding django-shorturls

Time to time I sync my blog software to the Django trunk and introduce small enhancements. Some of them visible to my readers, some of them are just for me. This time after reading Simon Willison’s post on rev=canonical I decided to add short URLs to my blog as well. This is a convention to provide custom short URLs managing the mapping on your own web site. In my opinion the idea is very cool.

To be completely honest: while I like the idea I am not completely sold on its implementation. For example Mark Nottingham advocates a different way to provide the same information: rel=shorturl (via Simon’s post), and it sounds more logical to me. Oh, well. The first approach is easy to try: Simon coded it for Django, and Jacob Kaplan-Moss even created a full-fledged Django application out of it available both on github and on pypi — with this kind of availability I had no excuse not to try it. And rev=canonical can be used together with rel=shorturl, if I want to add it later, using the same universal django-shorturls application.

So after buying lzt.me domain I armed myself with Simon’s article and Jacob’s git repository, and set to add it to my blog. And while everything about django-shorturls is super-simple I managed to hit the brick wall repeatedly in all possible ways (don’t judge me too harsh — I decided to do it at night not thinking clearly). Below you will find a list of gotchas for myself and others:

  1. While the application is called “django-shorturls”, when it is installed it is available as “shorturls”. Simple enough.
  2. While the application is available as “shorturls”, its template tag library is called “shorturl” (no s at the end). Somehow my fingers typed “shorturls” in all templates — probably my tired brain expected that the major piece of functionality is called exactly as the application itself. It looks like Jacob was confused like me and typed it both ways in examples of the readme file.
  3. The main tag is called “shorturl” logically enough. Probably that’s what gave the name for the template library.
  4. After coding everything up (literally 5 minutes + stupid gotchas above) I’ve noticed that in some places I am getting nothing instead of an expected URL value. I figured that something is wrong and Django famously silenced errors in templates. After frantic attempts to debug the problem I’ve noticed that I misspelled a name of my model in SHORTEN_MODELS settings. The stupid typo led to exceptions in the template tag, which was silenced, and no other diagnostics were given.
  5. Now everything works as expected per Jacob’s readme, yet no cigar — my blog generates URLs like this: http://lzt.me/dBh, but it doesn’t process them. Instead it expects URLs like this: /short/dBh. WTF? There is nothing about it in the readme. But Jacob starts it by referencing Simon’s article, and if you read it to the end you would know that it requires a little help from a web server: a URL rewriting. Simon does it with nginx.
  6. I love nginx! In fact I use it to drive my web site. Finally I got lucky and can use his recipe directly! I copied Simon’s code and… it doesn’t work. The close inspection found the problem: django-shorturls expects incoming short URLs to be anchored at /short/, but Simon’s code rewrites to /shorter/. An easy fix when you know the problem.

While I am embarrassed to have so many mishaps with a simple application, I hope people will learn from my mistakes.

The last problem happened mostly because Simon’s code intimidated me: it uses some fancy proxy_pass, yet sets proxy_redirect to off. I don’t understand what it does, all proxy business feels slow. Most probably it targets a setup with multiple servers. I use just one physical server for everything, so I rewrote his fancy redirects “by the book”. Here are the relevant parts of nginx.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# processing the "short" domain
server {
  listen 80;
  # both lzt.me and www.lzt.me versions are allowed
  server_name lzt.me www.lzt.me;
  location =/ {
    # the root is mapped to my main site
    rewrite (.*) http://lazutkin.com/ permanent;
    return 444;
  }
  location / {
    # the rest is rewritten as /short/
    rewrite (.*) http://lazutkin.com/short$1 permanent;
    return 444;
  }
}

# suppress www.lazutkin.com in favor of lazutkin.com
server {
  listen 80;
  server_name www.lazutkin.com;
  rewrite ^/(.*)$ http://lazutkin.com/$1 permanent;
  return 444;
}

# the main web site
server {
  listen 80;
  server_name lazutkin.com;

  location / {
    # let's call Django using FastCGI
    root   html;
    index  index.html index.htm;
    fastcgi_pass 127.0.0.1:8989;

    include fastcgi_params;

    fastcgi_param PATH_INFO $fastcgi_script_name;
    fastcgi_pass_header Authorization;
    fastcgi_intercept_errors off;
  }

  # the rest maps static resources
}

Now I am happy with the result. Want to try it out? Just grab the shortening bookmarklet from the original Simon’s post and run it on this page.

You should be aware of some restrictions: django-shorturls provides a two-way mapping using a numeric primary key of an underlying object. If your primary key is not a number, it will not work. Another restriction: your URL should match some unique object. It means that there is no easy way to give short URLs to lists of objects, different views of the same object, or any other derivatives.

In practice these restrictions are minor. For example, this blog fits the scheme almost perfectly, and I am happy to trade the unnecessary flexibility for the short and sweet code. But if you have a lot of web pages like described above, you should think about more sophisticated ways to map URLs to their shorter alternatives.