Navigation in django
Asked Answered
S

30

115

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?

Serrato answered 4/12, 2008 at 15:6 Comment(3)
I created github.com/orokusaki/django-active-menu for this - it supports nested URL structures, and relies on configuration over convention (as evil as that sounds), so you can define your site's hierarchy however you want. You just use <a href="{% url "view:name" %}" {% active_class "view:name" %}>. You can optionally use it to generate just the " active" value (by passing False as a second argument to the tag) to append to an existing class attribute, but for most nav links that example is what I use.Dozier
This question seems to be related to this one https://mcmap.net/q/189565/-how-to-render-menu-with-one-active-item-with-dryLiberticide
Maybe this grid helps: djangopackages.org/grids/g/navigationMidsummer
P
88

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 %}
Pilot answered 4/12, 2008 at 19:26 Comment(7)
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.Maurer
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.Addi
I looked at a few other solutions, and it seems like they're all a bit of a hack. This one, at least, is pretty straightforward and simple to implement/scrap.Remodel
I refactored the <ul id="nav">....</ul> to a different file, lets say tabs.html. So now base.html contained {%block nav%}{%include "tabs.html"%}{%endblock%} and then highlighting of the active tab stopped working(in about.html above). Am I missing anything ?Dwarfish
@Maddy You've got enough indirection going on that I'm not absolutely certain I'm keeping it straight in my head, but I think the answer has to do with how the include tag works. Check out the note included in the docs: docs.djangoproject.com/en/dev/ref/templates/builtins/#include In your case, by the time you're trying to override the base template in about.html, I think you've already got a rendered HTML block, rather than a Django template block waiting to be processed.Pilot
This doesn't work on the latest stable django, I get the following error, 'url' requires a non-empty first argument. The syntax changed in Django 1.5, see the docs.Hafnium
At first glance, this this seems to be an interesting approach, however if you think of multiple pages which were create based on this "base.html" template, this approach is insane. Imagine, you decide to modify, extend or remove on of the menu items, than this implies that you need to think of every page which was extended and apply the change everywhere, this is just against all paradigms, lolLiberticide
D
122

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

Damp answered 25/1, 2009 at 14:0 Comment(7)
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!Ambrosine
+1 This is more loosely coupled from applications. As a beginner I figured out tags needs it's own app, you can't just dump that into a global tags.py file. I created a new app called tags and everything went smoothly. docs.djangoproject.com/en/dev/howto/custom-template-tagsBodice
@Keyo, create a templatetags directory in your project, and add your project to installedapps. That'll also do the trick. Alternatively, like you said, create your main site as an app within your project.Phene
One thing I don't like about this is that you set the variables using the url tag. Instead, a cleaner solution may be to do the url reversal within the simple tag.Sorayasorb
Don't forget to add django.core.context_processors.request to your TEMPLATE_CONTEXT_PROCESSORS in settings.pyFrowsy
why import re is inside tag defenition and not on module level>Furbish
This is invalid for states that may be nested, e.g. mysite.com (as home) and mysite.com/blog, as the path will show as / and /blog/ (respectively) yielding a match for the former each time. If you don't use / as a landing, that may be okay otherwise I just use return 'active' if pattern == request.path else '' (I've not seen issues with this yet, but I just set up using this).Daudet
P
88

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 %}
Pilot answered 4/12, 2008 at 19:26 Comment(7)
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.Maurer
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.Addi
I looked at a few other solutions, and it seems like they're all a bit of a hack. This one, at least, is pretty straightforward and simple to implement/scrap.Remodel
I refactored the <ul id="nav">....</ul> to a different file, lets say tabs.html. So now base.html contained {%block nav%}{%include "tabs.html"%}{%endblock%} and then highlighting of the active tab stopped working(in about.html above). Am I missing anything ?Dwarfish
@Maddy You've got enough indirection going on that I'm not absolutely certain I'm keeping it straight in my head, but I think the answer has to do with how the include tag works. Check out the note included in the docs: docs.djangoproject.com/en/dev/ref/templates/builtins/#include In your case, by the time you're trying to override the base template in about.html, I think you've already got a rendered HTML block, rather than a Django template block waiting to be processed.Pilot
This doesn't work on the latest stable django, I get the following error, 'url' requires a non-empty first argument. The syntax changed in Django 1.5, see the docs.Hafnium
At first glance, this this seems to be an interesting approach, however if you think of multiple pages which were create based on this "base.html" template, this approach is insane. Imagine, you decide to modify, extend or remove on of the menu items, than this implies that you need to think of every page which was extended and apply the change everywhere, this is just against all paradigms, lolLiberticide
E
36

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):
    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" # change this if needed for other bootstrap version (compatible with 3.2)
        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>
Excellent answered 17/3, 2009 at 22:49 Comment(1)
It's maybe we best answer with Marcus one, but how it works with the "home" ? it's always active ? How to make it active only on root url call (www.toto.com/ and www.toto.com/index) ? Both answers don't result this problem...Passifloraceous
P
21

I'm the author of django-lineage which I wrote specifically to solve this question :D

I became annoyed using the (perfectly acceptable) jpwatts method in my own projects and drew inspiration from 110j's answer. Lineage looks like this:

{% load lineage %}
<div id="navigation">
    <a class="{% ancestor '/home/' %}" href="/home/">Home</a>
    <a class="{% ancestor '/services/' %}" href="/services/">Services</a>
    <a class="{% ancestor '/contact/' %}" href="/contact/">Contact</a>
</div>

ancestor is simply replaced with "active" if the argument matches the start of current page URL.

Variable arguments, and full {% url %} type reverse resolution, is also supported. I sprinkled in a few configuration options and fleshed it out a little and packaged it up for everyone to use.

If anyone is interested, read a bit more about it at:

>> github.com/marcuswhybrow/django-lineage

Permalloy answered 11/4, 2013 at 17:16 Comment(1)
hard coding paths into the template :(Magda
A
13

Since Django 1.5:

In all generic class-based views (or any class-based view inheriting from ContextMixin), the context dictionary contains a view variable that points to the View instance.

So if you are using such views, you could add something likie breadcrumbs as a class level field and use it in your templates.

Example view code:

class YourDetailView(DetailView):
     breadcrumbs = ['detail']
     (...)

In your template you could use it in this way:

<a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a>

If you want to additionally "highlight" parent navigation items, you need to extend breadcrumbs list:

class YourDetailView(DetailView):
     breadcrumbs = ['dashboard', 'list', 'detail']
     (...)

... and in your template:

<a href="/dashboard/" {% if 'dashboard' in view.breadcrumbs %}class="active"{% endif %}>Dashboard</a>
<a href="/list/" {% if 'list' in view.breadcrumbs %}class="active"{% endif %}>List</a>
<a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a>

This is easy and clean solution and works pretty well with nested navigation.

Ayers answered 26/9, 2013 at 9:8 Comment(3)
In that example, wouldn't all three navigation items be .active?Serrato
Yes, but this is typically what you want to achieve with multi-level navigation. You could of course put one item into breadcrumbs if you want. But you have right - my example is not the best one.Bahner
@Serrato improved example.Bahner
A
9

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 */ }
Arabel answered 4/12, 2008 at 19:9 Comment(0)
R
7

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))
Restrain answered 6/12, 2008 at 20:52 Comment(2)
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.Lobar
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.Ozieozkum
K
7

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.

#current_nav.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 %}
Kellen answered 25/11, 2009 at 22:59 Comment(0)
M
6

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;
}
Mohan answered 7/12, 2009 at 15:12 Comment(0)
D
3

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

Downstroke answered 4/12, 2008 at 15:31 Comment(0)
S
3

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.

Serrato answered 5/12, 2008 at 9:24 Comment(2)
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?Thickening
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.Serrato
A
3

I found the best is to use an inclusion tag:

templates/fnf/nav_item.html

<li class="nav-item">
    <a class="nav-link {% if is_active %}active{% endif %}" href="{% url url_name %}">{{ link_name }}</a>
</li>

This is just my basic bootstrap nav item I wish to render.

It gets the href value, and optionally the link_name value. is_active is calculated based on the current request.

templatetags/nav.py

from django import template

register = template.Library()


@register.inclusion_tag('fnf/nav_item.html', takes_context=True)
def nav_item(context, url_name, link_name=None):
    return {
        'url_name': url_name,
        'link_name': link_name or url_name.title(),
        'is_active': context.request.resolver_match.url_name == url_name,
    }

Then use it in a nav: templates/fnf/nav.html

{% load nav %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
        <ul class="navbar-nav mr-auto">
                {% nav_item 'dashboard' %}
            </ul>
Andersonandert answered 22/3, 2019 at 4:45 Comment(3)
Just a cursory reading, but does this not limit things to exact matches on the URL? I commonly use navigation hints like this for deep pages too. Eg the About nav item would be highlighted if you were on either /about/company-history/ or /about/what-we-do/Serrato
Yes, but is_active can be replaced and other keys added to the dictionary returned. Also, the check can be context.request.resolver_match.url_name.startswith(x) or anything else. Also, you can have code before the return statement in order to establish the dict values. Also, you can use different templates, ie one for top_level_nav.html with different logic, etc.Andersonandert
Clean and simple solution... nice!Genous
H
2

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>
Haste answered 21/4, 2009 at 3:18 Comment(0)
T
2

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.

Teen answered 22/5, 2009 at 21:4 Comment(1)
I think it really makes sense to have these in the global context, so you can reference the site section in various ways (using different templates for different site sections for example. +1.Addi
H
2

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 %}
Hughie answered 9/9, 2009 at 17:51 Comment(0)
P
2

Just another ehnancement of the original solution.

This accept multiple patterns and which is best also unnamed patterns written as relative URL wrapped in '"', like following:

{% url admin:clients_client_changelist as clients %}
{% url admin:clients_town_changelist as towns %}
{% url admin:clients_district_changelist as districts %}

<li class="{% active "/" %}"><a href="/">Home</a></li>
<li class="{% active clients %}"><a href="{{ clients }}">Clients</a></li>
{% if request.user.is_superuser %}
<li class="{% active towns districts %}">
    <a href="#">Settings</a>
    <ul>
        <li><a href="{{ towns }}">Towns</a></li>
        <li><a href="{{ districts }}">Districts</a></li>
    </ul>
</li>
{% endif %}

Tag goes like this:

from django import template

register = template.Library()

@register.tag
def active(parser, token):
    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, urls):
        self.urls = urls

    def render(self, context):
        path = context['request'].path

        for url in self.urls:
            if '"' not in url:
                cpath = template.Variable(url).resolve(context)
            else:
                cpath = url.strip('"')

            if (cpath == '/' or cpath == '') and not (path == '/' or path == ''):
                return ""
            if path.startswith(cpath):
                return 'active'
        return ""
Pheon answered 12/2, 2012 at 14:59 Comment(0)
P
2

I used jquery to highlight my navbars. This solution simply adds the css class "active" to the item which fits the css selector.

<script type="text/javascript" src="/static/js/jquery.js"></script>
<script>
    $(document).ready(function(){
        var path = location.pathname;
        $('ul.navbar a.nav[href$="' + path + '"]').addClass("active");
    });
</script>
Psychognosis answered 11/11, 2012 at 21:2 Comment(0)
B
2

A little enhancement over @tback's answer, without any %if% tags:

# navigation.py
from django import template
from django.core.urlresolvers import resolve

register = template.Library()

@register.filter(name="activate_if_active", is_safe=True)
def activate_if_active(request, urlname):
  if resolve(request.get_full_path()).url_name == urlname:
    return "active"
  return ''

Use it in your template like that:

{% load navigation %}
<li class="{{ request|activate_if_active:'url_name' }}">
  <a href="{% url 'url_name' %}">My View</a>
</li>

And include "django.core.context_processors.request" in your TEMPLATE_CONTEXT_PROCESSORS setting.

Bipartisan answered 15/2, 2015 at 12:16 Comment(0)
L
2

Inspired by this solution, I started to use this approach:

**Placed in templates as base.html**

{% block tab_menu %}
<ul class="tab-menu">
  <li class="{% if active_tab == 'tab1' %} active{% endif %}"><a href="#">Tab 1</a></li>
  <li class="{% if active_tab == 'tab2' %} active{% endif %}"><a href="#">Tab 2</a></li>
  <li class="{% if active_tab == 'tab3' %} active{% endif %}"><a href="#">Tab 3</a></li>
</ul>
{% endblock tab_menu %}

**Placed in your page template**

{% extends "base.html" %}

{% block tab_menu %}
  {% with active_tab="tab1" %} {{ block.super }} {% endwith %}
{% endblock tab_menu %}
Liberticide answered 3/1, 2018 at 13:19 Comment(0)
C
1

Slightly modifying Andreas' answer, it looks like you can pass in the name of the route from urls.py to the template tag. In my example my_tasks, and then in the template tag function use the reverse function to work out what the URL should be, then you can match that against the URL in the request object (available in the template context)

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

register = template.Library()

@register.tag
def active(parser, token):
    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, name):
        self.name = name

    def render(self, context):

        if context['request'].path == reverse(self.name[1]):
            return 'active'
        else:
            return ''

urls.py

url(r'^tasks/my', my_tasks, name = 'my_tasks' ),

template.html

<li class="{% active request all_tasks %}"><a href="{% url all_tasks %}">Everyone</a></li>
Coalesce answered 2/6, 2012 at 10:43 Comment(1)
Maybe, a more straightforward approach: turnkeylinux.org/blog/django-navbarMurraymurre
M
1

I know I'm late to the party. I didn't like any of the popular solutions though:

The block method seems wrong: I think the navigation should be self contained.

The template_tag method seems wrong: I don't like that I have to get the url from the url-tag first. Also, I think the css-class should be defined in the template, not the tag.

I therefore wrote a filter that doesn't have the drawbacks I described above. It returns True if a url is active and can therefore be used with {% if %}:

{% load navigation %}
<li{% if request|active:"home" %} class="active"{% endif %}><a href="{% url "home" %}">Home</a></li>

The code:

@register.filter(name="active")
def active(request, url_name):
    return resolve(request.path_info).url_name == url_name

Just make sure to use RequestContext on pages with navigation or to enable the request context_processor in your settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
    ...
    'django.core.context_processors.request',
)
Make answered 23/11, 2013 at 10:52 Comment(0)
P
1

I've seen jpwatts', 110j's, nivhab's & Marcus Whybrow's answers, but they all seem to lack in something: what about the root path ? Why it's always active ?

So I've made an other way, easier, which make the "controller" decides by itself and I think it resolve most of the big problems.

Here is my custom tag:

## myapp_tags.py

@register.simple_tag
def nav_css_class(page_class):
    if not page_class:
        return ""
    else:
        return page_class

Then, the "controller" declares CSS classes needed (in fact, the most important is it declares its presence to the template)

## views.py

def ping(request):
    context={}
    context["nav_ping"] = "active"
    return render(request, 'myapp/ping.html',context)

And finally, I render it in my navigation bar:

<!-- sidebar.html -->

{% load myapp_tags %}
...

<a class="{% nav_css_class nav_home %}" href="{% url 'index' %}">
    Accueil
</a>
<a class="{% nav_css_class nav_candidats %}" href="{% url 'candidats' %}">
    Candidats
</a>
<a class="{% nav_css_class nav_ping %}" href="{% url 'ping' %}">
    Ping
</a>
<a class="{% nav_css_class nav_stat %}" href="{% url 'statistiques' %}">
    Statistiques
</a>
...

So each page has its own nav_css_class value to set, and if it's set, the template renders active: no need of request in template context, no URL parcing and no more problems about multi-URL pages or root page.

Passifloraceous answered 15/6, 2015 at 17:25 Comment(0)
C
0

Here's my go at it. I ended up implementing a class in my views that contains my navigation structure (flat with some metadata). I then inject this to the template and render it out.

My solution deals with i18n. It probably should be abstracted out a bit more but I haven't really bothered with that really.

views.py:

from django.utils.translation import get_language, ugettext as _


class Navi(list):
    items = (_('Events'), _('Users'), )

    def __init__(self, cur_path):
        lang = get_language()
        first_part = '/' + cur_path.lstrip('/').split('/')[0]

        def set_status(n):
            if n['url'] == first_part:
                n['status'] == 'active'

        for i in self.items:
            o = {'name': i, 'url': '/' + slugify(i)}
            set_status(o)
            self.append(o)

# remember to attach Navi() to your template context!
# ie. 'navi': Navi(request.path)

I defined the template logic using includes like this. Base template:

{% include "includes/navigation.html" with items=navi %}

Actual include (includes/navigation.html):

 <ul class="nav">
     {% for item in items %}
         <li class="{{ item.status }}">
             <a href="{{ item.url }}">{{ item.name }}</a>
         </li>
     {% endfor %}
 </ul>

Hopefully someone will find this useful! I guess it would be pretty easy to extend that idea to support nested hierarchies etc.

Caspar answered 14/3, 2012 at 7:37 Comment(0)
S
0

Create an include template "intranet/nav_item.html":

{% load url from future %}

{% url view as view_url %}
<li class="nav-item{% ifequal view_url request.path %} current{% endifequal %}">
    <a href="{{ view_url }}">{{ title }}</a>
</li>

And include it in the nav element:

<ul>
    {% include "intranet/nav_item.html" with view='intranet.views.home' title='Home' %}
    {% include "intranet/nav_item.html" with view='crm.views.clients' title='Clients' %}
</ul>

And you need to add this to settings:

from django.conf import global_settings
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
    'django.core.context_processors.request',
)
Steady answered 1/10, 2012 at 12:50 Comment(0)
L
0

here is pretty simple solution, https://github.com/hellysmile/django-activeurl

Legislatorial answered 21/2, 2013 at 5:33 Comment(1)
Please note that you should post the useful points of an answer here, on this site, or your post risks being deleted as "Not an Answer". You may still include the link if you wish, but only as a 'reference'. The answer should stand on its own without needing the link.Disjunction
E
0

from this SO Question

{% url 'some_urlpattern_name' as url %}
<a href="{{url}}"{% if request.path == url %} class="active"{% endif %}>Link</a>

Repeat as necessary for each link.

Extenuatory answered 28/10, 2013 at 13:48 Comment(2)
This only works for direct matches. Most navigation systems mark the nav item active if a descendant page is active too. Ie if /blog/posts/2021/04/12 was the url the /blog/ nav item would be active.Serrato
@Serrato yes it won't work some times. for example in stackoverflow navigation ie Questions,Tags,Users,Badges,Unanswered,Ask Question. it won't work for Questions, but for all other navs it will work fine.Extenuatory
W
0

I also used jQuery to highlight it and find it more elegant than cluttering the template with non-semantic Django template tags.

The code below works with nested dropdowns in bootstrap 3 (highlights both the parent, and the child <li> element.

// DOM Ready
$(function() {
    // Highlight current page in nav bar
    $('.nav, .navbar-nav li').each(function() {
        // Count the number of links to the current page in the <li>
        var matched_links = $(this).find('a[href]').filter(function() {
            return $(this).attr('href') == window.location.pathname; 
        }).length;
        // If there's at least one, mark the <li> as active
        if (matched_links)
            $(this).addClass('active');
    });
});

It's also quite easy to add a click event to return false (or change the href attribute to #) for the current page, without changing the template/html markup:

        var matched_links = $(this).find('a[href]').filter(function() {
            var matched = $(this).attr('href') == window.location.pathname;
            if (matched)
                $(this).click(function() { return false; });
            return matched;
        }).length;
Willams answered 25/11, 2013 at 22:42 Comment(0)
E
0

I use a combination of this mixin for class based views:

class SetActiveViewMixin(object):
    def get_context_data(self, **kwargs):
        context = super(SetActiveViewMixin, self).get_context_data(**kwargs)
        context['active_nav_menu'] = {
            self.request.resolver_match.view_name: ' class="pure-menu-selected"'
        }
        return context

with this in the template:

<ul>
    <li{{active_nav_menu.node_explorer }}><a href="{% url 'node_explorer' '' %}">Explore</a></li>
    <li{{active_nav_menu.node_create }}><a href="{% url 'node_create' path %}">Create</a></li>
    <li{{active_nav_menu.node_edit }}><a href="{% url 'node_edit' path %}">Edit</a></li>
    <li{{active_nav_menu.node_delete }}><a href="{% url 'node_delete' path %}">Delete</a></li>
</ul>
Ebbie answered 10/5, 2014 at 18:21 Comment(0)
M
0

Mine is a bit similar to another JS approach submitted previously.. just without jQuery...

Say we have in base.html the following:

<div class="pure-u-1 pure-menu pure-menu-open pure-menu-horizontal header" >
    <ul class="">
        <li id="home"><a href="{% url 'article:index' %}">Home</a></li>
        <li id="news"><a href="{% url 'article:index' %}">News</a></li>
        <li id="analysis"><a href="{% url 'article:index' %}">Analysis</a></li>
        <li id="opinion"><a href="{% url 'article:index' %}">Opinion</a></li>
        <li id="data"><a href="{% url 'article:index' %}">Data</a></li>
        <li id="events"><a href="{% url 'article:index' %}">Events</a></li>
        <li id="forum"><a href="{% url 'article:index' %}">Forum</a></li>
        <li id="subscribe"><a href="{% url 'article:index' %}">Subscribe</a></li>
    </ul>
    <script type="text/javascript">
        (function(){
            loc=/\w+/.exec(window.location.pathname)[0];
            el=document.getElementById(loc).className='pure-menu-selected';         
        })();   
    </script>
</div>

I just made my hierarchy to follow a certain URL pattern... after the host address... i have my main category, eg, home, news, analysis, etc. and the regex just pulls the first word out of the location

Mernamero answered 14/11, 2014 at 15:15 Comment(0)
T
0

**

Just add url and name in jinja format like this

**

 <ul class="nav navbar-nav">
        <li>
           <a href="{% url 'index' %}">Cities</a>
        </li>
        <li>
           <a href="{% url 'all_cafes' %}">Cafes</a>
        </li>
    </ul>
Terpstra answered 4/10, 2020 at 9:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.