views:

4999

answers:

12

I've just done my first little webapp in django and I love it. I'm about to start on converting an old production PHP site into django and as part its template, there is a navigation bar.

In PHP, I check each nav option's URL against the current URL, in the template code and apply a CSS class if they line up. It's horrendously messy.

Is there something better for django or a good way of handling the code in the template?

To start, how would I go about getting the current URL?

+3  A: 

You could use the reverse function with the appropriate parameters to get the current url.

Corey
+5  A: 

You could apply a class or id to the body element of the page, rather than to a specific nav item.

HTML:

<body class="{{ nav_class }}">

CSS:

body.home #nav_home,
body.about #nav_about { */ Current nav styles */ }
Michael Warkentin
+18  A: 

I use template inheritance to customize navigation. For example:

base.html

<html>
    <head>...</head>
    <body>
        ...
        {% block nav %}
        <ul id="nav">
            <li>{% block nav-home %}<a href="{% url home %}">Home</a>{% endblock %}</li>
            <li>{% block nav-about %}<a href="{% url about %}">About</a>{% endblock %}</li>
            <li>{% block nav-contact %}<a href="{% url contact %}">Contact</a>{% endblock %}</li>
        </ul>
        {% endblock %}
        ...
    </body>
</html>

about.html

{% extends "base.html" %}

{% block nav-about %}<strong class="nav-active">About</strong>{% endblock %}
jpwatts
I like this idea a lot, especially for flexibility, but it comes with the less-DRY trade-off. I did start using this in a site though.
anonymous coward
I'm not enthusiastic about this approach because it's not uncommon to have multiple site sections handled by the same sub-template. So you end up putting custom vars in views and conditionals in templates, or re-arranging sub-templates so they're all unique... all just to detect the current site section. The template tag approach ends up being cleaner in the end.
shacker
+1  A: 

Thanks for your answers so far, gents. I've gone for something slightly different again..

In my template:

<li{{ link1_active }}>...link...</li>
<li{{ link2_active }}>...link...</li>
<li{{ link3_active }}>...link...</li>
<li{{ link4_active }}>...link...</li>

Once I've worked out which page I'm on in the logic (usually in urls.py), I pass class="selected" as part of the context under the right name to the template.

Eg if I'm on the link1 page, I'll append {'link1_active':' class="selected"'} to the context for the template to scoop up and inject.

It appears to work and it's fairly clean.

Edit: to keep HTML out of my controller/view, I've modified this a bit:

<li{% if link1_active %} class="selected"{% endif %}>...link...</li>
<li{% if link2_active %} class="selected"{% endif %}>...link...</li>
...

It makes the template a little less readable, but I agree, it's better to not push through raw HTML from the urls file.

Oli
You should *really* avoid handling raw HTML in your view, which is what this technique requires. Have you thought about writing a custom template tag?
Justin Voss
You're right. I've edited to stop passing through the HTML. I just pass through True now. I haven't written any template tags yet, but yes, this could be a good place to start.
Oli
+6  A: 

I do it like this:

<a class="tab {% ifequal active_tab "statistics" %}active{% endifequal %}" href="{% url Member.Statistics %}">Statistics</a>

and then all I have to do is in my view add {'active_tab': 'statistics'} to my context dictionary.

If you are using RequestContext you can get current path in your template as:

{{ request.path }}

And in your view:

from django.template import RequestContext

def my_view(request):
    # do something awesome here
    return template.render(RequestContext(request, context_dict))
muhuk
Thanks for sharing this info. I used this method, but also had a flatpage in my nav bar, so to detect that and highlight it correctly, I used {% ifequal flatpage.url '/about/' %}. I don't like the hardcoded detection of the URL, but it works for a once-off hack.
Matt Garrison
The problem with this solution is that you have hard-coded "statistics" into the code. This defeats the purpose of using the url tag to get the url of the page.
justin
+13  A: 

You do not need an if to do that, have a look at the following code:

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

## urls.py
urlpatterns += patterns(”,
    (r’/$’, view_home_method, ‘home_url_name‘),
    (r’/services/$’, view_services_method, ‘services_url_name‘),
    (r’/contact/$’, view_contact_method, ‘contact_url_name‘),
)

## base.html

{% load tags %}

{% url home_url_name as home %}
{% url services_url_name as services %}
{% url contact_url_name as contact %}

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

that's it. for implementation details have a look at:
gnuvince.wordpress.com
110j.wordpress.com

Very good! Thanks!
xaralis
The href's properties are missing django template brackets {{, }}. For example, <a class="{% active request home %}" href="home">Home</a>should be,<a class="{% active request home %}" href="{{home}}">Home</a>the tags.py file will also need a few includes.Otherwise, great solution!
bsk
+6  A: 

I liked the cleanness of 110j above so I took most of it and refactored to solve the 3 problems I had with it:

  1. the regular expression was matching the 'home' url against all others
  2. I needed multiple URLs mapped to one navigation tab, so I needed a more complex tag that takes variable amount of parameters
  3. fixed some url problems

Here it is:

## tags.py
from django import template

register = template.Library()

@register.tag
def active(parser, token):
    import re
    args = token.split_contents()
    template_tag = args[0]
    if len(args) < 2:
        raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag
    return NavSelectedNode(args[1:])

class NavSelectedNode(template.Node):
    def __init__(self, patterns):
        self.patterns = patterns
    def render(self, context):
        path = context['request'].path
        for p in self.patterns:
            pValue = template.Variable(p).resolve(context)
            if path == pValue:
                return "-active"
        return ""

## urls.py
urlpatterns += patterns(”,
    url(r’/$’, view_home_method, {}, name=‘home_url_name‘),
    url(r’/services/$’, view_services_method, {}, name=‘services_url_name‘),
    url(r’/contact/$’, view_contact_method, {}, name=‘contact_url_name‘),
    url(r’/contact/$’, view_contact2_method, {}, name=‘contact2_url_name‘),
)

## base.html

{% load tags %}

{% url home_url_name as home %}
{% url services_url_name as services %}
{% url contact_url_name as contact %}
{% url contact2_url_name as contact2 %}

<div id="navigation">
    <a class="{% active request home %}" href="home">Home</a>
    <a class="{% active request services %}" href="services">Services</a>
    <a class="{% active request contact contact2 %}" href="contact">Contact</a>
</div>
nivhab
+2  A: 

I have multiple menus on the same page that are created dynamically through a loop. The posts above relating to the context gave me a quick fix. Hope this helps somebody. (I use this in addition to the active template tag - my fix solves the dynamic issue). It seems like a silly comparison, but it works. I chose to name the variables active_something-unique and something-unique, this way it works with nested menus.

Here is a portion of the view (enough to understand what i am doing):

def project_list(request, catslug):
    "render the category detail page"
    category = get_object_or_404(Category, slug=catslug, site__id__exact=settings.SITE_ID)
    context = {
        'active_category': 
            category,
        'category': 
            category,
        'category_list': 
            Category.objects.filter(site__id__exact=settings.SITE_ID),

    }

And this is from the template:

<ul>
  {% for category in category_list %}
    <li class="tab{% ifequal active_category category %}-active{% endifequal %}">
      <a href="{{ category.get_absolute_url }}">{{ category.cat }}</a>
    </li>
  {% endfor %}
</ul>
rizumu
A: 

My solution was to write a simple context processor to set a variable based on the request path:

def navigation(request):
"""
Custom context processor to set the navigation menu pointer.
"""
nav_pointer = ''
if request.path == '/':
    nav_pointer = 'main'
elif request.path.startswith('/services/'):
    nav_pointer = 'services'
elif request.path.startswith('/other_stuff/'):
    nav_pointer = 'other_stuff'
return {'nav_pointer': nav_pointer}

(Don't forget to add your custom processor to TEMPLATE_CONTEXT_PROCESSORS in settings.py.)

Then in the base template I use an ifequal tag per link to determine whether to append the "active" class. Granted this approach is strictly limited to the flexibility of your path structure, but it works for my relatively modest deployment.

+1  A: 

I just wanted to share my minor enhancement to nivhab's post. In my application I have subnavigations and I did not want to hide them using just CSS, so I needed some sort of "if" tag to display the subnavigation for an item or not.

from django import template
register = template.Library()

@register.tag
def ifnaviactive(parser, token):
    nodelist = parser.parse(('endifnaviactive',))
    parser.delete_first_token()

    import re
    args = token.split_contents()
    template_tag = args[0]
    if len(args) < 2:
        raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag
    return NavSelectedNode(args[1:], nodelist)

class NavSelectedNode(template.Node):
    def __init__(self, patterns, nodelist):
        self.patterns = patterns
        self.nodelist = nodelist

    def render(self, context):
        path = context['request'].path
        for p in self.patterns:
            pValue = template.Variable(p).resolve(context)
            if path == pValue:
                return self.nodelist.render(context)
        return ""

You can use this basically in the same way as the active tag:

{% url product_url as product %}

{% ifnaviactive request product %}
    <ul class="subnavi">
        <li>Subnavi item for product 1</li>
        ...
    </ul>
{% endifnaviactive %}
Nino
+3  A: 

I took the code from nivhab above and removed some wierdness and made it into a clean templatetag, modified it so that /account/edit/ will still make /account/ tab active.

#tags.py
from django import template

register = template.Library()

@register.tag
def current_nav(parser, token):
    import re
    args = token.split_contents()
    template_tag = args[0]
    if len(args) < 2:
        raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag
    return NavSelectedNode(args[1])

class NavSelectedNode(template.Node):
    def __init__(self, url):
        self.url = url

    def render(self, context):
        path = context['request'].path
        pValue = template.Variable(self.url).resolve(context)
        if (pValue == '/' or pValue == '') and not (path  == '/' or path == ''):
            return ""
        if path.startswith(pValue):
            return ' class="current"'
        return ""



#template.html
{% block nav %}
{% load current_nav %}
{% url home as home_url %}
{% url signup as signup_url %}
{% url auth_login as auth_login_url %}
<ul class="container">
    <li><a href="{{ home_url }}"{% current_nav home_url %} title="Home">Home</a></li>
    <li><a href="{{ auth_login_url }}"{% current_nav auth_login_url %} title="Login">Login</a></li>
    <li><a href="{{ signup_url }}"{% current_nav signup_url %} title="Signup">Signup</a></li>
</ul>
{% endblock %}
Andreas
+3  A: 

This is just a variant of the css solution proposed by Toba above:

Include the following in your base template:

<body id="section-{% block section %}home{% endblock %}">

Then in your templates that extend the base use:

{% block section %}show{% endblock %}

You can then use css to highlight the current area based on the body tag (for example if we have a link with an id of nav-home):

#section-home a#nav-home{
 font-weight:bold;
}
dtt101