在 Django 中,如何序列化 mptt 树?

In django, how to serialize mptt tree?

以下是我的代码:

class File(MPTTModel):
    name=models.CharField(max_length=36, primary_key=True)
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
    num=models.IntegerField(null=True)
    class MPTTMeta:
        order_insertion_by = ['name']

然后我尝试使用以下代码序列化此 class:

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

class FileSerializer(serializers.ModelSerializer):
    parent=RecursiveField(many=True)
    class Meta:
        model = File
        fields=('name','num','parent')

但是我失败了,我只能输出这个tree.It的根节点的内容似乎序列化程序无法访问根的children,进一步children children... 具体问题是,在输出中,'parent'显示为'null',但实际上它有4个children,每个都包含几个后代。 我的代码有什么问题? 谢谢大家的帮助!

MPTT 和 REST 框架都没有魔法。

MPTT 将新字段添加到您的模块中,以实现 a nested set model。它还跟踪从子级到父级的向上link,用于一些优化,并重建嵌套集树,如果它被损坏。

所以基本上,您的模型具有以下您手动添加的字段 name/num,您添加以触发 MPTT 的 parent,以及以下自动字段:

  • tree_id:树标识符。连接到同一根的所有节点共享相同的 tree_id.
  • ̀level: 节点在树内的深度。
  • lft/rght:嵌套集合索引。参见上面的 link,但基本思想是一个节点是另一个节点的后代,如果它的 lft 大于或等于并且它的 rght 小于或等于另一个节点的.

REST framework 不支持 mptt,也不需要。它只会看到一个包含 7 个字段的常规模型,它会很乐意对其进行序列化。

虽然您可以实现一个递归序列化器来塑造对象的对象的对象的嵌套对象中的序列化表示...,但通常不是一个好主意这一点。

现在,如果你真的想这样做,你需要以另一种方式来做。您必须序列化根节点,并确保它们的序列化表示递归地包括它们的所有子节点。不是相反,就像你在这里试过的那样。

我们的想法是构建这样的东西:

class FileSerializer(serializers.ModelSerializer):
    children = FileSerializer(many=True)
    class Meta:
        model = File
        fields=('name','num')

但是你不能这样做,因为 FileSerializer 没有在你想要的地方定义。您可以尝试覆盖构造函数并在那里插入额外的序列化程序,如下所示:

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields=('name','num')

    def get_fields(self):
        fields = super(FileSerializer, self).get_fields()
        fields['children'] = FileSerializer(many=True)
        return fields

未经测试,但您明白了。

然而:

  • 以这种幼稚的方式进行操作是一个非常糟糕的主意,因为树中的每个非叶节点都会触发对数据库的额外查询以获取其子节点。
  • 这不适用于反序列化。

怎么样你只序列化平面节点,如果你真的需要在客户端重建对象树?

[{'id': 1, 'name': 'foo', 'parent': null},  // /foo
 {'id': 2, 'name': 'bar1', 'parent': 1},    // /foo/bar1
 {'id': 3, 'name': 'bar2', 'parent': 1},    // /foo/bar2
 {'id': 4, 'name': 'foo2', 'parent': null}, // /foo2
 {'id': 5, 'name': 'baz1', 'parent': 4},    // /foo2/baz1
 {'id': 6, 'name': 'baz2', 'parent': 4}]    // /foo2/baz2

2021 年更新。

对于那些需要创建嵌套树的人,我升级了@spectras 答案并设法 return 嵌套树。您只需要在 get_fields() 上的 FileSerializer 中添加 'required=False' 并且在您的视图集中,您必须找到一种方法 return “仅”第一个节点。

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields=('name','num')

    def get_fields(self):
        fields = super(FileSerializer, self).get_fields()
        fields['children'] = FileSerializer(many=True, required=False)
        return fields

在我的例子中,在视图集中,我过滤到 return 只有具有 parent=0.

的项目

继续上面的例子:

class FileViewSet(viewsets.ModelViewSet):
    """
    Viewset to be used on Urls.py
    """
    serializer_class = File

    def get_queryset(self):
        queryset = File.objects.filter(level=0)
        return queryset

您将拥有这样的端点:

[
     {
      'id': 1,
      'name': 'foo',
      'parent': 0,
      'children':[
         {
           'id': 2,
           'name': 'bar2',
           'parent': 1,
           'children':[
              {
                'id': 3,
                'name': 'bar3',
                'parent': 2,
                'children':[]
               }
             ]
          } 
       ]
     }, 
     {
      'id': 4,
      'name': 'bar2',
      'parent': 0,
      'children':[]
     }
]

如果您需要更新任何节点,只需添加您想要正常更新的节点的id。