Reputation: 121
I'm trying to use the Django Rest Framework serializers to make API's for my front-end to create / update and delete articles in a store. These articles can have multiple prices (depending on time). So there is a one-to-many relation from article (one) to price (many). I have defined this in models.py
:
class Article(models.Model):
article_id = models.AutoField(primary_key=True, verbose_name="Article ID")
article_name = models.CharField(max_length=250, verbose_name="Name")
article_order = models.IntegerField(verbose_name="Order")
class Price(models.Model):
price_id = models.AutoField(primary_key=True, verbose_name="Price ID")
article_id = models.ForeignKey(Article, on_delete=models.CASCADE, verbose_name="Article ID", related_name="Prices")
price_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="Price")
My serializers.py file looks like this:
from rest_framework import serializers
from .models import *
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields = ('price_price',)
class ArticleSerializer(serializers.ModelSerializer):
Prices = PriceSerializer(many=True)
def create(self, validated_data):
prices_data = validated_data.pop('Prices')
article = Article.objects.create(**validated_data)
for price_data in prices_data:
Price.objects.create(article_id=article, **price_data)
return article
def update(self, instance, validated_data):
prices_data = validated_data.pop('Prices')
Article.objects.filter(article_id=instance.article_id).update(**validated_data)
for price_data in prices_data:
Price.objects.get_or_create(article_id=instance, **price_data)
return instance
class Meta:
model = Article
fields = '__all__'
This works perfectly and I can create a new article-price measurement with this data: (article_order
will be used later for ordering the list)
{"Prices":[{"price_price":"1"}],"article_name":"Article A","article_order":"1"}
Until this point, everything is working as expected. But when I try to update the prices, the Price.objects.get_or_create()
statement does not recognize existing prices. For example: when the first price is €10, then €20 and then again €10, the last price will not be inserted because the get_or_create
statement does not recognize this as a new instance of the price
model. That probably makes sense, because the PriceSerializer
does not serialize the price_id
. But when I change the serializer to:
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields = '__all__
, I can no longer create instances because the ArticleSerializer
is requiring an article_id
for the Price
instance (but that doesn't exist yet).
Does anyone have an idea how to solve this problem? The DRF documentation only includes the create statement for this type of nested serializer.
https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
Upvotes: 6
Views: 4884
Reputation: 1
You can see from Django REST Framework documentation here about serializers.
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
return instance
I hope it can help you.
Upvotes: 0
Reputation: 6296
You're not using the get_or_create
method correctly.
It accepts kwargs
which are used to lookup the object and if it does not exist, creates them, adding other field values from defaults
.
This means that if you pass the new values to update as kwargs
, it won't find such object because it currently has different values for those fields.
Instead, you should pass in fields that do not change in kwargs
and the rest in defaults
.
This is what you should do
def update(self, instance, validated_data):
prices_data = validated_data.pop('Prices')
Article.objects.filter(article_id=instance.article_id).update(**validated_data)
for price_data in prices_data:
Price.objects.get_or_create(article_id=instance, defaults=price_data)
return instance
Note however, that you may get a MutipleObjectsReturned
error since an article can have several prices based on your DB architecture. You may want to change the article_id
to a OneToOneField
to prevent this if mutiple prices per Article was not intended.
You can read more about get_or_create
in the Django docs
As a side note, in OOP architecture id
in Article
already implies article id, so prepending article
to your field names is a really redundant and bad practice. And again, you may want to use the Django autogenerated id field instead of redefining it, unless you want to customize it in some way, which is currently not obvious in your code
Upvotes: 2