How to serialize a Django MPTT family and keep it hierarchical?
Asked Answered
D

2

5

I was trying to find solution for this (Google cache)

And I could only came up with a solution like this:

import json

from mptt.utils import tree_item_iterator
from rest_framework import generics
from rest_framework.response import Response

from .models import Category

def tree_family_items_to_json(instances):
    data = ''
    channel = '"{}"'.format(instances[0].channel.slug)
    for category, structure in tree_item_iterator(instances):
        if structure['new_level']:
        data += '{'
        else:
            data += '],'
            data += '"channel": {}'.format(channel)
            data += '},{'
        data += '"slug": "{}",'.format(category.slug)
        data += '"name": "{}",'.format(category.name)
        data += '"subcategories": ['
        for level in structure['closed_levels']:
            data += '],'
            data += '"channel": {}'.format(channel)
            data += '}'

    return json.loads(data)

class CategoryFamily(generics.RetrieveAPIView):
    lookup_field = 'slug'
    queryset = Category.objects.all()

    def retrieve(self, request, *args, **kwargs):
        instances = self.get_object().get_family()
        json_data = tree_family_items_to_json(instances)
        return Response(json_data)

The point is that I used tree_item_iterator from mptt and now I'm looking for something more fancy.

It suited the need for a while. But now sure for how long.

Any ideas?

Demented answered 11/12, 2017 at 23:36 Comment(0)
C
6

Here is one approach in order to have the tree structure in rest api:

# serializers.py
class CategoryTreeSerializer(ModelSerializer):
    children = SerializerMethodField(source='get_children')
    class Meta:
        fields = ('children',)  # add here rest of the fields from model 

    def get_children(self, obj):
        children = self.context['children'].get(obj.id, [])
        serializer = CategoryTreeSerializer(children, many=True, context=self.context)
        return serializer.data


 # views.py
 class CategoryViewSet(viewsets.ModelViewSet):

    queryset = Category.objects.all()
    serializer_class = CategoryTreeSerializer

    @detail_route()
    def tree(self, request, pk=None):
        """
        Detail route of an category that returns it's descendants in a tree structure.
        """
        category = self.get_object()
        descendants = category.get_descendants() # add here any select_related/prefetch_related fields to improve api performance

        children_dict = defaultdict(list)
        for descendant in descendants:
            children_dict[descendant.get_parent().pk].append(descendant)

        context = self.get_serializer_context()
        context['children'] = children_dict
        serializer = CategoryTreeSerializer(category, context=context)

        return Response(serializer.data)

In my case you get a new endpoint (depending on your url) it will be something like this: category/<category_pk>/tree in which you get the tree structure of specified category.

The idea is to get all descendants and populate the children_dict for each parent, which will be passed to serializer's context in order to avoid multiple queries.

Chev answered 12/12, 2017 at 14:9 Comment(2)
It looks like I can get something to work as you have said. I almost got there. Now I am applying some more effort.Demented
I wish I could give you more credit and am suprised this page isn't visited that much, because this is a very legit answer to a common question!Narco
R
4

You can use djangorestframework-recursive for more convenience.

views.py

from rest_framework import viewsets, generics
from yourapp.serializers import CategorySerializer
from yourapp.models import Category

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.root_nodes()
    serializer_class = CategorySerializer

serializers.py

from rest_framework import serializers
from yourapp.models import Category
from rest_framework_recursive.fields import RecursiveField

class CategorySerializer(serializers.ModelSerializer):
    children = RecursiveField(many=True)

    class Meta:
        model = Category
        fields = ['id', 'name', 'children']
Raylenerayless answered 17/10, 2019 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.