Why I don't like reverse and {% url %} in Django

Django has a feature that allows you to look up an url based on a function name and arguments. Here’s how it works:

######
# urls.py
urlpatterns = patterns('',
    (r'^projects/(?P\d+)/$', 'views.view_project'),
)
 
######
# views.py
def view_project(request, project_id):
    # create the view
 
######
# something.py
some_url = reverse("views.view_project", 4)
# some_url = /projects/4/
 
######
# sometemplate.html
<a href="{% url 'views.view_project' 4 %}">My project</a>

The idea behind urls.py is to separate urls, which are not supposed to change (because someone may have bookmarked them, there might be links to the page, etc) from implementation (views.py), which can change at any time (due to refactoring, new features, and bugfixes). You can move functions around at will, and simply change urls.py to point at the right function without breaking people’s links. I love this decoupling.

The idea behind reverse and url is that you should not be hard-coding urls into your code, you should use the names of the functions being called instead. I’ve tried to do this because its the “proper thing to do” if you’re a django coder… but I don’t like it, and I’m going to quit using it in my personal projects.

Here’s why:
1) It’s harder to maintain. As stated above, I often change the name or location of a view function. When I do that, I have to go through all the files that have a url or reverse call and change the view there. If I had hardcoded the url, I’d only have to change it in urls.py. I’ve been stung by this much more often than by changing urls.

2) Its harder to read. When you see “/path/to/something” you know you’re looking at an url. When you see reverse(‘some.module.path’, some, arg), it takes longer for the brain to parse, even if you know what the reverse call does.

3) In the case of {% url %} it exposes implementation details to the template author. Template authors should not know or care what python functions are being called internally. But they know what an url is, and what it represents.

In short, it adds an extra layer of abstraction to url handling. I like abstraction layers to be thin and useful; in the case of reverse(), the added complexity does not, in my minimalist opinion, justify the supposed gain in simplicity.

7 Comments

  1. Andrey says:

    Hi

    First of all, you didn’t never read section about urls from django patterns (http://djangodesignpatterns.uswaretech.net/urls.html ), also you need to learn about different between 301 and 302 redirects (must read: http://uswaretech.com/blog/2008/10/building-seo-optimised-django-web-applications/ ). About assertion #3: If you will use practic—É of usage name of url from django patterns every non-stupid “template author” will can read and to understand all urls such as {% url category_edit category.id %} or {% url user_add %}.

  2. cactus says:

    I think for the base case (non-portable site specific), you are right. It is more work with little benefit.

    However, if you are writing a portable app that someone else is meant to tie into their django site, you might not know where the app will be ‘rooted’. They could add the url mapping with an include, rooting it wherever they please.

    An example is the comments app.

    (r’^comments/’, include(‘django.contrib.comments.urls’)),

    That is really one of the only cases I can make for reverse. Much like you, I have always felt a little weird whenever I use it.

  3. dusty says:

    Cactus: That’s true; when designing reusable apps, a lot of things are done differently, as you’re basically writing a library. In practice, most projects are composed of apps not intended for reuse.

    Andrey: (I edited your comment so the two clickable links don’t return 404s. No other content was changed.) I am suggesting that the urls ‘django pattern’ should be applied only thoughtfully; many Django coders follow the ‘always use reverse and url’ rule, when it is actually making code less readable and maintainable (not good Python). A better rule is “use reverse and url when it makes sense to do so.”

    I’m puzzled that you inferred from my post that I don’t understand the difference between permanent and temporary redirects. While a permanent redirect can obviously “fix” changed urls, they also create two (or more) references to a single object, which makes long-term maintenance confusing. They are useful when necessary, but should be avoided and not used as a crutch, and certainly not as an excuse for sloppy url design. However, if you like permanent redirects, I suggest taking a look at Branko’s nifty middleware to accomplish the task automatically: http://github.com/foxbunny/django-permalib/tree/master

    As for giving all urls a name that template authors can read, they already have one: the url.

  4. cactus says:

    off topic: comments are showing up in reverse-date order. Kind of counter intuitive!

  5. Antti Rasinen says:

    This is precisely the raison d’etre for named url patterns. Naming your url as “project_detail” solves both 1) and 3). The point about readability still stands. However, {% url project_detail 4 %} doesn’t have as much fluff and is almost as readable.