Sunday 12 April 2009

Name your URLs

Today's tip will be delving into the subject of url naming. Whilst this is touched on in part 4 of the django tutorial it is pretty much a blink and you'll miss it affair for what is an incredibly useful tool and thus warrants further consideration.


Take the following snippet of code, extracted from a typical project, as an example:


# File: project/urls.py
from django.conf.urls.defaults import *

urlpatterns = patterns('',
# Include mysite urls
(r'^mysite/', include('mysite.urls')),
)

# File: project/mysite/urls.py
from django.conf.urls.defaults import *
from views import display_homepage, display_about, display_article,\
display_comment

urlpatterns = patterns('',
# Display the homepage.
(r'^$, display_homepage),
# Display the about page.
(r'^about/$', display_about),
# Display an index of all articles.
(r'^article/$', display_article),
# Display a specific article.
(r'^article/(?P<article_id>\d+)/$', display_article),
# Display a specific comment for a specific article.
(r'^article/(?P<article_id>\d+)/comment/(?P<comment_id>\d+)/$',
display_comment),
)

These url files have already been pretty well defined, but they will require urls to be hard coded throughout the views increasing the chance that a link could be malformed and creating a potential logistical nightmare should a url ever have to be changed. This is a perfect time to start naming things!


# File: project/mysite/urls.py
from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.views',
# Display the homepage.
url(r'^$, 'display_homepage', name='mysite_homepage'),
# Display the about page.
url(r'^about/$', 'display_about', name='mysite_about'),
# Display an index of all articles.
url(r'^article/$', 'display_article', name='mysite_article_index'),
# Display a specific article.
url(r'^article/(?P<article_id>\d+)/$',
'display_article', name='mysite_article_by_id'),
# Display all the comments for a specific article.
url(r'^article/(?P<article_id>\d+)/comment/$',
'display_comment', name='mysite_comment_index'),
# Display a specific comment for a specific article.
url(r'^article/(?P<article_id>\d+)/comment/(?P<comment_id>\d+)/$',
'display_comment', name='mysite_comment_by_id'),
)

The first difference that you might notice is that each entry passed in to patterns is now the result of a call to url rather than just being a simple tuple. Internally this doesn't change much as it's pretty much the first thing django does with each entry when it gets them anyway but by doing this yourself it allows you to pass a value in to the name argument. Which leads nicely to the other change made to these files which is the names themselves. Each url entry now has a name which as you can see I've formed from the name of the application and the description of the view.


Now that we've got the names in place we can get around to the good part, ensuring that we no longer have urls hard coded in our project. For inserting urls into templates we can use the template tag aptly named url so where you once had
<a href="/mysite/about/">About This Site</a>
you would now have
<a href="{% url mysite_about %}">About This Site</a>
whilst this may look a little verbose for the simple example given here, if we were to change the url for the about page to about_us/ then one change to the urls.py file would be sufficient to change every occurrence throughout the entire site.


Next we can go about replacing the various instances for the more complex urls, and this is where the url tag really begins to shine as what once would have been
<a href="/mysite/article/{{ article.id }}/comment/{{ comment.id }}/">{{ comment.title }}</a>
becomes
<a href="{% url mysite_comment_by_id article.id comment.id %}/">{{ comment.title }}</a>
By adding parameters one after another this will ensure that they are inserted into the correct place in the url and that if any part of the url changes it will update them all automatically. The only stipulation on any changes is that any parameters must stay in the same order.


Whilst the url tag allows us to eliminate hard coded urls from templates it can't be used within normal python code. For this you need to call upon the reverse function like so:


# File: project/mysite/views.py
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response

def display_comment_link(request, article_id, comment_id):
"""A function that for some reason passes two urls to a template for display."""
comment_link_url = reverse('mysite_comment_by_id',
kwargs={'article_id':article_id,
'comment_id':comment_id,
}
)
article_link_url = reverse('mysite_article_by_id',
kwargs={'article_id':article_id,}
)
return render_to_response('mysite/display_comment_link.html',
{ 'comment_link_url':comment_link_url,
'article_link_url':article_link_url,
}
)

Now whilst this function is fairly pointless it demonstrates how you can generate the urls programmatically and thus eliminate the need for hard coding throughout your code.


Unfortunately there are still going to be times where you will be unable to use named urls within your code. Luckily these are few and far between and exist solely when the url regular expression contains a choice separated by a | character. In these cases the reverse function will be unable to tell which option should be used and so fail to generate a result. Hopefully the django team will come up with a solution for this issue in the near future, until then if you wish to use naming for those kinds of urls then the regular expression will need to be split up into one entry per combination of options (although this may not be feasible if the expression is particularly complex with many sets of options).


So we have reached the conclusion of this first set of tips on using django. I hope it has been of some use and that going forward your urls will remain manageable. If you have any questions feel free to leave a comment below, and if you are interested in further django tips feel free to register as a follower, or just add the site's feed to your aggregator.

6 comments:

  1. Thanks so much for posting this detailed article with the color highlighting. I had somehow missed adding url to the beginning of the urlpatterns( function when adding name="". I couldn't get template tag url to work with just a path. Anyway it all works now.

    ReplyDelete
  2. Happy to be of assistance :-)

    ReplyDelete
  3. I know this post is somehow old, but it really has helped me. I'm a beginner in Django and these "little things" make a huge difference in the end. Thanks!

    ReplyDelete
  4. I'm glad these posts are still proving useful.
    Hopefully at some point in the near future I'll find some time to bring some of them up to date with a few of the changes that have occurred in the last three years!

    ReplyDelete
  5. Weon eres una bestia! Te harĂ­a un kiko inmediatamente. Has salvado un proyecto completo! =D

    ReplyDelete
  6. effective and essential post for me. this post contains a lot of things. thanks for share it.many and effective information are include there.but its nice to read its content.thanks for this.

    ReplyDelete