Reputation: 1611
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
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
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
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
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
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
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