Grouping URLs in Django routing

One of the things I liked (and still like) about 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 looking something like:

from django.conf.urls import url, patterns

urlpatterns = patterns('myapp.views',
    url(r'^(?P<slug>[-\w]+)/$', 'detail'),
    url(r'^(?P<slug>[-\w]+)/edit/$', 'edit'),
    url(r'^(?P<slug>[-\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<slug>[-\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<slug>[-\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 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<slug>[-\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<slug>[-\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 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.

One thought on “Grouping URLs in Django routing

Leave a Reply

Your email address will not be published. Required fields are marked *