dejandenib
dejandenib

Reputation: 75

Django nested serializer for multiple models, with chained foreignKeys

Let's use these 3 simple models for example. A city can have multiple shops, and a shop can have multiple products

models.py

class City(models.Model):
    name=models.CharField(max_length=300)

class Shop(models.Model):
    name = models.CharField(max_length=300)
    city = models.ForeignKey(City, related_name='related_city', on_delete=models.CASCADE)

class Product(models.Model):
    name=models.CharField(max_length=300)
    shop=models.ForeignKey(Shop, related_name='related_shop', on_delete=models.CASCADE)

serializers.py

class CitySerializer(serializers.ModelSerializer):
    class Meta:
        model = City
        fields=['id','name']

class ShopSerializer(serializers.ModelSerializer):
    related_shop = CitySerializer(many=True, read_only=True)
    class Meta:
        model = Shop
        fields=['id','name','related_city']

class ProductSerializer(serializers.ModelSerializer):
    related_shop = ShopSerializer(many=True, read_only=True)
    class Meta:
        model = Product
        fields=['id','name','related_shop']

In views.py, in the get_queryset(), I am using .select_related().all() to fetch the foreign objects. ProductSerializer will give me all products, and will fetch the foreignKey shops, and I will also get the names of the shops where this product is found.

ShopSerializer, in a similar way, will give me all shops names, and all the cities where this shop can be found.

But how can I make a serializer, that will retrieve from all 3 tables at once? The fields I want are: fields=['product_name','shop_name', 'city_name']

I know that, that list I will get will have repetitions, and can be considered 1NF or 2NF, opposed to the model design I have as database 3NF. But that is the query that I want.

I was actually thinking of denormalizing my database, so I can easily achieve this.

My second question is, is it better to denormalize it to 1NF, and have repetitions, so that I reduce the CPU intensive inner joins on 'id' between these 3 tables? I researched a lot about this, and usually the lazy answer is: Try both variants, benchmark, and decide yourself.

Upvotes: 1

Views: 2142

Answers (2)

Reza GH
Reza GH

Reputation: 2102

use source like this:

class ProductSerializer(serializers.ModelSerializer):
    shop_name = serializers.CharField(source='shop.name')
    city_name = serializers.CharField(source='shop.city.name')

    class Meta:
        model = Product
        fields = ['id', 'name', 'shop_name', 'city_name']

for second question. you may act like this:

class City(models.Model):
    name=models.CharField(max_length=300)

class Shop(models.Model):
    name = models.CharField(max_length=300)

class Product(models.Model):
    name=models.CharField(max_length=300)
    city=models.ForeignKey(City, related_name='product_city', on_delete=models.CASCADE)
    shop=models.ForeignKey(Shop, related_name='product_shop', on_delete=models.CASCADE)

by using this you can access to city and shop for each product easily in template or whatever your gonna use for forntend and there is less pressure on database to achieving product's shop and city

Upvotes: 1

partizaans
partizaans

Reputation: 305

There will be different approaches available for your question. Fetching related objects from the database at once does not depend on the serializer itself actually. That's done in your view layer where you can append select_related('shop__city') to your queryset. By appending this you will have both shop and the shop.city values preloaded on Productobjects in a single query.

A simple way of serializing those fields is to set source in the serializer fields like below:

class ProductSerializerV2(serializers.ModelSerializer):
    shop_name = serializers.CharField(source='shop.name')
    city_name = serializers.CharField(source='shop.city.name')

    class Meta:
        model = Product
        fields = ['name', 'shop_name', 'city_name']

Conclusion

According to the descriptions above, the below snippet will only have a single query to the database

p = Product.objects.select_related('shop__city').last()
print(ProductSerializerV2(p).data)  
# {'name': 'product-z', 'shop_name': 'shop-z', 'city_name': 'z'} as a sample output

Upvotes: 2

Related Questions