Django mptt是个Django第三方组件,目标是使Django项目能在数据库中存储层级数据(树形数据)。它主要实现了修改过的前序遍历算法,如果你对原理还不是很了解,可以看我的这篇文章。当然,使用mptt时,原理是可以不用了解的,因为具体的实现细节都已经隐藏。不过,如果项目不是使用的Django,可以参考具体的实现原理。
在整篇文章中,我们将会拿《在数据库中存储层级结构》中的例子作为本文的例子。我们打算在数据库中存储这张图中的数据:
在介绍mptt之前,如果你的需求仅仅是像这样显示以上数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<
li
>Food
<
ul
>
<
li
>Fruit
<
ul
>
<
li
>Red
<
ul
>
<
li
>Cherry</
li
>
</
ul
>
</
li
>
<
li
>Yellow
<
ul
>
<
li
>Banana</
li
>
</
ul
>
</
li
>
</
ul
>
</
li
>
<
li
>Meat
<
ul
>
<
li
>Beef</
li
>
<
li
>Pork</
li
>
</
ul
>
</
li
>
</
ul
>
</
li
>
|
mptt就显得大材小用了,因为Django已经有内置模板过滤器来完成这个工作:unordered_list(官方文档)。如果你的需求不只这么简单,那就跳过这一段。不过这里还是要讲解一下unordered_list的做法。我们就来实现以上的结果。
当然我们首先要写一个简单的Model。
1
2
3
4
5
6
7
8
|
from
django.db
import
models
class
Food(models.Model):
title
=
models.CharField(max_length
=
50
)
parent
=
models.ForeignKey(
"self"
, blank
=
True
, null
=
True
, related_name
=
"children"
)
def
__unicode__(
self
):
return
self
.title
|
开启自动admin,在后台添加完数据。接着,我们来看看怎么样使用unordered_list这个过滤器来显示树形图。
按照官方文档的说法,显示时传递给template的数据应该是这样:
1
|
[
'Food'
, [
'Fruit'
, [
'Red'
, [
'Cherry'
],
'Yellow'
, [
'Banana'
]],
'Meat'
, [
'Beef'
,
'Pork'
]]]
|
我们需要写一个递归的工具函数:
1
2
3
4
5
6
7
8
9
10
11
|
def
display(foods):
display_list
=
[]
for
food
in
foods:
display_list.append(food.title)
children
=
food.children.
all
()
if
len
(children) >
0
:
display_list.append(display(food.children.
all
()))
return
display_list
|
于是在views中,我们只要得到根节点,然后把disaply函数生成的列表传递给template,就像这样:
1
2
3
4
5
6
7
|
from
django.shortcuts
import
render_to_response
def
unordered_list(request):
foods
=
Food.objects.
filter
(parent
=
None
)
var
=
display(foods)
return
render_to_response(
'mpttexample/unordered_list.html'
, {
'var'
: var})
|
最后在模板中添加:
1
|
{{ var|unordered_list }}
|
就可以看到显示效果了。
关于unordered_list过滤器的用法就介绍到这里。因为有时候需求不止这么简单,比如有时需要展现样式等等,unordered_list就远远不够了。这个时候就需要mptt,下面开始介绍mptt的用法。
首先是安装mptt,如果安装了setup tools,就可以用这个指令:
easy_install django-mptt
下载包安装的方式就不赘述了,下载地址在这里。
安装完成后,需要在settings文件下的INSTALLED_APPS中添加'mptt'。
接着写Models,这里我们的Models相之前的实现几乎没有任何的变化。只需继承MPTTModel类:
1
2
3
4
5
6
|
class
MPTTFood(MPTTModel):
title
=
models.CharField(max_length
=
50
)
parent
=
models.ForeignKey(
"self"
, blank
=
True
, null
=
True
, related_name
=
"children"
)
def
__unicode__(
self
):
return
self
.title
|
这里需要说明的是,实际上MPTTModel隐藏了四个变量:level,lft,rght和tree_id。大多数时候我们是用不到这几个变量的。另外,如果你的Model中parent变量名字不是"parent"时,应当在Model类中MPTT元类中指明:
1
2
3
4
5
6
7
8
9
10
11
|
from
mptt.models
import
MPTTModel
class
MPTTFood(MPTTModel):
title
=
models.CharField(max_length
=
50
)
parent_food
=
models.ForeignKey(
"self"
, blank
=
True
, null
=
True
, related_name
=
"children"
)
class
MPTTMeta:
parent_attr
=
'parent_food'
def
__unicode__(
self
):
return
self
.title
|
Model的其他选项,请参考官方说明。
对于继承MPTTModel的类的实例,将会有额外的方法,比如get_ancestors(更多参考文档)。我们运行manage.py shell命令作实验:
python manage.py shell
如果安装了自动Admin,可以在Admin模板中像这样显示数据:
只需在admin.site注册,像这样:
1
2
3
4
|
from
django.contrib
import
admin
from
mptt.admin
import
MPTTModelAdmin
admin.site.register(MPTTFood, MPTTModelAdmin)
|
接下来的话题,就是怎样在模板中显示的问题。我们来修改之前ordered_list的显示,结构是一样的,只是对于叶子节点,我们让它显示成红色。在模板中,不要忘了加”{% load mptt_tags %}“。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{% load mptt_tags %}
{% recursetree nodes %}
<
li
>
{% if node.is_leaf_node %}
<
span
style
=
"color: red;"
>{{ node.title }}</
span
>
{% else %}
{{ node.title }}
<
ul
>
{{ children }}
</
ul
>
{% endif %}
</
li
>
{% endrecursetree %}
|
这里在视图中传递给模板的参数名必须是nodes。views中就像这样:
1
2
3
4
|
def
mptt(request):
nodes
=
MPTTFood.tree.
all
()
return
render_to_response(
'mpttexample/mptt.html'
, {
'nodes'
: nodes})
|
模板中的其他用法,请参考官方文档。
关于mptt的介绍就到这里,如果以上这些不能满足你的需求,如在django forms中使用mptt form field等等,请继续参考MPTT官方文档。
过段时间,再和大家分享MPTT的源码。