In my hurry to make Pownce (four months!), I didn't really take the time to look into all the really nice features of Django.
Anyways, I've been working on a side project in my spare time and since I'm not pressed to complete it, I've taken my sweet time to explore some of the niceties of the Django framework. One of my favorite such niceties is the URL lookup template tags.
For my side project, code named Tilde, I'd like to have a series of settings pages for various user settings such as profile information, S3 account information, and the user's password. I have a series of links like this:
<a href="/settings/profile/">Profile</a>
<a href="/settings/account/">Account</a>
<a href="/settings/password/">Password</a>
My urls.py contains the mapping of these URLs to my Django views for the settings pages like so:
urlpatterns += patterns('tilde.account.views',
(r'^settings/profile/$', 'settings_profile'),
(r'^settings/account/$', 'settings_account'),
(r'^settings/password/$', 'settings_password'),
)
This works just fine... except it violates one of Django's design philosophies - Don't repeat yourself (DRY). I really shouldn't be hard-coding my URLs in my templates since I've already defined them in urls.py.
Instead, Django provides a built-in template tag, url, to lookup a URL for a particular view. So now my settings tab URLs look like this:
<a href="{% url tilde.account.views.settings_profile %}">Profile</a>
<a href="{% url tilde.account.views.settings_account %}">Account</a>
<a href="{% url tilde.account.views.settings_password %}">Password</a>
This generates the same HTML as my first attempt, without having to hard-code any URLs. I could actually change the URL to any of these views without worrying about breaking any links on my site. Not that it's a good idea to change around URLs on a live website, but it's handy for development.
So let's get a little bit more complex. I'm a big fan of re-using code so I'd like to re-use my views and forms from my S3 account settings page for my S3 account signup page.
So in tilde.account.views.py I have a basic S3 account view:
def account(request, template='signup_account.html'):
# code for S3 account form
...
return render_to_response(template,
{
'form': form,
}, context_instance=RequestContext(request))
And I have the following in my urls.py:
urlpatterns += patterns('tilde.account.views',
(r'^signup/account/$', 'account'),
(r'^settings/profile/$', 'settings_profile'),
(r'^settings/account/$', 'account', {'template': 'settings/account.html'})
(r'^settings/password/$', 'settings_password'),
)
And my settings links look like:
<a href="{% url tilde.account.views.settings_profile %}">Profile</a>
<a href="{% url tilde.account.views.account %}">Account</a>
<a href="{% url tilde.account.views.settings_password %}">Password</a>
Which unfortunately renders as:
<a href="/settings/profile/">Profile</a>
<a href="/signup/account/">Account</a>
<a href="/settings/password/">Password</a>
Since I've mapped two URLs to the same view, when I request the corresponding URL for tilde.account.views.account I get the first one defined in urls.py (/signup/account/) which is not the one I'm looking for! I could just switch the order, but then I couldn't use the url template tag for the signup url and really, that's cheating.
Thankfully, I can name my URLs to distinguish between the one for signup and the one for settings.
urlpatterns += patterns(
'tilde.account.views',
url(r'^signup/account/$', 'account', name='signup_account'),
url(r'^settings/profile/$', 'settings_profile', name='settings_profile'),
url(r'^settings/password/$', 'settings_password', name='settings_password'),
url(r'^settings/account/$', 'account', {'template': 'settings/account.html'}, name='settings_account'),
)
I can then use these names in my template tags like so:
<a href="{% url settings_profile %}">Profile</a>
<a href="{% url settings_account %}">Account</a>
<a href="{% url settings_password %}">Password</a>
Purty!! My settings links are now awesome.
However, I'd like to go just a bit further and make them a bit more tab-like. That is, when I'm on a particular settings page, I'd like to have it just display text instead of a link so I know which page I'm on. I know I can get the current page's path with request.path, so I can do the following:
{% ifequal request.path '/settings/profile/' %}
<span>Profile</span>
{% else %}
<a href="{% url settings_profile %}">Profile</a>
{% endif %}
{% ifequal request.path '/settings/account/' %}
<span>Account</span>
{% else %}
<a href="{% url settings_account %}">Account</a>
{% endif %}
{% ifequal request.path '/settings/password/' %}
<span>Password</span>
{% else %}
<a href="{% url settings_password %}">Password</a>
{% endif %}
Blech. Resorting back to hard-coding the URLs is not cool and there's no way I can put the url template tag inside another template tag! In Django 1.0 and later, I can actually store the result of the url template tag into a value like so:
{% url settings_profile as url_profile %}
I can then use that variable in other template tags. (This actually breaks from Django convention a bit and smells like PHP, but it's awfully useful, so I'll let that go.)
My final settings links then look like this:
{% url settings_profile as url_profile %}
{% url settings_account as url_account %}
{% url settings_password as url_password %}
{% ifequal request.path url_profile %}
<span>Profile</span>
{% else %}
<a href="{{ url_profile }}">Profile</a>
{% endifequal %}
{% ifequal request.path url_account %}
<span>Account</span>
{% else %}
<a href="{{ url_account }}">Account</a>
{% endifequal %}
{% ifequal request.path url_password %}
<span>Password</span>
{% else %}
<a href="{{ url_password }}">Password</a>
{% endifequal %}
Nice! For more details about Django URL mapping, check out the URL dispatcher docs.