Django class based views with url issue - django

I'm trying to get to grips with class based views.
I have urls.py as follows:
urlpatterns = patterns('homework.views',
(r'^index/$', 'index'),
url(r'^(?P<sub_slug>\w+)/$', NavListView.as_view(), name='nav'),
url(r'^(?P<sub_slug>\w+)/(?P<class_grp_slug>\w+)/$', SubNavListView.as_view(), name='subnav'),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),)
I have my views.py:
# Subject navigation
class NavListView(ListView):
template_name = 'templates/home.html'
def get_queryset(self):
self.subject = Subject.objects.all()
return self.subject
def get_context_data(self, **kwargs):
context = super(NavListView, self).get_context_data(**kwargs)
context['subjects'] = self.subject
return context
# Class group navigation
class SubNavListView(NavListView):
def get_queryset(self):
self.group = Group.objects.filter(subject__name__iexact=self.kwargs['sub_slug'])
return self.group
def get_context_data(self, **kwargs):
context = super(NavListView, self).get_context_data(**kwargs)
context['groups'] = self.group
return context
In my 'templates/home.html' I have:
{% extends 'templates/base.html' %}
{% load url from future %}
{% block nav-menu-items %}
<ul class="nav">
{% for sub in subjects %}
<li class="">{{ sub }}</li>
{% endfor %}
<li class="active">Add Subject</li>
</ul>
{% endblock nav-menu-items %}
{% block class_groups_nav %}
<div class="tabbable">
<ul class="nav nav-tabs">
{% for group in groups %}
<li>
<a data-toggle="tab" href="{% url 'subnav' sub_slug class_grp_slug %}">{{ group }}</a>
</li>
{% endfor %}
<li>Add</li>
</ul>
{% endblock class_groups_nav %}
I'm trying to achieve a 'nav' of subjects, then a 'subnav' below showing a tab for each class group for the subject selected in the navigation above.
I've tried different ways of doing this such as making Subject.objects.all() available as context processors. I have also attempted to subclass NavListView so I can inherit the previous context, making them available in SubNavListView.
At the moment, I'm getting a NoReverseMatch error where the url named 'nav' is not passing the sub_slug and so I can't use it in the url in the template.
Any thoughts on getting this working?
Many thanks,

Assuming your Subject model has field named slug in it, you need to update your code to
<li class="">{{ sub }}</li>
ie. pass an appropriate parameter to {%url ... %}. Change sub.slug to whatever field name you want to refer to.
If you want to, you can also do {% url 'nav' sub_slug=sub.slug %}.
You are trying to pass sub_slug, but which is not defined in the template context, and result in empty string. So nav url will not get any parameter.
I see similar problem in your other {%url ...%} tags in the template.

Related

Trouble paginating results from a 3rd party API in Django

I'm making a portfolio project where I'm using the Google Books API to do a books search, and the Django Paginator class to paginate the results. I've been able to get search results using a CBV FormView and a GET request, but I can't seem to figure out how to get pagination working for the API response.
The solution I can think of is to append &page=1 to the url of the first search, then pull that param on every GET request and use that to paginate. The problem is, I can't figure out how to append that param on the first search, and I don't know how I'd increment that param value when clicking the pagination buttons.
Here's what I've got now:
Form:
class SearchForm(forms.Form):
search = forms.CharField(label='Search:', max_length=150)
View:
class HomeView(FormView):
template_name = "home.html"
form_class = SearchForm
pageIndex = 0
def get(self, request, *args, **kwargs):
# get submitted results in view and display them on results page. This will be swapped out for an AJAX call eventually
if "search" in request.GET:
# getting search from URL params
search = request.GET["search"]
kwargs["search"] = search
context = super().get_context_data(**kwargs)
# Rest API request
response = requests.get(
f'https://www.googleapis.com/books/v1/volumes?q={search}&startIndex={self.pageIndex}&key={env("BOOKS_API_KEY")}'
)
response = response.json()
items = response.get("items")
# pagination...needs work
paginator = Paginator(items, 2)
page_obj = paginator.get_page(1)
context["results"] = page_obj
return self.render_to_response(context)
else:
return self.render_to_response(self.get_context_data())
Template:
{% extends "base.html" %}
{% block content %}
<form action="/">
{{ form }}
<input type="submit" value="Submit">
</form>
<h1>Books</h1>
<ul>
{% for result in results %}
<li>{{ result.volumeInfo.title }} : {{result.volumeInfo.authors.0}}</li>
{% empty %}
<li>Search to see results</li>
{% endfor %}
</ul>
{% if results %}
<div class="pagination">
<span class="step-links">
{% if results.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ results.number }} of {{ results.paginator.num_pages }}
</span>
{% if results.has_next %}
next
last »
{% endif %}
</span>
</div>
{% endif %}
{% endblock content %}
I also looked at Django REST Framework for this, but the Google Books API response doesn't contain any info on next page, previous page, etc. I've done this kind of pagination in React and it's not difficult, I'm just having trouble adjusting my mental model for how to do this to Django. If anyone could offer some advice on how to make this work, I'd be very grateful.

Check if text exists in Django template context variable

This may not be the best way of doing this (open to suggestions). But I want to display a button on my home page depending on the value of a Boolean in the custom user model.
I am passing the value of this boolean via context in the view. But I can't seem to get the template logic to work.
Models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
isAdmin = models.BooleanField(default = False,)
#more models...
views.py
from django.views.generic import TemplateView
from django.contrib.auth import get_user_model
from accounts.models import CustomUser
class HomePageView(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated:
adminStatus = CustomUser.objects.get(id=self.request.user.id)
print(adminStatus.isAdmin)
context['adminStatus'] = str(adminStatus.isAdmin)
return context
home page template.html
{% extends 'base.html' %}
{% block body %}
{% if user.is_authenticated %}
<h4>Hi {{ user.username }}!</h4>
<a class="btn btn-primary btn-lg" href="{% url 'dashboard' %}" role="button"> Go to Dashboard</a>
{% else %}
<p>You are not logged in</p>
login
</div>
{% if adminStatus == "True" %}
<h1>test</h1>
<div class = "adminPanel">
<a class="btn btn-primary btn-lg" href="{% url 'newEquipment' %}" role="button"> add new equipment</a>
</div>
{% endif %}
{% endif %}
{% endblock %}
I can't see the "newEquipment" button even though the adminStatus context is equal to "True", as verified by the print() command.
I have a feeling my template logic is not correct. I also tried:
{% if adminStatus contains "True" %}
In the view, context['adminStatus'] is defined only when the user is logged in. Meanwhile in the template, you are checking for adminStatus when the user is not logged in.
First the return context statement needs to be un-indented once, so that context (with or without adminStatus) is available regardless:
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated:
adminStatus = CustomUser.objects.get(id=self.request.user.id)
context['adminStatus'] = adminStatus.isAdmin
return context
Next, yes you probably need to fix your template logic. Assuming you want to check for adminStatus only if the user is logged in, it should look like:
{% if user.is_authenticated %}
<h4>Hi {{ user.username }}!</h4>
...
{% if adminStatus %}
<h1>test</h1>
...
{% endif %}
{% else %}
<p>You are not logged in</p>
...
{% endif %}
Original answer:
In the view, you likely don't have to stringify adminStatus.isAdmin.
context['adminStatus'] = adminStatus.isAdmin
If passed to the context as a boolean, you should be able to use this expression in the template:
{% if adminStatus %}

Multiple models generic DetailView to template

I have 2 models and I got the IndexView working properly using the get_context_data method. However my DetailView using the same technique is not working. How do I simply get 2 models into the DetailView?
views.py
from .models import CharacterSeries, CharacterUniverse
class IndexView(generic.ListView):
template_name = 'character/index.html'
context_object_name = 'character_series_list'
def get_queryset(self):
return CharacterSeries.objects.order_by('name')
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['character_universe_list'] = CharacterUniverse.objects.order_by('name')
return context
class DetailView(generic.DetailView):
model = CharacterSeries
template_name = 'character/detail.html'
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context['character_universe_list'] = CharacterUniverse.objects.all()
return context
I am missing something; I need to get CharacterUniverse into the DetailView. I have tried using this page's information to no avail.
Thanks all.
UPDATE:
detail.html
<ul>
{% for series in characterseries.character_set.all %}
<li>{{ series.name }}</li>
{% endfor %}
</ul>
<ul>
{% for universe in characteruniverse.character_set.all %}
<li>{{ universe.name }}</li>
{% endfor %}
</ul>
index.html
{% load staticfiles %}
<link rel ="stylesheet" type="text/css" href="{% static 'character/style.css' %}" />
<h1>Character Series</h1>
<ul>
{% for character_series in character_series_list %} {# for MODEL in .. #}
<li>{{ character_series.name }}</li>
{% endfor %}
</ul>
<h1>Character Universe</h1>
<ul>
{% for character_universe in character_universe_list %} {# for MODEL in .. #}
<li>{{ character_universe.name }}</li>
{% endfor %}
</ul>
An example from the official documentation: This is not possible in my case?
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
Perhaps this is the final solution? This is not working though.. Naming wrong?
ALL THE UPDATES
views.py
from django.shortcuts import get_object_or_404, render
from django.views import generic
from django.views.generic import DetailView
from .models import CharacterSeries, CharacterUniverse
class IndexView(generic.ListView):
template_name = 'character/index.html'
context_object_name = 'character_series_list'
def get_queryset(self):
return CharacterSeries.objects.order_by('name')
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['character_universe_list'] = CharacterUniverse.objects.order_by('name')
return context
class SeriesDetail(DetailView):
model = CharacterSeries
template_name = 'character/series_detail.html'
class UniverseDetail(DetailView):
model = CharacterUniverse
template_name = 'character/universe_detail.html'
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.SeriesDetail.as_view(), name='series_detail'),
url(r'^(?P<pk>[0-9]+)/$', views.UniverseDetail.as_view(), name='universe_detail'),
]
index.html
{% load staticfiles %}
<link rel ="stylesheet" type="text/css" href="{% static 'character/style.css' %}" />
<h1>Character Series</h1>
<ul>
{% for character_series in character_series_list %}
<li>{{ character_series.name }}</li>
{% endfor %}
</ul>
<h1>Character Universe</h1>
<ul>
{% for character_universe in character_universe_list %}
<li>{{ character_universe.name }}</li>
{% endfor %}
</ul>
series_detail.html
<ul>
{% for series in characterseries.character_set.all %}
<li>{{ series.name }}</li>
{% endfor %}
</ul>
universe_detail.html
<ul>
{% for universe in characteruniverse.character_set.all %}
<li>{{ universe.name }}</li>
{% endfor %}
</ul>
Explanation of why you can't use one view for both models
A DetailView is meant to display details about an object from one model. It's fine to include extra context, but the view isn't designed to handle two possible models.
The example from the docs is showing the details for one publisher, and displaying all the books at the same time.
Your DetailView lets you show the details for one CharacterSeries, and display all of the CharacterUniverse at the same time.
However, you cannot use that same view to display details for one CharacterUniverse. You need a different view to display details for one CharacterUniverse
Solution
Therefore, you need two different detail views, one for each model.
You need a distinct url for each view. Otherwise, the request will always match the first regex (in this case series_detail. The following would work.
url(r'^series/(?P<pk>[0-9]+)/$', views.SeriesDetail.as_view(), name='series_detail'),
url(r'^universe/(?P<pk>[0-9]+)/$', views.UniverseDetail.as_view(), name='universe_detail'),

On a Django site, how to show correct section navigation

I'm re-working a Django site and it's divided into sections. Each section has a separate set of navigation. I'm struggling with how best to highlight the correct one.
Initially I started setting a context variable in the view and using that to pick the correct subnav. eg:
# views.py
class HomeView(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
context['subnav'] = 'news'
return context
# templates/base.html
{% if subnav %}
<ul>
{% if subnav == 'news' %}
<li>Link 1</li>
<li>Link 2</li>
{% elif subnav == 'sport' %}
<!-- etc -->
{% endif %}
</ul>
{% endif %}
That works fine... but only if every page has a view. All the Flatpages don't, for example.
I'm going round and round with context processors and template tags and nothing seems satisfactory. Any ideas?
(One obvious way would be to put each bit of subnav HTML in separate templates and extend from those for separate pages... but I already use template inheritance for different sorts of layouts, and adding the dimension of section would complicate matters hugely.)
I've worked out what seems like a reasonable solution.
I made a custom assignment tag that sets the name of the subnavigation for the current page:
# templatetags/nav_tags.py
from django import template
register = template.Library()
#register.assignment_tag
def get_subnav(request):
# The names on the right are the names of URLs:
subnavs = (
('news', ('home', 'news_archive', 'news_month_archive')),
('sport', ('sport_home', 'sport_archive', 'sport_month_archive')),
# etc
)
url_name = request.resolver_match.url_name
for subnav, names in subnavs:
if url_name in names:
return subnav
return False
Notes:
This assumes you're using named URLs in your urls.py.
request.resolver_match.url_name is only available since Django 1.5.
Then in the template I can do:
{% load nav_tags %}
{% get_subnav request as subnav %}
{% if subnav %}
<ul>
{% if subnav == 'news' %}
<li>Link 1</li>
<li>Link 2</li>
{% elif subnav == 'sport' %}
<!-- etc -->
{% endif %}
</ul>
{% endif %}
This has the benefit of reducing the amount of "if request.path=this or request.path=that or request.path=..." in the template itself. And the benefit of not relying on actual URL paths anywhere, which always seems fragile to me -- it's what named URLs are for.
I also made a context processor that puts url_name in the template context, as I find it useful to have around, and stops me relying on URL paths in other places.

Navigation in django

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?
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
I use template inheritance to customize navigation. For example:
base.html
<html>
<head>...</head>
<body>
...
{% block nav %}
<ul id="nav">
<li>{% block nav-home %}Home{% endblock %}</li>
<li>{% block nav-about %}About{% endblock %}</li>
<li>{% block nav-contact %}Contact{% endblock %}</li>
</ul>
{% endblock %}
...
</body>
</html>
about.html
{% extends "base.html" %}
{% block nav-about %}<strong class="nav-active">About</strong>{% endblock %}
I liked the cleanness of 110j above so I took most of it and refactored to solve the 3 problems I had with it:
the regular expression was
matching the 'home' url against all
others
I needed multiple URLs
mapped to one navigation tab, so I
needed a more complex tag that takes
variable amount of parameters
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>
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
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.
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 */ }
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))
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>Home</li>
<li>Login</li>
<li>Signup</li>
</ul>
{% endblock %}
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;
}
You could use the reverse function with the appropriate parameters to get the current url.
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.
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>
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 %}">
{{ category.cat }}
</li>
{% endfor %}
</ul>
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.
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 %}
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 "/" %}">Home</li>
<li class="{% active clients %}">Clients</li>
{% if request.user.is_superuser %}
<li class="{% active towns districts %}">
Settings
<ul>
<li>Towns</li>
<li>Districts</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 ""
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>
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' }}">
My View
</li>
And include "django.core.context_processors.request" in your TEMPLATE_CONTEXT_PROCESSORS setting.
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 %}">Tab 1</li>
<li class="{% if active_tab == 'tab2' %} active{% endif %}">Tab 2</li>
<li class="{% if active_tab == 'tab3' %} active{% endif %}">Tab 3</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 %}
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 %}">Everyone</li>
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 %}>Home</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',
)
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.
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 }}">
{{ item.name }}
</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.
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 %}">
{{ title }}
</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',
)
here is pretty simple solution, https://github.com/hellysmile/django-activeurl
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.
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;
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 }}>Explore</li>
<li{{active_nav_menu.node_create }}>Create</li>
<li{{active_nav_menu.node_edit }}>Edit</li>
<li{{active_nav_menu.node_delete }}>Delete</li>
</ul>
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">Home</li>
<li id="news">News</li>
<li id="analysis">Analysis</li>
<li id="opinion">Opinion</li>
<li id="data">Data</li>
<li id="events">Events</li>
<li id="forum">Forum</li>
<li id="subscribe">Subscribe</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
**
Just add url and name in jinja format like this
**
<ul class="nav navbar-nav">
<li>
Cities
</li>
<li>
Cafes
</li>
</ul>