Reputation: 509
I have two related models: Product
and ProductDescription
. In 1 submit action user able to insert a new Product
with multiple descriptions depend on the available languages. I use writable nested serializer to insert into Product
and ProductDescription
simultaneously. I do it by overriding create
function in ProductDescriptionSerializer
class, it works. However, I can only insert 1 ProductDescription
at a time.
Then I tried to use this answer to create multiple model instances at once. The problem is it also creates the same Product
twice instead of using the newly created Product
Id to insert the next ProductDescription
.
My models.py
:
class Product(models.Model, ProductStatus):
product_code = models.CharField(max_length=6)
color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
video = models.URLField(verbose_name='Video URL', max_length=250, null=True, blank=True)
status = models.CharField(max_length=20, choices=ProductStatus.status, default=ProductStatus.active)
class ProductDescription(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
description = models.TextField(max_length=500, null=True, blank=True)
def __str__(self):
return '%s - %s' % (self.product, self.language)
My serializers.py
:
class CustomRelatedField(serializers.RelatedField):
def display_value(self, instance):
return instance
def to_representation(self, value):
return str(value)
def to_internal_value(self, data):
model = self.queryset.model
return model.objects.get(id=data)
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
class ProductDescriptionSerializer(serializers.ModelSerializer):
product = ProductSerializer()
language = CustomRelatedField(many=False, queryset=Language.objects.all())
class Meta:
model = ProductDescription
fields = ['id', 'product', 'language', 'description']
def to_representation(self, instance):
data = super().to_representation(instance)
if self.context['request'].method == 'GET':
data['product'] = instance.product.product_code
return data
return Serializer.to_representation(self, instance)
# The `.create()` method does not support writable nested fields by default.
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product = Product.objects.create(**product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
My views.py
:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
class ProductDescriptionView(CreateListModelMixin, viewsets.ModelViewSet):
permission_classes = [permissions.DjangoModelPermissions]
queryset = ProductDescription.objects.all()
serializer_class = ProductDescriptionSerializer
http_method_names = ['get', 'head', 'post', 'put', 'patch', 'delete']
The JSON format I use to POST data:
[
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 1,
"description": "English description."
},
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 2,
"description": "Vietnamese description."
}
]
It creates a duplicate Product
in Product List:
[
{
"id": 26,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
{
"id": 27,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
}
]
The ProductDescription
datas are correct though:
[
{
"id": 5,
"product": "BQ1080",
"language": "English",
"description": "English description."
},
{
"id": 6,
"product": "BQ1080",
"language": "Vietnam",
"description": "Vietnamese description."
}
]
Upvotes: 1
Views: 1191
Reputation: 47374
To avoid duplicate product you can use get_or_create()
method:
class ProductDescriptionSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product_code = product_data.pop("product_code")
product, _ = Product.objects.get_or_create(product_code=product_code, defaults=product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
Note that get_or_create
is prone to race condition. So if two same requests came to you service at the same time you may still have duplicate products.
Upvotes: 1
Reputation: 52028
I think you need to override your ProductSerializer
's create method. Maybe you can try like this:
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
def create(self, validated_data):
instance, _ = Product.objects.get_or_create(**validated_data)
return instance
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
So that, first it will try to get if the Product exists, else create the instance(hence reducing duplicate entry).
Upvotes: 1