Nikita Biryukov
Nikita Biryukov

Reputation: 25

Django double nested serializer

I am writing an api's application where Order is an entity that provides information about what customers want to buy, one Order can contain different products with different quantity and different prices. The entity that includes this information in Order is called Order Detail.

And I have some problems with double nested serializers.

After post request with:

{
    "external_id": "PR-123-321-123",
    "details": [{
        "product": {"id": 4},
        "amount": 10,
        "price": "12.00"
    }, ... ]
}

I want to get this response:

{
    "id": 1,
    "status": "new",
    "created_at": "2021-01-01T00:00:00",
    "external_id": "PR-123-321-123",
    "details": [{
        "id": 1,
        "product": {"id": 4, "name": "Dropbox"},
        "amount": 10,
        "price": "12.00"
    }, ... ]
}

But now i have some error: "Cannot assign "OrderedDict()": "OrderDetail.product" must be a "Product" instance."

What's wrong?

Models:

from django.db import models


class Order(models.Model):
    STATUS_CHOICES = (
        ('new', 'new'),
        ('accepted', 'accepted'),
        ('failed', 'failed')
    )
    status = models.CharField(max_length=12, choices=STATUS_CHOICES, default='new')
    created_at = models.DateTimeField(auto_now_add=True)
    external_id = models.CharField(max_length=128, unique=True)


class Product(models.Model):
    name = models.CharField(max_length=64)

    def __str__(self):
        return self.name


class OrderDetail(models.Model):
    order = models.ForeignKey(Order, related_name='details', on_delete=models.CASCADE)
    amount = models.IntegerField()
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=8, decimal_places=2)

    def __str__(self):
        return f'{self.order} detail'

Serializers:

from rest_framework import serializers
from rest_framework.serializers import ModelSerializer

from order.models import Order, Product, OrderDetail


class ProductSerializer(ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'
        read_only_fields = ('name', )


class OrderDetailSerializer(ModelSerializer):
    product = ProductSerializer()

    class Meta:
        model = OrderDetail
        fields = (
            'id',
            'product',
            'amount',
            'price'
        )


class OrderSerializer(ModelSerializer):
    details = OrderDetailSerializer(many=True)
    status = serializers.CharField(source='get_status_display', read_only=True)

    class Meta:
        model = Order
        fields = (
            'id',
            'status',
            'created_at',
            'external_id',
            'details'
        )

    def create(self, validated_data):
        details_data = validated_data.pop('details')
        order = Order.objects.create(**validated_data)
        for detail_data in details_data:
            OrderDetail.objects.create(**detail_data, order=order)
        return order

Upvotes: 0

Views: 317

Answers (3)

Nikita Biryukov
Nikita Biryukov

Reputation: 25

    def create(self, *args, **kwargs):
        order = Order.objects.create(external_id=self.context.get('request').data.get('external_id'))
        OrderDetail.objects.bulk_create(
            [OrderDetail(**order_detail, order=order, product=Product.objects.get(**order_detail.pop('product')))
             for order_detail in self.context.get('request').data.get('details')]
        )
        return order

Upvotes: 1

Rahul Raj
Rahul Raj

Reputation: 516

class OrderSerializer(serializers.ModelSerializer):
    order = OrderDetailSerializer(many=True, source='order_set', required=False)

    class Meta:
        model = Order
        fields = (
            'id',
            'status',
            'created_at',
            'external_id',
            'details'
        )
 
    def create(self, validated_data):
        if 'order_set' in validated_data:
            order = validated_data.pop('order_set')
        else:
            order = []    
        instance = super().create(validated_data)

        for cd in order:
           instance.order_set.create(**cd)
        return instance

    
       

Upvotes: 0

Aprimus
Aprimus

Reputation: 1551

Modify the OrderSerializer create method, to first retrieve the Product instance before the OrderDetail object is created.

class OrderSerializer(ModelSerializer):
    details = OrderDetailSerializer(many=True)
    status = serializers.CharField(source='get_status_display', read_only=True)

    class Meta:
        model = Order
        fields = (
            'id',
            'status',
            'created_at',
            'external_id',
            'details'
        )

    def create(self, validated_data):
        details_data = validated_data.pop('details')
        order = Order.objects.create(**validated_data)
        for detail_data in details_data:
            try:
                product = Product.objects.get(**detail_data.pop('product'))
                OrderDetail.objects.create(**detail_data, order=order, product=product)
            except (Product.DoesNotExist, Product.MultipleObjectsReturned):
                raise serializers.ValidationError('Your custom error message...')
        return order

Upvotes: 0

Related Questions