Asma Ben
Asma Ben

Reputation: 11

Serializing MPTT Tree with Django REST Framework

I searched for a similar question without success.. So, i am working on a website in which i am using django-mptt to organize categories. My Model looks like:

class Category(MPTTModel):

    parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, related_name='children')
    name = models.CharField(max_length=90)
    slug = models.SlugField(unique=True)

    _full_slug_separator = '/'

    @property
    def url(self):
        names = [category.name for category in self.get_ancestors(include_self=True)]
        return self._full_slug_separator.join(names)

I defined the CategorySerializer as bellow:

class CategorySerializer(serializers.ModelSerializer):

    children = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ('name', 'url', 'children')

    def get_children(self, obj):
        return CategorySerializer(obj.get_children(), many=True).data

# views.py
class CategoryList(generics.ListAPIView):
    queryset = Category.objects.root_nodes()
    serializer_class = CategorySerializer

The question is how can i:
1. have the 'url' data included in leaf nodes only.
2. have the 'children' data included in non leaf nodes only.

Here is an example of the output I am looking for

[
    {
    "title":"root node",
    "children":[
      {
        "title":"leaf node",
        "url":"link"
      },
      {
        "title":"non leaf node",
        "children":[
           {
              "title":"leaf node",
              "url":"link"
           }
        ]
     },
     {
        "title":"non leaf node",
        "children":[
           {
              "title":"non leaf node",
              "children":[
                 {
                    "title":"leaf node",
                    "url":"link"
                 }
              ]
           }
        }
     ]
  },
  {
    "title":"root node",
    "url":"link"
  }
]

Also i want to know if there is a good way for generating the 'url' to reduce queries

And thanks for any help in advance.

Upvotes: 1

Views: 1023

Answers (1)

Ashutosh Chapagain
Ashutosh Chapagain

Reputation: 72

I ran into the same problem and found this question. But there were no answers. So, I am posting how I managed to do it for anyone who may need it in the future. This is most likely not the best solution but it works.

My models.py:


    from django.utils.text import slugify
    from django.utils.translation import gettext_lazy as _
    from mptt.models import MPTTModel, TreeForeignKey
    
    class Category(MPTTModel):
        name = models.CharField(max_length=100, null=False, blank=False, verbose_name=_("category name"), help_text=_("format: required, max_length=100"))
        slug = models.SlugField(max_length=150, null=False, blank=False, editable=False, verbose_name=_("category url"), help_text=_("format: required, letters, numbers, underscore or hyphen"))
        parent = TreeForeignKey("self", on_delete=models.SET_NULL, related_name="children", null=True, blank=True, verbose_name=_("parent category"), help_text=_("format: not required"))
    
        class MPTTMeta:
            order_insertion_by = ['name']
        
        class Meta:
            verbose_name = _('product category')
            verbose_name_plural = _('product categories')
        
        def save(self, *args, **kwargs):
            self.slug = slugify(self.name)
            super(Category, self).save(*args, **kwargs)
        
        def __str__(self):
            return self.name

My serializers.py:


    from rest_framework import serializers
    
    class SubCategorySerializer(serializers.ModelSerializer):
        """Serializer for lowest level category that has no children."""
    
        parent = serializers.SerializerMethodField(source='get_parent')
    
        class Meta:
            model = Category
            fields = ['id', 'name', 'slug', 'parent', ]
    
        def get_parent(self, obj):
            if obj.parent:
                return obj.parent.name
    
    
    class CategorySerializer(serializers.ModelSerializer):
        """Serializer for category."""
    
        parent = serializers.SerializerMethodField(source='get_parent')
        children = serializers.SerializerMethodField(source='get_children')
    
        class Meta:
            model = Category
            fields = ['id', 'name', 'slug', 'parent', 'children', ]
    
        def get_parent(self, obj):
            if obj.parent:
                return obj.parent.name
        
        def get_children(self, obj):
            if obj.children.exists():
                children = [child for child in obj.children.all()]
                children_with_children = [child for child in children if child.children.exists()]
                children_without_children = [child for child in children if not child.children.exists()]
                if children_with_children:
                    return CategorySerializer(children_with_children, many=True).data
                if children_without_children:
                    return SubCategorySerializer(children_without_children, many=True).data

This way children is included in non-leaf nodes only. Here is an example of how the data looks:


    {
        "count": 2,
        "next": null,
        "previous": null,
        "results": [
            {
                "id": 4,
                "name": "Clothes",
                "slug": "clothes",
                "parent": null,
                "children": [
                    {
                        "id": 5,
                        "name": "Men's Clothes",
                        "slug": "mens-clothes",
                        "parent": "Clothes",
                        "children": [
                            {
                                "id": 7,
                                "name": "Men's Jeans",
                                "slug": "mens-jeans",
                                "parent": "Men's Clothes"
                            }
                        ]
                    },
                    {
                        "id": 6,
                        "name": "Women's Clothes",
                        "slug": "womens-clothes",
                        "parent": "Clothes",
                        "children": [
                            {
                                "id": 8,
                                "name": "Women's Jeans",
                                "slug": "womens-jeans",
                                "parent": "Women's Clothes"
                            }
                        ]
                    }
                ]
            },
            {
                "id": 1,
                "name": "Technology",
                "slug": "technology",
                "parent": null,
                "children": [
                    {
                        "id": 3,
                        "name": "Laptop",
                        "slug": "laptop",
                        "parent": "Technology"
                    },
                    {
                        "id": 2,
                        "name": "Mobile Phone",
                        "slug": "mobile-phone",
                        "parent": "Technology"
                    }
                ]
            }
        ]
    }

Upvotes: 0

Related Questions