[Django]-Django Rest Framework categories and childs in one model

3👍

by source option you should use . in instead of __:

childs = CategoryListSerializer(many=True, source='assortment_child.parent_category')

but still you will has many queries, to fix it you should use prefetch-related

def get_queryset(self):
    shop = self.get_shop()
    qs = Category.objects.filter(assortment_parent__shop=shop).all()
    return qs.prefetch_related('assortment_child').distinct()

more detail you can read in the how-can-i-optimize-queries-django-rest-framework

0👍

I had the similar problem and the best solution I have found is to do some manual processing in order to receive desired tree representation.
So firstly we fetch all Assortment for shop and then build the tree manually.

Let’s look at the example.

def get_categories_tree(assortments, context):
    assortments = assortments.select_related('category', 'parent_category')
    parent_categories_dict = OrderedDict()

    for assortment in assortments:
        parent = assortment.parent_category
        # Each parent category will appear in parent_categories_dict only once
        # and it will accumulate list of child categories
        if parent not in parent_categories_dict:
            parent_data = CategoryListSerializer(instance=parent, context=context).data
            parent_categories_dict[parent] = parent_data
            parent_categories_dict[parent]['childs'] = []

        child = assortment.category
        child_data = CategoryListSerializer(instance=child, context=context).data
        parent_categories_dict[parent]['childs'].append(child_data)

    # convert to list as we don't need the keys already - they were used only for matching
    parent_categories_list = list(parent_categories_dict.values())
    return parent_categories_list


class InstitutionTreeCategories(generics.ListAPIView):
    def list(self, request, *args, **kwargs):
        shop = self.get_shop()
        assortments = Assortment.objects.filter(shop=shop)
        context = self.get_serializer_context()
        categories_tree = get_categories_tree(assortments, context)
        return Response(categories_tree)

All in single DB query.

The problem here is that there is no explicit relation between category and parent_category. If you define ManyToManyField in Category using Assortment as through intermediate model, you will get an access which Django can understand, so you would just use attribute childs on Category for example. However this will still return all children (the same would happen if your source example works) categories, ignoring shop, so some clever Prefetch would have to be done to achieve correct results. But I believe manual “join” is simpler.

0👍

you need to use prefetch_related along with serializer method field

serializer:

class CategoriesTreeSerializer(CategoryListSerializer):

    children = serializers.SerializerMethodField()

    class Meta(CategoryListSerializer.Meta):

        fields = (
            'id', 
            'name', 
            'image', 
            'children'
            )

    def get_children(self, obj):
        children = set()
        for assortment in obj.assortment_parent.all():
            children.add(assortment.category)
        serializer = CategoryListSerializer(list(children), many=True)
        return serializer.data

your get queryset method:

def get_queryset(self):
    shop = self.get_shop()
    return (Category.objects.filter(assortment_parent__shop=shop)
           .prefetch_related(Prefetch('assortment_parent', queryset=Assortment.objects.all().select_related('category')
           .distinct())

Leave a comment