djangodjango-rest-framework

Django - How to filter children of a nested queryset?


I have this model called Menu which has a many-to-many relationship with another model called category. Both this models have a field called is_active which indicates that menu or category is available or not. Alright, then I have an api called RestaurantMenus, which returns all active menus for a restaurant with their categories extended, the response is something like this:

Menu 1
     Category 1
     Category 2
Menu 2
     Category 3
Menu 3
     Category 4
     Category 5
     Category 6

Now what I try to achieve is to only seriliaze those menus and categories which are active (is_active = True). To filter active menus is simple but to filter its children is what I'm struggling with.

My Models:

class Category(models.Model):
    menu = models.ForeignKey(Menu, related_name='categories', on_delete=models.CASCADE)
    name = models.CharField(max_length=200)

class Menu(models.Model):
    name = models.CharField(max_length=200)

My Serializers:

class CategorySerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Category
        fields = "__all__"

class MenuSerializer(serializers.ModelSerializer):
    categories = CategorySerializer(many=True, read_only=True)
    
    class Meta:
        model = Menu
        fields = "__all__"

P.S. Category model itself has a many-to-many relationship with another model Called Item, which has an is_active field too. I want the same effect for those too but I cut it from the question cause I think the process should be the same. So actually the api response is something like this:

Menu 1
     Category 1
          Item 1
          Item 2
          Item 3
     Category 2
          Item 4

Solution

  • Here you can use Django Prefetch objects. This lets you define queryset to choose the set objects from.

    So, you would write your Menu retrieval query something like this:

    from django.db.models import Prefetch
    
    menus = Menu.objects.filter(is_active=True).prefetch_related(Prefetch('categories', queryset=Category.objects.filter(is_active=True)))
    

    To add category items as well to the result set, use the following query:

    menus = Menu.objects.filter(is_active=True).prefetch_related(Prefetch('categories', queryset=Category.objects.filter(is_active=True)), Prefetch('categories__items', queryset=Item.objects.filter(is_active=True))))
    

    This should solve your problem.

    Note: I have not tested the code so you might need to make some modifications.