I don’t like Django’s {% url %} template tag, and I'm about to tell you why. But first, let's have a little history lesson so we understand why the {% url %} tag exists and what problem it attempts to solve.
I’ve been involved in Django since the .90 release, or nearly three years. As long as I’ve been working with Django, there’s been a convention which basically says that any model whose instances are represented by a page on the site should get a method called get_absolute_url, which returns the relative URL to that instance's page (for now, please ignore the fact that get_absolute_url is misnamed and actually returns a URL relative to the root of the site).
In the early days, the get_absolute_url method for a blog entry might have looked like this...
def get_absolute_url(self)
y = self.date_published.strftime("%Y")
m = self.date_published.strftime("%b").lower()
d = self.date_published.strftime("%d")
return "/blog/%s/%s/%s/%s/" % (y, m, d, self.slug)
…which returns a string like:
/blog/2008/nov/10/djangos-url-template-tag-sucks/
In order to function properly, this string needed to match an entry in your URL configuration. If you changed your URL configuration (for example, to run the blog at /journal/ instead of /blog/), you also needed to change the get_absolute_url method accordingly. This was a significant problem, and one of the biggest complaints about Django back in those days. You should only have to define your URLs in one place.
So, Django added the idea of reverse URL resolving. By using the permalink decorator (or the reverse() function), you can return a string like above, but do so in a way that is aware of your URL configuration. For example:
@permalink
def get_absolute_url(self):
y = self.date_published.strftime("%Y")
m = self.date_published.strftime("%b").lower()
d = self.date_published.strftime("%d")
return ('myproject.blog.entry_detail', None, { 'year': y, 'month': m, 'day': d, 'slug': self.slug })
Now, the string generated by the method will always be correct, no matter if you’re running your blog at /blog/ or /journal/. Beautiful. Problem solved. In order for template authors to link to a blog's entry detail page, all they need to do is use {{ entry.get_absoulte_url }} in their templates. Simple. However...
Along with the permalink decorator and the reverse() function came a new template tag called {% url %}. It is basically a template-side implementation of reverse(), which allows you to get the URL to a particular view, based on its name or path, plus any arguments to the view needed to construct the URL. For example:
{% url myproject.blog.entry_detail entry.date_published|date:"Y",entry.date_published|date:"b"|lower,entry.date_published|date:"d",entry.slug %}
Whew. Okay, so the first and most obvious problem with this is that it’s long and difficult to get just right. I’ll end up with at least one typo in there 90% of the time, whereas I can usually type {{ entry.get_absolute_url }} correctly. Update: It’s worth noting that I actually published this blog entry with a typo in there — but it’s been fixed. :)
But if that wasn’t enough, there are three bigger reasons why I, after several months of using the {% url %} because I was told it was the new hotness, have migrated back to using get_absolute_url.
A core part of Django philosophy has always been that template authors shouldn’t be able to break the site. This is why template tags are designed to fail silently. The design of Django has always been targeted towards the situation where you’ve got developers working on Python code and designers or HTML authors working on templates (which makes sense, because this is the situation we had in Lawrence, where Django was developed).
However, the {% url %} tag does not fail silently. It fails very, very loudly, and very, very regularly. One typo in that long and finicky template tag and you've done broken your site, with 500 errors strewn all over the place (specifically, a NoReverseMatch exception).
This is really frustrating to me. I am completely baffled as to why the Django core team allowed this template tag to fail so loudly when template tags failing silently has always been a core philosophy of the template system.
What’s more: in order for a template author to use the {% url %} tag, they must understand the signature of a view function -- not to mention know the name or path to that function. These are both things a template author should not need to understand. Again, we’re going back to the core Django philosophy that says template authors shouldn’t need to be programmers.
In using the {% url %} tag, a template author will need to regularly refer to the URL configuration to see what path or name a particular view was given, and also need to understand the sometimes-complex regexs being used in the URL so they can pass the right arguments to the view.
This goes so strongly against the grain of key Django philosophies; it’s mind-boggling.
But perhaps the biggest reason I’ve started to use the old standby get_absolute_url instead of the {% url %} tag is the simplest: get_absolute_url still works just fine. It's simple, it's easy, and it lets me keep the business logic of my app in the model method and out of the templates. That feels a lot more elegant, to me.
There are probably situations, particularly when you’re dealing with a third-party reusable app, where you truly need the {% url %} tag. So, I think it's fine that it exists. I wish it stuck to Django's core philosophies that say template authors shouldn't have to be programmers and shouldn't be able to break the site by making a typo, but I'm fine with its existence. But 99% of the time, get_absolute_url works just fine, is a lot less finicky, and just feels way more elegant to me.
Call me old school, but I roll with get_absolute_url.
001 // Nathan Borror // 11.10.2008 // 8:46 AM
I don’t think the NoReverseMatch error showing in non-debug. I think it’s an error you should see when DEBUG=True so you can catch it.
We should also think about some auto-magical documentation generation for template authors to find available url names.
The url tag isn’t a replacement for getabsoluteurl it’s just another tool in the toolbox, IMO. I still heavily depend on getabsoluteurl and I use the url tag when necessary.
There’s an ongoing debate on the topic of template tag errors, whether they should error loudly or softly. I personally think it’s a case by case basis and it’d be nice if template tag authors could easily make this decision in their tag code.
002 // John Shimek // 11.10.2008 // 8:46 AM
I agree with you. I also feel like it violates DRY. I would have a huge url tag in many templates. If I wanted to change the arguments needed for that url, I would have to change many templates instead of just the urls.py and getabsoluteurl.
003 // Adam // 11.10.2008 // 8:48 AM
Well, yeah, when the url you’re referring to is an object’s detail url, then I think getabsoluteurl is probably the way to go, but that’s not all there is to a site. {% url view-name arg1,arg2 %} is still better than /some/arbitrary/path/{{ arg1 }}/{{ arg2 }}/.
Not all URLs relate specifically to objects. {% url login %}, {% url account-settings %}, {% url foo-list %} are much better than the alternative, IMHO.
Basically I write getabsoluteurl methods and use them whenever that’s what I’m referring to, but other urls use the {% url %} tag and named url patterns. Seems like a pretty good compromise to me.
Maybe the {% url %} tag shouldn’t fail so loudly, that’s debatable, but you could always write your own version of the {% url %} tag that fails silently if you’re worried about designers breaking the site.
004 // Joshua Works // 11.10.2008 // 8:49 AM
This sounds pretty spot on. I’m not enjoying using {% url %} for those same reasons.
However, getabsoluteurl only works for object detail pages. I find {% url %} useful in linking to list pages, and especially list pages out of context of the current template.
Example: If I wanted to link to a date-based photo archive from a blog entry, getabsoluteurl isn’t going to help, but I could easily use the blog entry’s published date to generate a url to a photo archive page (or any view using date-based signatures).
005 // kevin // 11.10.2008 // 8:49 AM
I don’t completely agree with you on this one Jeff; well I do agree it should fail silently or like nathan said above it should scream if DEBUG=True, but I do like the combination of the url template tag and named urls.
In fact, Leah Culver just wrote why she does like the url template tag. Maybe that’s why you wrote this post? In response? Here the link: http://leahculver.com/2008/11/06/django-url-goodness/
I hear your points, and I do believe your request/frustration has been mentioned quite often recently. But I also don’t think getabsoluteurl or even the url template tag is carved in stone forever though either, is anything really? So maybe this post will help evolve the solution even further and better than it already is.
Simon Willison has an alpha project he started in an experiment to open up the discussion on resolving the issue with Django’s getabsoluteurl method.
http://code.google.com/p/django-urls/
May want to check that out.
006 // Clint Ecker // 11.10.2008 // 8:51 AM
Re: the view signature, you can give a URL pattern a descriptive name like “blog_entry” in its definition and use it in the {% url %} tag. This is a lot nicer than the signature, as you’ve pointed out.
I personally find {% url %} tags to be quite useful when linking to data driven views. Say for example I have an API url that adds an item to a list of items.
/blah/sorter/list/12/add/item/23/index/22/
I can put this in my templates:
{% url addtolist list.id item.id index %}
Very nice as far as I’m concerned. There’s no way I could use a getabsoluteurl() there because that view isn’t about a single object or even really a single model.
007 // Jeff Croft // 11.10.2008 // 8:57 AM
Very good point. For URLs that don’t correspond to an object, it certainly makes sense. Again, I am not saying we should pull the URL tag from Django — I’m just saying we should remember that
get_absolute_urlstill works just fine and is more elegant in many cases, so let's not throw it out the window.That’s not what I mean by “signature.” By “signature,” I mean the arguments the view function takes. In order to use the URL tag, the template author needs to know the path (or descriptive name) of the view function, as well as any arguments that view takes. Even if you give the URL a descriptive name, it still requires the template author to go digging through Python code to find that name, which goes against the core Django concept that template authors shouldn’t need to understand Python.
Good discussion so far, guys! :)
008 // Adam // 11.10.2008 // 9:13 AM
Yep, I figured that’s what you meant, just wanted to explicitly point it out :). I do agree that forcing designers to know url patterns that basically turn into method signatures is not a good idea. If you find yourself using that kind of thing a lot, it would probably be better to add methods/properties to the object, something like {% obj.getediturl %}, {% obj.getdeleteurl %}. There are probably better ways to do that, but something of the type would be better than requiring the designer to know that the url requires the date in Y/m/d format and the slug.
009 // Peter Baumgartner // 11.10.2008 // 9:24 AM
Adam Gomaa came up with a creative solution to the problem that some objects may need more than one URL associated with them that you may find useful.
http://adam.gomaa.us/blog/the-python-property-builtin/
(Perhaps that is the Adam that posted just above?)
010 // Kyle // 11.10.2008 // 9:25 AM
I think template tags and filters should always fail silently. I agree that template authors should not be able to break a site just because they perhaps misused a tag.
A technique I started using is outputting an HTML comment with the exception message when a tag fails. It’s been pretty handy, since it lets me debug template tag errors without having to show an entire 500 page.
011 // Adam E (clarifying this time...) // 11.10.2008 // 9:29 AM
Nope, different Adam :), though I’m working with Adam G. on a project right now, and we’re using that in a few places. I started to write it that way, but then didn’t want to write out the full example, so I made it simpler. Same idea, whatever works best for you.
012 // James Bennett // 11.10.2008 // 9:30 AM
It’s all about named patterns: the simplification is immense, regardless of whether you have to pass an argument or two. And for objects which have one or more complex URLs associated with them, you can always define other methods on those objects, decorated with
permalink(which can be used for any method you like, not justget_absolute_url.013 // Camron Flanders // 11.10.2008 // 9:48 AM
Like most, for anything that’s a model, I’m using
get_absolute_url, for everything else I am using theurltag; however, I do agree that it's in desperate need of reworking.Off the top of my head, I came up with something that I might try to implement with my own template tag:
If you pass an object that has a
get_absolute_urldefined to theurltemplate tag, it should use that.Here’s an example, I have a post model with
get_absolute_url. In my template I use{% url post %}, it callspost.get_absolute_urlfor me and inserts it.Now, if I need to pass some arguments here is what I would do. If your URLconf looks something like:
Using
{% url post-detail post %}would see if the var/object passed to the tag had aslugattribute, and insert that for me. Compared to{% url post-detail post.slug %}.Or, a more complex example would involve the tag being able to build a url with similar syntax to the ORM:
Now instead of
you could use
Any thoughts?
014 // Jeff Croft // 11.10.2008 // 10:01 AM
I love this part of it. That just makes sense, to me. I need to read the rest of it about five times in order to understand it completely. :)
015 // Jeff Croft // 11.10.2008 // 10:03 AM
Yes, I always use named patterns. But, simplification? For the template author? Seriously? You think that:
Is simpler than:
Really?
016 // Camron Flanders // 11.10.2008 // 11:45 AM
Thanks, I can’t wait to hear what you think of the rest once you re-re-re-re-read it :)
Basically, you map attributes of the object to your URLconf. If the tag can successfully create a url, you are given the URL. If not, it fails silently…of course, it will still allow you to explicitly pass arguments. I think the ability to have named args in your URL match attributes of a model would be helpful, and would obviously cut down on the amount of typing involved.
017 // James Bennett // 11.10.2008 // 12:03 PM
Quoting myself since Jeff apparently didn’t read what I wrote:
So if you’ve got a big scary URL with lots of arguments that’d be annoying to do a
urlcall for, well, that's your easiest solution.018 // Andrew Ingram // 11.10.2008 // 12:19 PM
I use the url tag for things like links to ordinarily static pages or site sections. Like an about page or contact form, something that is pretty much fixed but should still be done in such a way that changing the urls.py is all that’s required for changing the urls.
For anything complicated like an instance of a model or a complicated navigational view, I’ll always define a permalink-decorated method (or several) and I don’t think there are many people who would insist this should be done in the template.
019 // Jeff Croft // 11.10.2008 // 12:20 PM
Right, and that’s what I always do. Maybe you didn’t understand what I wrote, but my point was to say, “using a method like
get_absolute_urlis still a valid way of doing this, even with the presence of the {% url %} tag."020 // Jeff Croft // 11.10.2008 // 12:22 PM
Perhaps not. In reading the comments, you may be right. I certainly had the impression from the documentation and from talking to some people that using {{ whatever.getabsoluteurl }} was no still considered a “best practice.” But, perhaps I misunderstood.
Best practice or not, it makes sense to me, so I’m doing it. :)
021 // Matt Dawson // 11.11.2008 // 8:34 AM
Being new to Django, I’m so glad you took the time to write this up - and to the commenters for sharing their dissenting opinions.
I’ve only been working with Django for ~ a month, and I was similarly confused by the docs - on first read, I sure detected a best practice preference/bias for the url tag. And when I realized what I’d have to do to get the url tag to work with date-based generic views, I laughed my head off.
Since I’m trying to sell the idea of Django to the agency I work for, I’m always trying to think out how I’ll explain various template conventions to our design/buildout folks (one of which is me). Explaining that “getabsoluteurl” is for referencing an object detail page and “url” is for pretty much anything else should be sufficient - even if it is reductive and a tad misleading. From my experience, template authors would rather forget about the hows and whys when knee deep in a buildout.
022 // Johnny Webster // 11.13.2008 // 1:12 AM
This article has brought Django to my attention. I will be looking into it. Does anyone know where I can find more info or is it just here, http://www.djangoproject.com ?
023 // lgespee // 12.09.2008 // 4:18 AM
You made a typo:
… is use
{{ entry.get_absoulte_url }}in their ...Should be:
… is use
{{ entry.get_absolute_url }}in their ...024 // cwxwwwxwwxwx // 12.22.2008 // 7:01 PM
well, hi admin adn people nice forum indeed. how’s life? hope it’s introduce branch ;)
025 // Henrik Joreteg // 01.29.2009 // 8 AM
Thanks for that comment James! I didn’t get it at first, I’m fairly new to Django and didn’t really see the point of the URL tag until now. The concept of naming my url patterns was new to me, but now that I get it… I LOVE it!
@Jeff, as usual, I appreciated your post. I always learn something new.
026 // Unternehmensberatung Russland // 02.02.2009 // 1:45 PM
Very nice article. Thanks.
027 // Unternehmensberatung Russland // 02.02.2009 // 1:45 PM
Very nice article. Thanks.
028 // conficker // 03.31.2009 // 10:27 PM
Has this changed since you wrote the article?
029 // florida fishing // 04.17.2009 // 2:12 PM
ive had problems with template sites similar to this before. they suck! great article. thanks
030 // Some dude // 04.29.2009 // 5:26 PM
Thanks for this. New to django and did not look deep enough into the model docs to know about this. Way better approach than using url (I couldn’t even get the tag to work at all).
Additionally, is the benefit of automatically adding the view on site link in the admin interface when models implement this method.
031 // Wrinkle Cream Site Development // 06.05.2009 // 10:30 AM
We just took over management of a huge static site and are thinking about converting it to Drupal. The biggest problem is moving all the content into the new platform and making sure the old URL patterns are either matched or 301 redirected.
032 // aloy // 06.16.2009 // 7:13 AM
I think the major advantage with Django regarding SEO is the ability to write your own markup.
033 // test // 06.30.2009 // 4:54 AM
test
034 // How can I find someone's cell phone number // 09.09.2009 // 7:30 AM
Does the URL template tag make one of those cell phone links. The one’s the if you have a cell phone like Skype, it makes the little icons.?
035 // thencegog // 01.02.2010 // 8:09 PM
I need a Riddle ASAP! i know this sounds extreamly werid but i need a riddle. Dont ask why just give me something!! thanks!
the day the earth stood still
036 // thencegog // 01.02.2010 // 8:28 PM
I need a Riddle ASAP! i know this sounds extreamly werid but i need a riddle. Dont ask why just give me something!! thanks!
the day the earth stood still
037 // thencegog // 01.02.2010 // 9:56 PM
I need a Riddle ASAP! i know this sounds extreamly werid but i need a riddle. Dont ask why just give me something!! thanks!
the day the earth stood still
038 // thencegog // 01.02.2010 // 10:44 PM
I need a Riddle ASAP! i know this sounds extreamly werid but i need a riddle. Dont ask why just give me something!! thanks!
the day the earth stood still
039 // allevevam // 02.10.2010 // 12:17 AM
i mobile 904 http://www.orderphonetoday.com/c902-phone-quad-band-touch-screen-with-analog—item23.html t mobile tune blackberry mobile phones pentax pocketjet 3 mobile thermal printer kit
040 // Whertyheelt // 02.13.2010 // 1:02 PM
vintage mobile home pacemaker tri level http://www.orderphonetoday.com/page5.html windows mobile help pdf viewer for mobile phone comparing t mobile att data plans
041 // codethief // 03.03.2010 // 3:07 AM
Yes, getabsoluteurl is certainly useful. But using it the way you suggested is still wrong: By implementing the method inside your model classes you mix model and view, which you simply shouldn’t do for obvious reasons.
042 // adventurous tour // 03.10.2010 // 4:29 AM
The Django is use to write your own markup.