Monthly Archives: June 2013

Grouping URLs in Django routing

One of the things I liked (and still like) about [Django][django] is that request routing is configured with regular expressions. You can capture positional and named parts of the request path, and the request handler will be invoked with the captured strings as positional and/or keyword arguments.

Quite often I find that the URL patterns repeat a lot of the regular expressions with minor variations for different but related view functions. For example, suppose you want CRUD-style URLs for a particular resource, you would [write an `urls.py`][urls] looking something like:

from django.conf.urls import url, patterns

urlpatterns = patterns(‘myapp.views’,
url(r’^(?P[-\w]+)/$’, ‘detail’),
url(r’^(?P[-\w]+)/edit/$’, ‘edit’),
url(r’^(?P[-\w]+)/delete/$’, ‘delete’),
)

The `detail`, `edit` and `delete` view functions (defined in `myapp.views`) all take a `slug` keyword argument, so one has to repeat that part of the regular expression for each URL.

When you have more complex routing configurations, repeating the `(?P[-\w]+)/` portion of each route can be tedious. Wouldn’t it be nice to declare that a bunch of URL patterns all start with the same capturing pattern and avoid the repetition?

It _would_ be nice.

I want to be able to write an URL pattern that defines a common base pattern that the nested URLs extend:

from django.conf.urls import url, patterns, group
from myapp.views import detail, edit, delete

urlpatterns = patterns(”,
group(r’^(?P[-\w]+)/’,
url(r’^$’, detail),
url(r’^edit/$’, edit),
url(r’^delete/$’, delete),
),
)

Of course there is no `group` function defined in Django’s `django.conf.urls` module. But if there were, it would function [like Django’s `include`][include] but act on locally declared URLs instead of a separate module’s patterns.

It happens that this is trivial to implement! Here it is:

from django.conf.urls import url, patterns, RegexURLResolver
from myapp.views import detail, edit, delete

def group(regex, *args):
return RegexURLResolver(regex, args)

urlpatterns = patterns(”,
group(r’^(?P[-\w]+)/’,
url(r’^$’, detail),
url(r’^edit/$’, edit),
url(r’^delete/$’, delete),
),
)

This way the `detail`, `edit` and `delete` view functions still get invoked with a `slug` keyword argument, but you don’t have to repeat the common part of the regular expression for every route.

There is a problem: it won’t work if you want to use a module prefix string (the first argument to `patterns(…)`). You either have to give a full module string, or use the view objects directly. So you can’t do this:

urlpatterns = patterns(‘myapp.views’,
# Doesn’t work.
group(r’^(?P[-\w]+)/’,
url(r’^$’, ‘detail’),
),
)

Personally I don’t think this is much of an issue since I prefer to use the view objects, and if you are using [class-based views][cbv] you will likely be using the view objects anyway.

I don’t know if “group” is a good name for this helper function. Other possibilities: “prefix”, “local”, “prepend”, “buxtonize”. You decide.

[django]: https://www.djangoproject.com/
[include]: https://docs.djangoproject.com/en/1.5/ref/urls/#include
[cbv]: http://ccbv.co.uk/
[urls]: https://docs.djangoproject.com/en/1.5/topics/http/urls/