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!