adnan kaya
adnan kaya

Reputation: 589

Django Rest Framework | Writable multiple nested relations

I have some models and they are nested each others. I want to make a bulk create for 2 serializers , both have relations with other models. I looked at documentation on DRF but could not implement it in my code.

I send my json data like this:

{
  'status':true,
  'products':[
              {
               'painting':{'amount':10}, 
               'product':{'id':12, }
              },
              {
               'painting':{'amount':10}, 
               'product':{'id':12, }
              }
             ],
   'customer':{ 'name':'Adnan',
                'address':{'country':'Turkey'}
              },
    'total':111

}

#models.py

class Address():
    ...
class Customer():
    address = models.ForeignKey(Address, ...)
class Painting():
    ...
class Product():
    ...
class Selling():
    customer = models.ForeignKey(Customer, ...)
    products = models.ManyToManyField(Product, through='SellingProduct')
class SellingProduct():
    selling = models.ForeignKey(Selling, ...)
    product = models.ForeignKey(Product, ...)
    painting = models.ForeignKey(Painting, ...)

Here is my serializers.py

class AddressSerializer():
    ...
class CustomerSerializer():
    address = AddressSerializer()
    ...
class PaintingSerializer():
    ...
class ProductSerializer():
    ...
class SellingProductSerializer():
    painting = PaintingSerializer()
    product = ProductSerializer()

class SellingSerializer():
    customer = CustomerSerializer()
    products = SellingProductSerializer(many=True)
    ...
    def create(self, validated_data):
        ...

If I write this:

class SellingSerializer():
    ...
    def create(self, validated_data):
        customer_data = validated_data.pop('customer')
        products_data = validated_data.pop('products')
        selling = Selling.objects.create(**validated_data) #i didn't pass customer here
        for product_data in products_data:
            SellingProducts.objects.create(selling=selling, **product_data)
        return selling

I'm getting this error:

django.db.utils.IntegrityError: (1048, "Column 'customer_id' cannot be null")

If I write this:

class SellingSerializer():
    ...
    def create(self, validated_data):        
        selling = Selling.objects.create(**validated_data) #i didn't pass customer here        
        return selling

I'm getting this error:

ValueError: Cannot assign "OrderedDict...
..Selling.customer must be a "Customer" instance

I want to create a record for Selling and SellingProduct, Painting and I DON'T want to create Customer, Address, Product records in every request and I will use existence(in front-end selected) datas.

Thank you all in advance for any help!

Upvotes: 0

Views: 998

Answers (3)

bkawan
bkawan

Reputation: 1361

  1. SellingSerializer is related with CustomerSerializerh, ProductSerializer

  2. Before creating Selling object, we can validate each serializer and create

  3. update validated data

  4. the process many to many

You would not be just creating customer object product object.Data has to be validated and can use CustomerSerializer and ProductSerializer. Before creating them serialize your data with CustomerSerializer and ProductSerializer, then is valid create object else raise exception.

class SellingSerializer():
    ...

    def create(self, validated_data):
        # First Let's handle Customer data
        customer_data = validated_data.pop('customer')
        customer_serializer = CustomerSerializer(data=customer_data)
        customer_serializer.is_valid(raise_exception=True)
        customer = customer_serializer.save()
        validated_data.update({'customer':customer})  ## update our validated data with customer instance

        # Create Selling object
        selling = Selling.objects.create(**validated_data)  # will receive customer instance as well

        # Handle Products related Data
        products_data = validated_data.pop('products')
        for product_data in products_data:
            product_serializer = ProductSerializer(data=product_data)
            product_serializer.is_valid(raise_exception=True)
            product = product_serializer.save()
            SellingProducts.objects.create(selling=selling, product=product)

        return selling

Upvotes: 0

c6754
c6754

Reputation: 887

You need to use your CustomerSerializer to create a customer object and database row before you can create a Selling object with a foreign key to it. You are trying either not passing anything or are passing the JSON (that turns into the OrderedDict in your error message).

class SellingSerializer():
    ...
    def create(self, validated_data):
        customer_data = validated_data.pop('customer')
        products_data = validated_data.pop('products')
        customer = CustomerSerializer.save(**customer_data)
        selling = Selling.objects.create(customer=customer, **validated_data)
        for product_data in products_data:
            SellingProducts.objects.create(selling=selling, **product_data)
        return selling

Maybe reread the documentation on this issue.

Upvotes: 0

Ozgur Akcali
Ozgur Akcali

Reputation: 5492

Your first approach should work, if you make a few modifications. Your Selling model is dependent on on a Customer, so you first need to create a Customer. Then, your SellingProduct model is dependent on a Product and Painting, so you first need to create a Product and Painting, then create a SellingProduct with instances of those, like this:

class SellingSerializer():
    ...
    def create(self, validated_data):
        customer_data = validated_data.pop('customer')
        selling_products_data = validated_data.pop('products')

        customer = Customer.objects.create(**customer_data)
        selling = Selling.objects.create(customer=customer, **validated_data)

        for selling_product_data in selling_products_data :
            product_data = selling_product_data.pop('product')
            product = Product.objects.create(**product_data)

            painting_data = selling_product_data.pop('painting')
            painting = Painting.objects.create(**painting_data)

            SellingProducts.objects.create(selling=selling, product=product, painting=painting)

        return selling

Of course, this approach creates a new Customer, Products and Paintings for each request. Is this really what you want? If you do not want to create new Product and Painting instances for each request, but use references to existing instances, you can define them as PrimaryKeyRelatedField fields in the SellingSerializer and SellingProductSerializer. Then, you can change your create function to this:

def create(self, validated_data):
    customer = validated_data.pop('customer')
    selling_products_data = validated_data.pop('products')

    selling = Selling.objects.create(customer=customer, **validated_data)

    for selling_product_data in selling_products_data :
        SellingProducts.objects.create(selling=selling, **selling_product_data )

    return selling

Upvotes: 1

Related Questions