Django REST Framework - query limit on nested serializer? - django

I have a situation in which one table is related to another via a foreign key as follows:
models.py
class Container(models.Model):
size = models.CharField(max_length=20)
shape = models.CharField(max_length=20)
class Item(models.Model):
container = models.ForeignKey(Container, related_name='items')
name = models.CharField(max_length=20)
color = models.CharField(max_length=20)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True)
class Meta:
model = Container
fields = ('size', 'shape', 'items')
This works fine, but my problem is that all the items in the container get serialized. I only want items with color='green' to be serialized.

class ContainerSerializer(serializers.ModelSerializer):
items = serializers.SerializerMethodField()
def get_items(self, obj):
query = Item.objects.filter(item_set__color='green')
serializer = ItemSerializer(query, many=True)
return serializer.data
class Meta:
model = Container
fields = ('size', 'shape', 'items')

Instead of changing how serializer works, a simplier way, its just filter you Container with green color items and them try to serialize it
You can do something like this:
container_objects = Container.objects.filter(id='your_container_id',item_set__color='green')
serialized_containers = YourContainerSerializer(data=container_objects)

Related

How to prefetch_related() for GenericForeignKeys?

I have a List that consists of ListItems. These ListItems then point towards either a ParentItem or a ChildItem model via a GenericForeignKey:
# models.py
class List(models.Model):
title = models.CharField()
class ListItem(models.Model):
list = models.ForeignKey(List, related_name="list_items")
order = models.PositiveSmallIntegerField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
class ParentItem(models.Model):
title = models.CharField()
class ChildItem(models.Model):
title = models.CharField()
parent = models.ForeignKey(ParentItem, related_name="child")
I want to display a list of all my Lists with their ListItems and respective ItemA/ItemB data using ListSerializer:
# serializers.py
class ParentItemSerializer(serializers.ModelSerializer):
class Meta:
model = ParentItem
fields = ["title"]
class ChildItemSerializer(serializers.ModelSerializer):
parent = ParentItemSerializer()
class Meta:
model = ChildItem
fields = ["title", "parent"]
class ListItemSerializer(serializers.ModelSerializer):
contents = serializers.SerializerMethodField()
class Meta:
model = ListItem
fields = ["contents"]
def get_contents(self, obj):
item = obj.content_object
type = item.__class__.__name__
if type == "ParentItem":
return ParentItemSerializer(item).data
elif type == "ChildItem":
return ChildItemSerializer(item).data
class ListSerializer(serializers.ModelSerializer):
items = serializers.SerializerMethodField()
class Meta:
model = List
fields = ["title", "items"]
def get_items(self, obj):
return ListItemSerializer(obj.list_items, many=True).data
How can I optimize my List queryset to prefetch these GenericForeignKey relationships?
# views.py
class ListViewSet(viewset.ModelViewSet):
queryset = List.objects.all()
serializer_class = ListSerializer
List.objects.all().prefetch_related("list_items") works but the following does not seem to work:
List.objects.all().prefetch_related(
"list_items",
"list_items__content_object",
"list_items__content_object__parent",
)
I've read the documentation on prefetch_related which suggests it should work:
While prefetch_related supports prefetching GenericForeignKey
relationships, the number of queries will depend on the data. Since a
GenericForeignKey can reference data in multiple tables, one query per
table referenced is needed, rather than one query for all the items.
There could be additional queries on the ContentType table if the
relevant rows have not already been fetched.
but I don't know if that's applicable to DRF.
Edit: Some better success when I move some of the prefetching to the serializer:
class ListSerializer(serializers.ModelSerializer):
def get_items(self, obj):
return ListItemSerializer(obj.list_items.all().prefetch_related("content_object"), many=True).data
As per my edit, prefetching works as intended when I move the relevant fields when they're queried in the serializer instead of cramming it all in the view's queryset.

Serialization of child models

I have models:
class CommonEditor(models.Model):
def __str__(self):
return 'Common Atributes Mask'
class Color(models.Model):
name = models.CharField(max_length=25)
editor = models.ForeignKey(CommonEditor, on_delete=models.PROTECT, null=True)
So I make serialization this way:
class ColorSerializer(serializers.ModelSerializer):
class Meta:
model = Color
fields = '__all__'
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
And then view:
class CommonAttributeAPIView(generics.ListCreateAPIView):
serializer_class = CommonAttributesSerializer
queryset = CommonEditor.objects.all()
I get only pk of my CommonEditor Model. Why can't i get the full Atributes Mask and how can I fix it? Big thanks!
Default name for reverse foreign key relation is modelname_set or in your case color_set. So try to rename color field to color_set:
class CommonAttributesSerializer(serializers.ModelSerializer):
color_set = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color_set')
This can also be achieved via SerializerMethodField and can be seen as follow:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = serializers.SerializerMethodField()
class Meta:
model = CommonEditor
fields = ('pk', 'color')
def get_color(self, common_editor):
return ColorSerializer(common_editor.color_set.all(), many=True).data
Documentation: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
The CommonAttributesSerializer search for a color attribute in CommonEditor's instance, but it couldn't find. In DRF serializer, a parameter called source will says explicitly where to look for the data. So , change the serializer as below:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True, <b>source='color_set'</b>)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
Reference : DRF Fields -source

Django - Get parent object with children's object

I'm new to Django so I might miss the answer for this one because of terminology.
I am trying to get parent object with children objects, I've got:
#models.py
class Category(models.Model):
name = models.CharField(max_length=255)
sub_category = models.ForeignKey(SubCategory)
title = models.CharField(max_length=255, null=True)
#serializer.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
#views.py
Product.objects.all().filter(sub_category__category_id=category_id).select_related()
products_serializer = ProductSerializer(products, many=True)
return Response({
'data': products_serializer.data
})
I am trying to get parent category object within the children objects I've already got.
Thanks in advance :)
You already have your parent object, you can access it through each of your objects:
products = Product.objects.all().filter(sub_category__category_id=category_id)
for product in products:
parent = product.parent # You already have it
For better performance change your query to
Product.objects.all().filter(sub_category__category_id=category_id).select_related('parent')
As commented you can do it through serializers:
class ProductItemSerializer(serializers.ModelSerializer):
class Meta:
model = ProductItem
class ProductSerializer(serializers.ModelSerializer):
product_items = ProductItemSerializer(many=True, read_only=True)
class Meta:
model = Product
class CategorySerializer(serializers.ModelSerializer):
products = ProductSerializer(many=True, read_only=True)
class Meta:
model = Category
Now just get categories and products and items will be nested

django REST framework - limited queryset for nested ModelSerializer?

I have a ModelSerializer, but by default it serializes all the objects in my model. I would like to limit this queryset to only the most recent 500 (as opposed to all 50 million). How do I do this?
What I have currently is the following:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
The reason I don't think I can just specify the queryset in my viewset is that this is in fact the nested portion of another serializer.
models.py
class Container(models.Model):
size = models.CharField(max_length=20)
shape = models.CharField(max_length=20)
class Item(models.Model):
container = models.ForeignKey(Container, related_name='items')
name = models.CharField(max_length=20)
color = models.CharField(max_length=20)
views.py
class ContainerViewSet(viewsets.ModelViewSet):
queryset = Container.objects.all() # only a handful of containers
serializer_class = ContainerSerializer
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('name', 'color')
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True) # millions of items per container
class Meta:
model = Container
fields = ('size', 'shape', 'items')
In your View Set you may specify the queryset like follows:
from rest_framework import serializers, viewsets
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()[:500]
serializer_class = MyModelSerializer
I think what you are looking for is the SerializerMethodField.
So your code would look as follows:
class ContainerSerializer(serializers.ModelSerializer):
items = SerializerMethodField('get_items')
class Meta:
model = Container
fields = ('size', 'shape', 'items')
def get_items(self, container):
items = Item.objects.filter(container=container)[:500] # Whatever your query may be
serializer = ItemSerializer(instance=items, many=True)
return serializer.data
The one catch is that the SerializerMethodField is read only.
You may use source parameter
class Container(models.Model):
...
def get_items(self):
return self.items[:500]
and in serializer
items = ItemSerializer(many=True, source='get_items', )

Django REST Framework -- is multiple nested serialization possible?

I would like to do something like the following:
models.py
class Container(models.Model):
size = models.CharField(max_length=20)
shape = models.CharField(max_length=20)
class Item(models.Model):
container = models.ForeignKey(Container)
name = models.CharField(max_length=20)
color = models.CharField(max_length=20)
class ItemSetting(models.Model):
item = models.ForeignKey(Item)
attribute_one = models.CharField(max_length=20)
attribute_two = models.CharField(max_length=20)
serializers.py
class ItemSettingSerializer(serializers.ModelSerializer):
class Meta:
model = ItemSetting
class ItemSerializer(serializers.ModelSerializer):
settings = ItemSettingSerializer(many=True)
class Meta:
model = Item
fields = ('name', 'color', 'settings')
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True)
class Meta:
model = Container
fields = ('size', 'shape', 'items')
When I do nesting of only one level (Container and Item) it works for me. But when I try to add another level of nesting with the ItemSetting, it throws an AttributeError and complains 'Item' object has no attribute 'settings'
What am I doing wrong?
Multiple nested serialization works for me. The only major difference is that I specify a related_name for the FK relationships. So try doing this:
class Item(models.Model):
container = models.ForeignKey(Container, related_name='items')
class ItemSetting(models.Model):
item = models.ForeignKey(Item, related_name='settings')
Hope this works for you.