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

4
以下是我的代码:
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 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')

但我失败了,因为我只能输出这棵树的根节点的内容。似乎序列化程序无法访问根节点的子节点,进一步地,也无法访问子节点的子节点... 具体问题在于输出中显示“父级”为空,但实际上它有4个子节点,每个子节点都包含几个后代。我的代码哪里出了问题?谢谢大家帮忙!


一个MPTTModel是一个简单的模型,其中包含一些额外的字段,所以我不明白问题出在哪里:你可以将模型扁平化序列化,不需要递归进入其中,对吧? - Wtower
2个回答

8

无论是 MPTT 还是 REST 框架都没有魔法。

MPTT 在你的模块中添加了新字段,从而实现嵌套集模型。它还跟踪从子级到其父级的向上链接,用于某些优化,并在树结构损坏时重建嵌套集树。

因此,基本上,您的模型具有以下手动添加的字段 name/numparent 用于触发 MPTT,以及以下自动字段:

  • tree_id:树标识符。连接到同一根节点的所有节点共享相同的tree_id
  • level:节点在树中的深度。
  • lft/rght:嵌套集索引。请参见上面的链接,但基本思想是如果一个节点的lft大于或等于另一个节点的lft,并且rght小于或等于另一个节点的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

2
为了完整回答上述问题,这里提供一个 JavaScript 中“treeify”函数的链接:https://dev59.com/ZGEh5IYBdhLWcg3wdzaR - Leogout

0

更新于2021年。

对于那些需要创建嵌套树的人,我升级了@spectras的答案,并成功返回了嵌套树。您只需要在FileSerializerget_fields()中添加'required=False',并在您的视图集中找到一种方法仅返回“第一个节点”即可。

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

在我的情况下,在视图集上,我进行了过滤以仅返回具有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。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接