In this tutorial, you will learn how to implement categories and breadcrumb in your Django project. Categories may be related to each other as child-parent, so categories are hierarchical data and to store and access them efficiently mptt i.e modified preorder tree traversal should be used in which the number of database queries made is minimum. There are some referral links provided at the end of the post to understand how hierarchical data are stored efficiently.

The previous post on how to implement categories in Django is well illustrated about how things work, but that approach is not efficient because number of database queries are made, so for small website like a blog, that approach may be acceptable, but for a website which will be having good amount of nested categories, should use mptt that I am going to explain.


pip3 install django-mptt


And add 'mptt' in INSTALLED_APPS in

We will be implementing categories and breadcrumb for a blog website. So consider following models for my_posts app in Django project.

from mptt.models import MPTTModel, TreeForeignKey

class Post(models.Model):

    title = models.CharField(max_length=120)
    category = models.ForeignKey('Category', null=True, blank=True)
    content = HTMLField('Content')
    slug = models.SlugField(unique=True)
    timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
    def __str__(self):
        return self.title

    def get_slug_list_for_categories(self):
            ancestors = self.category.get_ancestors(include_self=True)
            ancestors = []
            ancestors = [ i.slug for i in ancestors]
        slugs = []
        for i in range(len(ancestors)):

        return slugs


And category model be :

class Category(MPTTModel):
    name = models.CharField(max_length=50, unique=True)
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
    slug = models.SlugField()

    class MPTTMeta:
        order_insertion_by = ['name']

    class Meta:
        unique_together = (('parent', 'slug',))
        verbose_name_plural = 'categories'

    def __str__(self):


Now run migration commands to create database tables. To show categories in admin panel add following to project_name/my_posts/

from mptt.admin import MPTTModelAdmin,PostAdmin) , MPTTModelAdmin) 


Now run the following command to collect static files from mptt, provided that you've already defined STATIC_ROOT in

python3 collectstatic


Doing these steps, you can now add some nested categories by selecting categories > ADD  CATEGORY of my_posts app in Django Admin panel, there is an image below to illustrate how to create a category object.


adding category


After adding some categories, we have following category tree


category tree


If you want to change the indentation pixels i.e the space before each category object so that the relation between them can be determined easily, add the following line of code in

  MPTT_ADMIN_LEVEL_INDENT = 20   //you can replace 20 with some other number
                                 //to change indentation space


The category tree showed above is not collapsable and expandable, you can tweak this by changing my_posts/ as

from mptt.admin import DraggableMPTTAdmin,PostAdmin), DraggableMPTTAdmin )


category tree with expand collpase button


Now go to posts > ADD POST then you will find category drop-down to associate a category to that post, this field is not a required field so you may leave it untouched. 


post creation with category

Next, we have to add URL pattern for the category, so go to and add the following line to the urlpatterns list.

url(r'^category/(?P<hierarchy>.+)/$', views.show_category, name='category'),

Now in add the following function.

def show_category(request,hierarchy= None):
    category_slug = hierarchy.split('/')
    parent = None
    root = Category.objects.all()

    for slug in category_slug[:-1]:
        parent = root.get(parent=parent, slug = slug)

        instance = Category.objects.get(parent=parent,slug=category_slug[-1])
        instance = get_object_or_404(Post, slug = category_slug[-1])
        return render(request, "postDetail.html", {'instance':instance})
        return render(request, 'categories.html', {'instance':instance})



Add following code in categories.html.

{% extends 'base.html' %}
{% load static  %}

{% block head_title %} {{ }} {% endblock %}

{% block content %}
<div class="text-center"><h2>{{}}</h2></div>

{% if  instance.children.all %}
    <h4>Sub Categories</h4>
    {% for i in instance.children.all %}
        <a href="{{ i.slug }}"> {{ }} </a><br>
    {% endfor %}

{% endif %}

{% if  instance.post_set.all %}
 {% for i in instance.post_set.all %}
  <div class="row small-up-1 medium-up-3" >

   <div class="column">
    <a href="{{ i.slug }}">
      <div class="card" style="width: 300px; border-color: black">
         <div class="card-divider">
           <strong>{{ i.title | truncatechars:30}}</strong>

         <div class="card-section">
          <small>{{ i.publish}} </small>
          <p>{{ i.content | safe | truncatechars_html:120 }}</p>
{% endfor %}

{% endif %}
{% endblock %}


The CSS framework used is Zurb's Foundation 6 you may use bootstrap as well.


Next,  add following code snippet inside the postDetail.html template to implement breadcrumbs in post detail page ( you should add it above post's title ).

{% load namify %}

<ul class="breadcrumbs">
    <li><a href="/">Home</a></li>
    {% for i in instance.get_slug_list_for_categories %}
       <li><a href="/category/{{ i }}">{{ i | get_name }}</a></li>
    {% endfor %}



Note that filter get_name is used to extract the name of categories from the slug.

To implement this filter create a directory templatetags at the same level as, etc in my_post app and create a empty python file to ensure the directory is treated as a Python package and create another python file, add following code in it.


from django import template

register = template.Library()

def get_name(value):
    spam = value.split('/')[-1]         # assume value be /python/web-scrapping
                                        # spam would be 'web-scrapping'
    spam = ' '.join(spam.split('-'))    # now spam would be 'web scrapping'
    return spam


Finally, we have following post detail page for recently created post


post detail page with breadcrumb


The breadcrumb above the title of the post is useful in navigation, for example, if you click web scrapping then it will take you to the catogories.html page where you can navigate posts in web scrapping as well as its sub categories as in the following image.

category page



Modified Preorder Tree Traversal is not just limited to categories but can also be used for efficiently accessing and storing hierarchical data. Although insert and move operations are not as efficient if the tree is changing frequently. However, overall mptt is currently the best solution for database queries overhead. If you have any query, ask me in comments below and do share the post if you like it.





Happy Coding :)