pythondjangodjango-mptt

In django, how to serialize mptt tree?


The follows is my code:

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']

And I try to serialize this class using the following code:

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

But I fail in that I can only output the contents of the root node of this tree.It seems the serializer cannot access the children of the root, and further the children of the children... The specific problem is that in the output, the 'parent' is shown to be 'null', but in fact it has 4 children and each of them contains several descendants. What's wrong with my code? Thank you all for helping me!


Solution

  • There is no magic about neither MPTT nor REST framework.

    MPTT adds new fields into your module, so as to implement a nested set model. It also tracks an upwards link from children to their parent, which it uses for some optimizations, and to rebuild the nested set tree, should it get corrupted.

    So basically, your model has the following fields name/num that you added manually, parent which you added to trigger MPTT, and the following automatic fields:

    REST framework is not mptt-aware, and does not need to be. It will just see a regular model with 7 fields, which it will happily serialize.

    While you may implement a recursive serializer to shape the serialized representation in a nested object of objects of objects of objects of..., it is usually not a good idea at this point.

    Now, if you actually want to do that, you need to do it the other way. You must serialize the root nodes, and make sure their serialized representation recursively includes all of their children. Not the other way around, like you tried here.

    The idea would be to construct something like this:

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

    But you cannot do it this way, as FileSerializer is not defined at the point you want it. You could try to override the constructor and insert the additional serializer there, like this:

    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
    

    Untested, but you get the idea.

    However:

    How about you just serialize flat nodes, and rebuild the object tree on the client side if you really need to?

    [{'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