A Django template tag for the current active page

An important part of a usable web site is a well-designed navigation bar. Something that any navigation bar should have is an indication of where the user is. Common practices for this include using a bold face for the link, a different color or adding a small icon next to the current section link. To do that, you could include your navigation code in every page and use an “active” class name on the a element to distinguish it from the other links.


<div id="navigation">
    <a href="/">Home</a>
    <a href="/services/">Services</a>
    <a class="active" href="/contact/">Contact</a>
</div>

Writing this in every template quickly becomes a pain in the neck. With Django, an easy solution would be to move this section to a different file, _nav.html for instance, and include it in every page. Another, even better way, would be to make sure that every template extends a base template (a very common practice with Django) and to add the navigation to that base template.

We face a little problem however: how do we know which page we’re on? If you use the render_to_response shortcut, you can give an extra keyword argument, context_instance. If you use the RequestContext class, you can pass more data to the template (the generic views use RequestContext by default, so you don’t have to worry about them.) Here’s how to do it:


## settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
    'django.core.context_processors.request',
)

## views.py
from django.template import ContextRequest

def home(request):
    return render_to_request('home.html', {}, context_instance=RequestContext(request))

You can now use the request template variable in home.html and its parent templates, which should include your base template. The HttpRequest object has an attribute, path, which contains the current URL (starting after the host name). We can now add the active class to the link for the current section:


<div id="navigation">
    <a class="{% ifequal request.path "/" %}active{% endifequal %}" href="/">Home</a>
    <a class="{% ifequal request.path "/services/" %}active{% endifequal %}" href="/services/">Services</a>
    <a class="{% ifequal request.path "/contact/" %}active{% endifequal %}" href="/contact/">Contact</a>
</div>

This code has two problems: (1) it’s really ugly (it’s probably all that should matter to make you want to rework this code), and (2) imagine that there are subsections under the services section, /services/web-hosting/, /services/tech-support/, etc. When you’re under one of those subsections, the link to the services section will not be highlighted. We’ll fix that by using a custom template tag that will check if request.path begins with a given string.


## tags.py
@register.simple_tag
def active(request, pattern):
    if request.path.startswith(pattern):
        return 'active'
    return ''

<!-- navigation -->

{% load tags %}
<div id="navigation">
    <a class="{% active request "/" %}" href="/">Home</a>
    <a class="{% active request "/services/" %}" href="/services/">Services</a>
    <a class="{% active request "/contact/" %}" href="/contact/">Contact</a>
</div>

Ah, the code is much nicer and now if we go to /services/web-hosting/, the services link will still be highlighted. Another link will be highlighted unfortunately: the home link will ALWAYS be highlighted, because every path begins with a slash. Let’s change the implementation of the active tag slightly to use regular expressions and we’ll also change the calls to {% active %}


## tags.py
@register.simple_tag
def active(request, pattern):
    import re
    if re.search(pattern, request.path):
        return 'active'
    return ''

<!-- navigation -->

{% load tags %}
<div id="navigation">
    <a class="{% active request "^/$" %}" href="/">Home</a>
    <a class="{% active request "^/services/" %}" href="/services/">Services</a>
    <a class="{% active request "^/contact/" %}" href="/contact/">Contact</a>
</div>

Nice! Now it’s how we want it. Home will be highlighted only when we’re viewing the / URL, the subsections still work and the code is still pretty cute.

27 Responses to “A Django template tag for the current active page”

  1. Jeethu Rao Says:

    Thats a neat solution. I just worked around the same problem just this morning by writing a custom middleware (to intercept process_view), a context processor func and a decorator to decorate every view func. Your solution is a whole lot simpler and elegant.

  2. Mark Thomas Says:

    Very neat. Uncannily similar to a solution I came up with for Rails. (I’m familiar with Rails but plan to learn Django too).

  3. James Bennett Says:

    Intermediate templates and inheritance are what you want; then you don’t need any tags at all :)

  4. gnuvince Says:

    James: I don’t see how that would help out. I still need to be aware of the page I’m currently on. Hence the need for a tag.

  5. Martin Geber Says:

    Hello,

    I just found your post, here is my solution:
    http://www.martin-geber.com/weblog/2007/10/25/breadcrumbs-django-templates/

    (By the way, I that’s what James means.)

    Cheers.

  6. Common web features in Django at The Best of Marc Garcia Says:

    [...] Highlight active menu option: use {{ request.path }} to know requested URL and compare it with menu options * [more info] [...]

  7. Greg Says:

    A fix:

    ## views.py
    from django.template import RequestContext

  8. The new and improved {% active %} tag « Occasionally sane Says:

    [...] new and improved {% active %} tag A few months ago, I described a Django template tag that I wrote to identify the currently active [...]

  9. Sameer Maggon Says:

    This made some code go way in templates! Thanks

  10. drozzy Says:

    This is very nice for my main navigation.

    But what about the Side navigation that keeps track of active liknks like:
    /events/1
    /events/2

    where each links is active depending on the current event beign displayed.

    I can’t really do {% active request “/events/” + event.id %} here :-(

  11. gnuvince Says:

    drozzy: Just do {% active request “/events/\d+” %} :)

  12. Chris Says:

    Thanks, this is an intuitive and insightful intro to some fundamental functionality.

  13. Jason Broyles Says:

    Thanks for the write-up. I was looking all over for something like this and couldn’t find it in the docs.

  14. Kenneth Kufluk Says:

    Thanks for an excellent and simple example!
    My tags.py file ended up looking like this:

    from django import template
    register = template.Library()
    @register.simple_tag
    def active(request, pattern):
    import re
    if re.search(pattern, request.path):
    return ‘active’
    return ”

  15. Kenneth Kufluk Says:

    but with indentation obviously…

  16. Tom Says:

    Thanks, this was almost exactly what I was planning on writing. You not only you saved me the time but also made a more flexible version by using regular expressions.

  17. Eric Says:

    Can you tell me, where do you put the code? Does it belong in the view.py file? If it belongs in its own file, where does that file go?

    Thank you very much.

  18. django template tag for active CSS class « 110j Says:

    [...] Vincent Foley has a very good article in his blog of how to implement a navigation bar in django. I include the latest paragraph of his post here, so if you are not familiar with url patterns and django template tags please read the full article of Vincent: [...]

  19. Omer Haderi Says:

    This is a good implementation. The only problem I see is that the url pattern is coupled with the template page. To decouple it you can use url names in the urls.py and variables in the template page. I have a post on my blog on how to do the decoupling, it based on your article obviously.

    110j.wordpress.com/2009/01/25/django-template-tag-for-active-class

  20. Ben Keating Says:

    This is a great solution. I haven’t seen anything similar posted anywhere else.

    Some argue that this is a job for base_* and some CSS but I disagree. What happens when you are using a mix of normal views and flatpages? regex is at the heart of this tag which makes it so much more flexible than most other solutions.

    Thanks!

  21. Alex Tercete Says:

    Just for the record, I’m using Django 1.0.2 and it seems that ‘request’ is available by default in every template, so there’s no need to do the ‘RequestContext’ thing.

  22. Pavan Mishra Says:

    That was neat. Helped me implement simple navigation.

    Thanks

  23. Peppe bergqvist Says:

    Thanks for a good post, but I found some ways to imporve further. It would be much better to use the reverse method to get the urls that you need and instead use the path of the methods.

    #tags.py:
    from django import template
    from string import Template
    from django.core.urlresolvers import reverse
    from django.utils.translation import ugettext_lazy as _

    register = template.Library()

    @register.simple_tag
    def active(request, url, title):
    s = Template(‘$title‘)
    import re
    print reverse(url), request.path
    #if re.search(reverse(url), request.path):
    if reverse(url) == request.path:
    active = ‘active’
    else:
    active = ”
    return s.substitute(active=active, link=reverse(url), title=unicode(_(title)))

    #in the template
    {% active request “mmp.form.views.default” “Home” %}

    This tag gets the right url from urls.py, so if you change it, you just have to change it in urls.py and nowhere else. The tag also uses djangos methods for translation at the same time.

    Cheers!

  24. wastemaster Says:

    I found this extremly useful, thanx
    my variation:

    from django.template import Library
    from django.core.urlresolvers import reverse
    import re

    register = Library()

    @register.simple_tag
    def active(request, pattern, return_value):
    import re
    if re.search(pattern, request.path):
    return return_value
    return “

    @register.simple_tag
    def reverse_active(request, url, return_value):

    print reverse(url), request.path
    if reverse(url) == request.path:
    return return_value
    return “

    # template
    class=”top-menu-element{% reverse_active request “catalog.views.browse” ” top-menu-element-active” %}”

    class=”top-menu-element{% active request “/help/” ” top-menu-element-active” %}”

  25. U_ Says:

    I get the following error:
    ‘DebugParser’ object has no attribute ‘path’

  26. COULD NOT CREATE HTTPREQUEST Says:

    Electrical appliances with, camera of Motorola?Another cannon to, include wholegrain breads.And therefore are, any weight loss.Car stocks for COULD NOT CREATE HTTPREQUEST, this encrypt all and save it.On the processes, game especially in.,

  27. free karaoke software Says:

    What tamplate do you use in your blog? Very interesting articles

Leave a Reply