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
你已经创建了一个 Django 项目,你需要管理一些分层数据。例如,您在 CMS 中有一堆分层页面,有时页面是其他页面的子页面

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:
    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:
tl;dr:MPTT 使大多数树操作在查询方面成本大大降低。事实上,所有这些操作最多只接受一个查询,有时甚至为零:
  • 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 开始使用 


As with most Django applications, you should add mptt to the INSTALLED_APPS in your settings.py file:
与大多数 Django 应用程序一样,您应该 INSTALLED_APPSsettings.py 文件中添加 mptt

    # ...

Set up your model
设置模型 

Start with a basic subclass of MPTTModel, something like this:
从 MPTTModel 的基本子类开始,如下所示:

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.
您必须定义一个 TreeForeignKey 父字段,即 to 'self' 。A TreeForeignKey 只是一个常规 ForeignKey ,它在管理员和其他一些地方以不同的方式呈现表单字段。

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.
由于您继承自 MPTTModel,因此模型还将具有许多其他字段: level 、、 lft rghttree_id 。这些字段由 MPTT 算法管理。大多数情况下,您不需要直接使用这些字段。

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.
MPTTMeta 类添加了一些 django-mptt 调整 - 在本例中,只是 order_insertion_by .这表示树中数据的自然排序。

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:
启动 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:
这个现在很简单。将此轻量级视图添加到您的 views.py

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

And add a URL for it in urls.py:
并在以下位置添加 urls.py URL:

(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:
django-mptt 包括一些模板标签,使这一点也变得简单。创建一个在模板目录中调用 genres.html 的模板,并将其放入其中:

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

That recursetree tag will recursively render that template fragment for all the nodes. Try it out by going to /genres/.
该递归树标记将递归呈现所有节点的模板片段。请转到 /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!
现在你可以停止考虑如何做树,开始制作一个伟大的 django 应用程序!

order_insertion_by gotcha  order_insertion_by 陷阱 

In the example above, we used order_insertion_by option, which makes django-mptt order items in the tree automatically, using name key. What does this mean, technically? Well, in case you add items in an unordered manner, django-mptt will update the database, so they will be ordered in the tree.
在上面的例子中,我们使用了 order_insertion_by option,它使用 name key 自动在树中生成 django-mptt 订单项。从技术上讲,这意味着什么?好吧,如果您以无序方式添加项目, django-mptt 将更新数据库,因此它们将在树中排序。

So why this is exactly a gotcha?

Well, it is not. As long as you don’t keep instances with references to old data. But chances are you do keep them and you don’t even know about this.

In case you do, you will need to reload your items from the database, or else you will be left with strange bugs, looking like data inconsistency.

The sole reason behind that is we can’t actually tell Django to reload every single instance of django.db.models.Model based on the table row. You will need to reload manually, by calling Model.refresh_from_db().
这背后的唯一原因是我们实际上不能告诉 Django 根据表行重新加载每个 django.db.models.Model 实例。您需要通过调用 Model.refresh_from_db() 手动重新加载。

For example, using that model from the previous code snippet:

>>> root = Genre.objects.create(name="")
# Bear in mind, that we're going to add children in an unordered
# manner:
>>> b = OrderedInsertion.objects.create(name="b", parent=root)
>>> a = OrderedInsertion.objects.create(name="a", parent=root)
# At this point, the tree will be reorganized in the database
# and unless you will refresh the 'b' instance, it will be left
# containing old data, which in turn will lead to bugs like:
>>> a in a.get_ancestors(include_self=True)
>>> b in b.get_ancestors(include_self=True)
# What? That's wrong! Let's reload
>>> b.refresh_from_db()
>>> b in b.get_ancestors(include_self=True)
# Everything's back to normal.