Tutorial

The Problem

You’ve created a Django project, and you need to manage some hierarchical data. For instance you’ve got a bunch of hierarchical pages in a CMS, and sometimes pages are children of other pages

Now suppose you want to show a breadcrumb on your site, like this:

Home > Products > Food > Meat > Spam > Spammy McDelicious

To get all those page titles you might do something like this:

titles = []
while page:
    titles.append(page.title)
    page = page.parent

That’s one database query for each page in the breadcrumb, and database queries are slow. Let’s do this a better way.

The Solution

Modified Preorder Tree Traversal can be a bit daunting at first, but it’s one of the best ways to solve this problem.

If you want to go into the details, there’s a good explanation here: Storing Hierarchical Data in a Database or Managing Hierarchical Data in Mysql

tl;dr: MPTT makes most tree operations much cheaper in terms of queries. In fact all these operations take at most one query, and sometimes zero:
  • get descendants of a node
  • get ancestors of a node
  • get all nodes at a given level
  • get leaf nodes
And this one takes zero queries:
  • count the descendants of a given node

Enough intro. Let’s get started.

Getting started

Add mptt To INSTALLED_APPS

As with most Django applications, you should add mptt to the INSTALLED_APPS in your settings.py file:

INSTALLED_APPS = (
    'django.contrib.auth',
    # ...
    'mptt',
)

Set up your model

Start with a basic subclass of MPTTModel, something like this:

from django.db import models
from mptt.models import MPTTModel, TreeForeignKey

class Genre(MPTTModel):
    name = models.CharField(max_length=50, unique=True)
    parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')

    class MPTTMeta:
        order_insertion_by = ['name']

You must define a parent field which is a TreeForeignKey to 'self'. A TreeForeignKey is just a regular ForeignKey that renders form fields differently in the admin and a few other places.

Because you’re inheriting from MPTTModel, your model will also have a number of other fields: level, lft, rght, and tree_id. These fields are managed by the MPTT algorithm. Most of the time you won’t need to use these fields directly.

That MPTTMeta class adds some tweaks to django-mptt - in this case, just order_insertion_by. This indicates the natural ordering of the data in the tree.

Now create and apply the migrations to create the table in the database:

python manage.py makemigrations <your_app>
python manage.py migrate

Create some data

Fire up a django shell:

python manage.py shell

Now create some data to test:

from myapp.models import Genre
rock = Genre.objects.create(name="Rock")
blues = Genre.objects.create(name="Blues")
Genre.objects.create(name="Hard Rock", parent=rock)
Genre.objects.create(name="Pop Rock", parent=rock)

Make a view

This one’s pretty simple for now. Add this lightweight view to your views.py:

def show_genres(request):
    return render(request, "genres.html", {'genres': Genre.objects.all()})

And add a URL for it in urls.py:

(r'^genres/$', show_genres),

Template

django-mptt includes some template tags for making this bit easy too. Create a template called genres.html in your template directory and put this in it:

{% load mptt_tags %}
<ul>
    {% recursetree genres %}
        <li>
            {{ node.name }}
            {% if not node.is_leaf_node %}
                <ul class="children">
                    {{ children }}
                </ul>
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>

That recursetree tag will recursively render that template fragment for all the nodes. Try it out by going to /genres/.

There’s more; check out the docs for custom admin-site stuff, more template tags, tree rebuild functions etc.

Now you can stop thinking about how to do trees, and start making a great django app!