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.