Vahid Al
Vahid Al

Reputation: 1611

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

Upvotes: 0

Views: 2261

Answers (6)

Parantap Parashar
Parantap Parashar

Reputation: 2010

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.

Upvotes: 2

ruddra
ruddra

Reputation: 51988

You can try like this using list_serializer_class:

class FilterActiveListSerializer(serializers.ListSerializer):
    def to_representation(self, data):
        data = data.filter(is_active=True)
        return super(FilterActiveListSerializer, self).to_representation(data)

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        list_serializer_class = FilterActiveListSerializer

class CategorySerializer(serializers.ModelSerializer):
    items = ItemSerializer(many=True, read_only=True)
    class Meta:
        model = Category
        list_serializer_class = FilterActiveListSerializer 

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

Upvotes: 1

satyajit
satyajit

Reputation: 694

try this:

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

class MenuSerializer(serializers.ModelSerializer):
    category     = SerializerMethodField()

    def get_category(self,instance):
        qs = Category.objects.filter(menu__id=instance.id)
        data = CategorySerializer(qs,many=True).data
        return data

    class Meta:
        model  = Menu
        fields = "__all__"

Upvotes: 1

Guillaume
Guillaume

Reputation: 2006

Using a SerializerMethodField is pretty straightforward.

You can filter inside get_< attribute >, you serialize the queryset and return the data from this serialized queryset.

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

class MenuSerializer(serializers.ModelSerializer):
    categories = serializers.SerializerMethodField('get_categories')

    def get_categories(self, menu):
        qs = Category.objects.filter(menu=menu, is_active=True)
        serializer = CategorySerializer(qs, many=True)
        return serializer.data
    
    class Meta:
        model = Menu
        fields = "__all__"

If you have a third nested class, you can just apply the same simple logic to CategorySerializer using a get_items and ItemSerializer.

This approach lets you filter Menu objects to check is_active = True on your views.py since this is business logic.

Upvotes: 3

Vu Phan
Vu Phan

Reputation: 584

I assume you define your models like this

Menu
    is_active
    categories: ManyToManyField (related_name='menus')

Category
    is_active
    items: ManyToManyField (related_name='categories')

Item
    is_active

E.g.

def get_categories():
    active_category_qs = Category.objects.filter(is_active=True)
    active_menu_qs = Menu.objects.filter(is_active=True, 
    categories__in=active_category_qs)
    categories = []

    for menu in active_menu_qs.iterator():
        for category in menu.categories.all():
            if category.id not in [
                    obj_target["id"] for obj_target in categories
            ]:
                category_data = CategorySerializer(category).data # just get the data of category object
                categories.append(category_data)
    return categories

Upvotes: 1

harshil suthar
harshil suthar

Reputation: 179

item_instance = Item.objects.filter(is_active = True, category__is_active=True)

you will get all active items that have an active category using this.

fetch all items and create JSON data from them.

upvote if it helps

Upvotes: -1

Related Questions