0

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

3 Answers 3

1
    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
Sign up to request clarification or add additional context in comments.

Comments

0

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

2 Comments

Thanks, but i got another one error: "MultipleObjectsReturned at /api/v1/orders/ get() returned more than one Product -- it returned 2!"
And i got except error, because i have one more product. I thought, maybe the problem is that the product is already being used in the order, but I created a new product and indicate it ID in the post request, as described above in the body of the question. Here is the request body where I pass the product id: ``` { "external_id": "PR-123-321-123", "details": [{ "product": {"id": 4}, "amount": 10, "price": "12.00" }] } ```
0
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

    
       

1 Comment

Thanks, but i have my own solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.