How to implement breadcrumbs in a Django template?

Viewed 52355

Some solutions provided on doing a Google search for "Django breadcrumbs" include using templates and block.super, basically just extending the base blocks and adding the current page to it. http://www.martin-geber.com/thought/2007/10/25/breadcrumbs-django-templates/

http://www.djangosnippets.org/snippets/1289/ - provides a template tag but I'm not sure this would work if you don't have your urls.py properly declared.

I'm wondering what's the best way? And if you have implemented breadcrumbs before how did you do it?

--- Edit --

My question was meant to be: is there a general accepted method of doing breadcrumbs in Django, but from the answers I see there is not, and there are many different solutions, I'm not sure who to award the correct answer to, as I used a variation of using the block.super method, while all the below answers would work.

I guess then this is too much of a subjective question.

15 Answers

Note: I provide the full snippet below, since djangosnippets has been finicky lately.

Cool, someone actually found my snippet :-) The use of my template tag is rather simple.

To answer your question there is no "built-in" django mechanism for dealing with breadcrumbs, but it does provide us with the next best thing: custom template tags.

Imagine you want to have breadcrumbs like so:

Services -> Programming
Services -> Consulting

Then you will probably have a few named urls: "services", and "programming", "consulting":

    (r'^services/$',
     'core.views.services',
     {},
     'services'),

    (r'^services/programming$',
     'core.views.programming',
     {},
     'programming'),

    (r'^services/consulting$',
     'core.views.consulting',
     {},
     'consulting'),

Now inside your html template (lets just look at consulting page) all you have to put is:

//consulting.html
{% load breadcrumbs %}

{% block breadcrumbs %}
{% breadcrumb_url 'Services' services %}
{% breadcrumb_url 'Consulting' consulting %}

{% endblock %}

If you want to use some kind of custom text within the breadcrumb, and don't want to link it, you can use breadcrumb tag instead.

//consulting.html
{% load breadcrumbs %}

{% block breadcrumbs %}
  {% breadcrumb_url 'Services' services %}
  {% breadcrumb_url 'Consulting' consulting %}
  {% breadcrumb 'We are great!' %}  
{% endblock %}

There are more involved situations where you might want to include an id of a particular object, which is also easy to do. This is an example that is more realistic:

{% load breadcrumbs %}

{% block breadcrumbs %}
{% breadcrumb_url 'Employees' employee_list %}
{% if employee.id %}
    {% breadcrumb_url employee.company.name company_detail employee.company.id %}
    {% breadcrumb_url employee.full_name employee_detail employee.id %}
    {% breadcrumb 'Edit Employee ' %}
{% else %}
    {% breadcrumb 'New Employee' %}
{% endif %}

{% endblock %}

DaGood breadcrumbs snippet

Provides two template tags to use in your HTML templates: breadcrumb and breadcrumb_url. The first allows creating of simple url, with the text portion and url portion. Or only unlinked text (as the last item in breadcrumb trail for example). The second, can actually take the named url with arguments! Additionally it takes a title as the first argument.

This is a templatetag file that should go into your /templatetags directory.

Just change the path of the image in the method create_crumb and you are good to go!

Don't forget to {% load breadcrumbs %} at the top of your html template!

from django import template
from django.template import loader, Node, Variable
from django.utils.encoding import smart_str, smart_unicode
from django.template.defaulttags import url
from django.template import VariableDoesNotExist

register = template.Library()

@register.tag
def breadcrumb(parser, token):
    """
    Renders the breadcrumb.
    Examples:
        {% breadcrumb "Title of breadcrumb" url_var %}
        {% breadcrumb context_var  url_var %}
        {% breadcrumb "Just the title" %}
        {% breadcrumb just_context_var %}

    Parameters:
    -First parameter is the title of the crumb,
    -Second (optional) parameter is the url variable to link to, produced by url tag, i.e.:
        {% url person_detail object.id as person_url %}
        then:
        {% breadcrumb person.name person_url %}

    @author Andriy Drozdyuk
    """
    return BreadcrumbNode(token.split_contents()[1:])


@register.tag
def breadcrumb_url(parser, token):
    """
    Same as breadcrumb
    but instead of url context variable takes in all the
    arguments URL tag takes.
        {% breadcrumb "Title of breadcrumb" person_detail person.id %}
        {% breadcrumb person.name person_detail person.id %}
    """

    bits = token.split_contents()
    if len(bits)==2:
        return breadcrumb(parser, token)

    # Extract our extra title parameter
    title = bits.pop(1)
    token.contents = ' '.join(bits)

    url_node = url(parser, token)

    return UrlBreadcrumbNode(title, url_node)


class BreadcrumbNode(Node):
    def __init__(self, vars):
        """
        First var is title, second var is url context variable
        """
        self.vars = map(Variable,vars)

    def render(self, context):
        title = self.vars[0].var

        if title.find("'")==-1 and title.find('"')==-1:
            try:
                val = self.vars[0]
                title = val.resolve(context)
            except:
                title = ''

        else:
            title=title.strip("'").strip('"')
            title=smart_unicode(title)

        url = None

        if len(self.vars)>1:
            val = self.vars[1]
            try:
                url = val.resolve(context)
            except VariableDoesNotExist:
                print 'URL does not exist', val
                url = None

        return create_crumb(title, url)


class UrlBreadcrumbNode(Node):
    def __init__(self, title, url_node):
        self.title = Variable(title)
        self.url_node = url_node

    def render(self, context):
        title = self.title.var

        if title.find("'")==-1 and title.find('"')==-1:
            try:
                val = self.title
                title = val.resolve(context)
            except:
                title = ''
        else:
            title=title.strip("'").strip('"')
            title=smart_unicode(title)

        url = self.url_node.render(context)
        return create_crumb(title, url)


def create_crumb(title, url=None):
    """
    Helper function
    """
    crumb = """<span class="breadcrumbs-arrow">""" \
            """<img src="/media/images/arrow.gif" alt="Arrow">""" \
            """</span>"""
    if url:
        crumb = "%s<a href='%s'>%s</a>" % (crumb, url, title)
    else:
        crumb = "%s&nbsp;&nbsp;%s" % (crumb, title)

    return crumb

My view functions emit the breadcrumbs as a simple list.

Some information is kept in the user's session. Indirectly, however, it comes from the URL's.

Breadcrumbs are not a simple linear list of where they've been -- that's what browser history is for. A simple list of where they've been doesn't make a good breadcrumb trail because it doesn't reflect any meaning.

For most of our view functions, the navigation is pretty fixed, and based on template/view/URL design. In our cases, there's a lot of drilling into details, and the breadcrumbs reflect that narrowing -- we have a "realm", a "list", a "parent" and a "child". They form a simple hierarchy from general to specific.

In most cases, a well-defined URL can be trivially broken into a nice trail of breadcrumbs. Indeed, that's one test for good URL design -- the URL can be interpreted as breadcrumbs and displayed meaningfully to the users.

For a few view functions, where we present information that's part of a "many-to-many" join, for example, there are two candidate parents. The URL may say one thing, but the session's context stack says another.

For that reason, our view functions have to leave context clues in the session so we can emit breadcrumbs.

Try django-breadcrumbs — a pluggable middleware that add a breadcrumbs callable/iterable in your request object.

It supports simple views, generic views and Django FlatPages app.

http://www.djangosnippets.org/snippets/1289/ - provides a template tag but i'm not sure this would work if you don't have your urls.py properly declared.

Nothing will work if you don't have your urls.py properly declared. Having said that, it doesn't look as though it imports from urls.py. In fact, it looks like to properly use that tag, you still have to pass the template some variables. Okay, that's not quite true: indirectly through the default url tag, which the breadcrumb tag calls. But as far as I can figure, it doesn't even actually call that tag; all occurrences of url are locally created variables.

But I'm no expert at parsing template tag definitions. So say somewhere else in the code it magically replicates the functionality of the url tag. The usage seems to be that you pass in arguments to a reverse lookup. Again, no matter what your project is, you urls.py should be configured so that any view can be reached with a reverse lookup. This is especially true with breadcrumbs. Think about it:

home > accounts > my account

Should accounts, ever hold an arbitrary, hardcoded url? Could "my account" ever hold an arbitrary, hardcoded url? Some way, somehow you're going to write breadcrumbs in such a way that your urls.py gets reversed. That's really only going to happen in one of two places: in your view, with a call to reverse, or in the template, with a call to a template tag that mimics the functionality of reverse. There may be reasons to prefer the former over the latter (into which the linked snippet locks you), but avoiding a logical configuration of your urls.py file is not one of them.

Try django-mptt.

Utilities for implementing Modified Preorder Tree Traversal (MPTT) with your Django Model classes and working with trees of Model instances.

You could also reduce the boiler plate required to manage breadcrumbs using django-view-breadcrumbs, by adding a crumbs property to the view.

urls.py

    urlpatterns = [
        ...
        path('posts/<slug:slug>', views.PostDetail.as_view(), name='post_detail'),
        ...
    ] 

views.py

    from django.views.generic import DetailView
    from view_breadcrumbs import DetailBreadcrumbMixin


    class PostDetail(DetailBreadcrumbMixin, DetailView):
        model = Post
        template_name = 'app/post/detail.html'

base.html

    {% load django_bootstrap_breadcrumbs %}

    {% block breadcrumbs %}
        {% render_breadcrumbs %}
    {% endblock %}

Something like this may work for your situation:

Capture the entire URL in your view and make links from it. This will require modifying your urls.py, each view that needs to have breadcrumbs, and your templates.

First you would capture the entire URL in your urls.py file

original urls.py
...
(r'^myapp/$', 'myView'),
(r'^myapp/(?P<pk>.+)/$', 'myOtherView'),
...
new urls.py
...
(r'^(?P<whole_url>myapp/)$', 'myView'),
(r'^(?P<whole_url>myapp/(?P<pk>.+)/)$', 'myOtherView'),
...

Then in your view something like:

views.py
...
def myView(request, whole_url):
    # dissect the url
    slugs = whole_url.split('/')
    # for each 'directory' in the url create a piece of bread
    breadcrumbs = []
    url = '/'
    for slug in slugs:
        if slug != '':
            url = '%s%s/' % (url, slug)
            breadcrumb = { 'slug':slug, 'url':url }
            breadcrumbs.append(breadcrumb)
    
    objects = {
        'breadcrumbs': breadcrumbs,
    }
    return render_to_response('myTemplate.html', objects)
...

Which should be pulled out into a function that gets imported into the views that need it

Then in your template print out the breadcrumbs

myTemplate.html
...
<div class="breadcrumb-nav">
    <ul>
    {% for breadcrumb in breadcrumbs %}
        <li><a href="{{ breadcrumb.url }}">{{ breadcrumb.slug }}</a></li>
    {% endfor %}
    </ul>
</div>
...

One shortcoming of doing it this way is that as it stands you can only show the 'directory' part of the url as the link text. One fix for this off the top of my head (probably not a good one) would be to keep a dictionary in the file that defines the breadcrumb function.

Anyways that's one way you could accomplish breadcrumbs, cheers :)

A generic way, to collect all callable paths of the current url could be resolved by the following code snippet:

from django.urls import resolve, Resolver404

path_items = request.path.split("/")
path_items.pop(0)
path_tmp = ""

breadcrumb_config = OrderedDict()
for path_item in path_items:
    path_tmp += "/" + path_item
    try:
        resolve(path_tmp)
        breadcrumb_config[path_item] = {'is_representative': True, 'current_path': path_tmp}
    except Resolver404:
        breadcrumb_config[path_item] = {'is_representative': False, 'current_path': path_tmp}

If the resolve function can't get a real path from any urlpattern, the Resolver404 exception will be thrown. For those items we set the is_representative flag to false. The OrderedDict breadcrumb_config holds after that the breadcrumb items with there configuration.

For bootstrap 4 breadcrumb for example, you can do something like the following in your template:

<nav aria-label="breadcrumb">
  <ol class="breadcrumb">
      {% for crumb, values in BREADCRUMB_CONFIG.items %}
        <li class="breadcrumb-item {% if forloop.last or not values.is_representative %}active{% endif %}" {% if forloop.last %}aria-current="page"{% endif %}>
            {% if values.is_representative %}
                <a href="{{values.current_path}}">
                    {{crumb}}
                </a>
            {% else %}
                {{crumb}}
            {% endif %}
        </li>
      {% endfor %}
  </ol>
</nav>

Only the links which won't raises a 404 are clickable.

I believe there is nothing simpler than that (django 3.2):

 def list(request):
        return render(request, 'list.html', {
            'crumbs' : [
                ("Today", "https://www.python.org/"),
                ("Is", "https://www.python.org/"),
                ("Sunday", "https://www.djangoproject.com/"),
            ]
        })

Breadcrumbs.html

<div class="page-title-right">
   <ol class="breadcrumb m-0">
      {% if crumbs %}
            {% for c in crumbs %}
                <li class="breadcrumb-item {{c.2}}"><a href="{{c.1}}">{{c.0}}</a></li>
            {% endfor %}
      {% endif %}
   </ol>
</div>

css:

.m-0 {
    margin: 0!important;
}
.breadcrumb {
    display: flex;
    flex-wrap: wrap;
    padding: 0 0;
    margin-bottom: 1rem;
    list-style: none;
    border-radius: .25rem;
}
dl, ol, ul {
    margin-top: 0;
    margin-bottom: 1rem;
}
ol, ul {
    padding-left: 2rem;
}

enter image description here

This answer is just the same as @Andriy Drozdyuk (link). I just want to edit something so it works in Django 3.2 (in my case) and good in bootstrap too.

for create_crumb function (Remove the ">" bug in the current code)

def create_crumb(title, url=None):
    """
    Helper function
    """
    if url:
        crumb = '<li class="breadcrumb-item"><a href="{}">{}</a></li>'.format(url, title)
    else:
        crumb = '<li class="breadcrumb-item active" aria-current="page">{}</li>'.format(title)

    return crumb

And for __init__ in BreadcrumbNode, add list() to make it subscriptable. And change smart_unicode to smart_text in render method

from django.utils.encoding import smart_text

class BreadcrumbNode(Node):
    def __init__(self, vars):
        """
        First var is title, second var is url context variable
        """
        self.vars = list(map(Variable, vars))

    def render(self, context):
        title = self.vars[0].var

        if title.find("'")==-1 and title.find('"')==-1:
            try:
                val = self.vars[0]
                title = val.resolve(context)
            except:
                title = ''

        else:
            title=title.strip("'").strip('"')
            title=smart_text(title)

And add this in base.html for the view for Bootstrap. Check the docs

<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
    <ol class="breadcrumb">
        {% block breadcrumbs %}

        {% endblock breadcrumbs %}
    </ol>
</nav>
Related