Nicer URL mappings in Django

Posted 2013-05-11 17:15. Last updated 2018-01-05 14:00. Tagged , , .

URL mappings in django consists more or less of a list of (regex, handler) tuples, if the regular expression matches the requested URL, the handler is used to serve the request. The regular expressions themselfes tends to be rather long and not as readable as I would want them to. Let’s see if we can fix that.

Update 2018-01-05 14:00: Django 2.0 was recently released and supports a scheme simliar to what I present here (but more generic). If you use this, consider upgrading to Django 2.0 and see the documentation on the URL dispatcher.

Here’s a few lines of urls.py from the main app of my blog server, as an example:

    url(r'^(?P<lang>(sv|en)?)$',          index, {'year': None}, name='index'),
    url(r'^(?P<year>[0-9]{4})/(?P<lang>(sv|en)?)$', index, name='index'),
        post_detail, name='post_detail'),
    url(r'^img/(?P<slug>[a-z0-9_-]+)',    image_view, name='image_view'),
    url(r'^tag/(?P<slug>[a-z0-9-]+)\.(?P<lang>(sv|en))$', tagged, name='tagged'

As you see, the regular expressions is rather repetitive. In particular, (?P<name>[a-z0-9-]+) comes up a lot. Since we know we are talking about URLs, we should be able to improve a lot.

How about we assume by default that parameters should match the format [a-z0-9-]+, and note beforehand when parameters should look like something else. How about like this:

    url.bind('year', '[0-9]{4}')
    url.bind('lang', '(sv|en)')
    url(r'^<lang>?$',               index, {'year': None}, name='index'),
    url(r'^<year>/<lang>?$',        index,       name='index'),
    url(r'^<year>/<slug>\.<lang>$', post_detail, name='post_detail'),
    url(r'^img/<imgid>',            image_view,  name='image_view'),
    url(r'^tag/<slug>\.<lang>$',    tagged,      name='tagged'),

Admittedly, this is one lines more but it is a few characters shorter, and more important, I think it is much easier to read. Luckily, this is Python, so it is quite easy to make the second version work. Here’s one way of doing it:

class UrlShortcut:
    def __init__(self):
        self.bound = {}
        self.default = r'[a-z0-9-]+'

    def bind(self, name, pattern):
        self.bound[name] = pattern

    def __call__(self, pattern, *args, **kwargs):
        def djangify(match):
            p = match.group(1)
            return r'(?P<%s>%s)' % (p, self.bound.get(p, self.default))
        return django_url(re.sub(r'<(\w+)>', djangify, pattern),
                          *args, **kwargs)

url = UrlShortcut()


Write a comment

Basic markdown is accepted.

Your name (or pseudonym).

Not published, except as gravatar.

Your presentation / homepage (if any).