Django mptt介绍以及使用

Django mptt是个Django第三方组件,目标是使Django项目能在数据库中存储层级数据(树形数据)。它主要实现了修改过的前序遍历算法,如果你对原理还不是很了解,可以看我的这篇文章。当然,使用mptt时,原理是可以不用了解的,因为具体的实现细节都已经隐藏。不过,如果项目不是使用的Django,可以参考具体的实现原理。

在整篇文章中,我们将会拿《在数据库中存储层级结构》中的例子作为本文的例子。我们打算在数据库中存储这张图中的数据:

树

在介绍mptt之前,如果你的需求仅仅是像这样显示以上数据:

  • Food
    • Fruit
      • Red
        • Cherry
      • Yellow
        • Banana
    • Meat
      • Beef
      • Pork
  • mptt就显得大材小用了,因为Django已经有内置模板过滤器来完成这个工作:unordered_list(官方文档)。如果你的需求不只这么简单,那就跳过这一段。不过这里还是要讲解一下unordered_list的做法。我们就来实现以上的结果。

    当然我们首先要写一个简单的Model。

    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的数据应该是这样:

    ['Food', ['Fruit', ['Red', ['Cherry'], 'Yellow', ['Banana']], 'Meat', ['Beef', 'Pork']]]
    

    我们需要写一个递归的工具函数:

    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,就像这样:

    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})
    

    最后在模板中添加:

    {{ var|unordered_list }}
    

    就可以看到显示效果了。

    关于unordered_list过滤器的用法就介绍到这里。因为有时候需求不止这么简单,比如有时需要展现样式等等,unordered_list就远远不够了。这个时候就需要mptt,下面开始介绍mptt的用法。

    首先是安装mptt,如果安装了setup tools,就可以用这个指令:

    easy_install django-mptt

    下载包安装的方式就不赘述了,下载地址在这里

    安装完成后,需要在settings文件下的INSTALLED_APPS中添加'mptt'。

    接着写Models,这里我们的Models相之前的实现几乎没有任何的变化。只需继承MPTTModel类:

    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元类中指明:

    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

    mptt shell

    如果安装了自动Admin,可以在Admin模板中像这样显示数据:

    mptt admin

    只需在admin.site注册,像这样:

    from django.contrib import admin
    from mptt.admin import MPTTModelAdmin
    
    admin.site.register(MPTTFood, MPTTModelAdmin)
    

    接下来的话题,就是怎样在模板中显示的问题。我们来修改之前ordered_list的显示,结构是一样的,只是对于叶子节点,我们让它显示成红色。在模板中,不要忘了加”{% load mptt_tags %}“。

    {% load mptt_tags %}
    {% recursetree nodes %}
    
  • {% if node.is_leaf_node %} {{ node.title }} {% else %} {{ node.title }}
      {{ children }}
    {% endif %}
  • {% endrecursetree %}

    这里在视图中传递给模板的参数名必须是nodes。views中就像这样:

    def mptt(request):
        nodes = MPTTFood.tree.all()
        
        return render_to_response('mpttexample/mptt.html', {'nodes': nodes})
    

    模板中的其他用法,请参考官方文档

    关于mptt的介绍就到这里,如果以上这些不能满足你的需求,如在django forms中使用mptt form field等等,请继续参考MPTT官方文档。

    过段时间,再和大家分享MPTT的源码。

    赞这篇文章

    分享到

    23个评论

    1. you_N_G

      你的博客是用 django 写的吗?
      是基于什么已经有的框架吗? 比如 django-cms ?

    2. you_N_G

      主要用的什么组件呢? 我也想自己用 django 写个 博客玩玩, 打算从 django-cms 的基础上写, 但是不知道有没有更好用的。

    3. @秦续业 作者

      在django-cms上二次开发估计比较困难,不过cms,顾名思义,内容管理系统,你可以迅速搭建一个portal这类的东西,当然包括站点。如果你是新手的话,建议就用django从数据库到后端到前端自己写。这样也能从中学到东西。
      组件的话,我除了用了这篇文章提到的django-mptt,还用了django-grapplli,它主要就是修改了django自动后台的界面,另外就是django-filebrowser,它其实是和grapplli集成的,主要用来在后台管理静态文件。

    4. @秦续业 作者

      如果是PHP语言的框架,我不知道有没有PHP的实现,理论上是可以自己去实现的。按照我这篇文章开始的链接去实现。

    5. wangeek

      我在自己的sae个人项目中运用了django-mptt。
      可以作为demo留给大家参考:
      http://nodeye.sinaapp.com/

    6. 秦续业 作者

      文章是三年前的了,有可能mptt的接口发生了变化,需要你自己去确认一下。

    7. Randy

      博主你好:

      想請問一下當初你有考慮過使用Facebook的society app中的comment嗎?目前我還在考慮到底要使用facebook comment還是django-mptt,想使用facebook原生的功能是能快速佈署,但缺點就是只能用facebook登入而無法用其它第三方登錄account。但使用django-mptt的話,又被日後沒有人在maintain此package,兩難啊!博主有什麼其它建議的嗎?

    8. meow

      博主您好:
      请问一下这个mptt的效率问题,当节点有上千个会不会页面加载的比较慢?

    给作者留言

    关于作者

    残阳似血(@秦续业),程序猿一枚,把梦想揣进口袋的挨踢工作者。现加入阿里云,研究僧毕业于上海交通大学软件学院ADC实验室。熟悉分布式数据分析(DataFrame并行化框架)、基于图模型的分布式数据库和并行计算、Dpark/Spark以及Python web开发(Django、tornado)等。

    博客分类

    点击排行

    标签云

    扫描访问

    主题

    残阳似血的微博