I have two models as:
class Book(AppModel):
title = models.CharField(max_length=255)
class Link(AppModel):
link = models.CharField(max_length=255)
class Page(AppModel):
book= models.ForeignKey("Book", related_name="pages", on_delete=models.CASCADE)
link = models.ForeignKey("Link", related_name="pages", on_delete=models.CASCADE)
page_no = models.IntegerField()
text = models.TextField()
and serializers
class LinkSerializer(serializers.ModelSerializer):
class Meta:
model = Link
fields = ['link']
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = ('link','text','page_no')
def validate_text(self, value):
#some validation is done here.
def validate_link(self, value):
#some validation is done here.
class BookSerializer(serializers.ModelSerializer):
pages = PageSerializer(many=True)
class Meta:
model = Book
fields = ('title','pages')
@transaction.atomic
def create(self, validated_data):
pages_data= validated_data.pop('pages')
book = self.Meta.model.objects.create(**validated_data)
for page_data in pages_data:
Page.objects.create(book=book, **page_data)
return book
There is a validate_text method in PageSerializer. The create method will never call the PageSerializer and the page_data is never validated.
So I tried another approach as:
@transaction.atomic
def create(self, validated_data):
pages_data = validated_data.pop('pages')
book = self.Meta.model.objects.create(**validated_data)
for page_data in pages_data:
page = Page(book=book)
page_serializer = PageSerializer(page, data = page_data)
if page_serializer.is_valid():
page_serializer.save()
else:
raise serializers.ValidationError(page_serializer.errors)
return book
Posted data:
{
"title": "Book Title",
"pages": [
{
"link": 1, "page_no": 52, "text": "sometext"
}
]
}
But the above approach throws error:
{
"link": [
"Incorrect type. Expected pk value, received Link."
]
}
I also found why this error is caused: Though I am posting data with pk value 1 of a Link, the data when passed to the PageSerializer from the BookSerializer appears as such: {"link": "/go_to_link/", "page_no":52, "text": "sometext"}
Why is an instance of Link passed to the PageSerializer whereas what I sent is pk of Link?
To validate a nested object using a nested serializer:
@transaction.atomic
def create(self, validated_data):
pages_data = validated_data.pop('pages') #pages data of a book
book= self.Meta.model.objects.create(**validated_data)
for page_data in pages_data:
page = Page(book=book)
page_serializer = PageSerializer(page, data = page_data)
if page_serializer.is_valid(): #PageSerializer does the validation
page_serializer.save()
else:
raise serializers.ValidationError(page_serializer.errors) #throws errors if any
return book
Suppose you send the data as:
{
"title": "Book Title",
"pages": [{
"link":2,#<= this one here
"page_no":52,
"text":"sometext"}]
}
In the above data we are sending an id of the Link object. But in the create method of the BookSerializer defined above, the data we sent changes to:
{
"title": "Book Title",
"pages": [{
"link":Link Object (2),#<= changed to the Link object with id 2
"page_no":52,
"text":"sometext"}]
}
And the PageSerializer is actually meant to receive an pk value of the link i.e, "link": 2 instead of "link":Link Object (2). Hence it throws error:
{
"link": [
"Incorrect type. Expected pk value, received Link."
]
}
So the workaround is to override the to_internal_value method of the nested serializer to convert the received Link Object (2) object to its pk value.
So your PageSerializer class should then be:
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = ('link','text','page_no')
def to_internal_value(self, data):
link_data = data.get("link")
if isinstance(link_data, Link): #if object is received
data["link"] = link_data.pk # change to its pk value
obj = super(PageSerializer, self).to_internal_value(data)
return obj
def validate_text(self, value):
#some validation is done here.
def validate_link(self, value):
#some validation is done here.
and the parent serializer:
class BookSerializer(serializers.ModelSerializer):
pages = PageSerializer(many=True)
class Meta:
model = Book
fields = ('title','pages')
@transaction.atomic
def create(self, validated_data):
pages_data = validated_data.pop('pages')#pages data of a book
book= self.Meta.model.objects.create(**validated_data)
for page_data in pages_data:
page = Page(book=book)
page_serializer = PageSerializer(page, data = page_data)
if page_serializer.is_valid(): #PageSerializer does the validation
page_serializer.save()
else:
raise serializers.ValidationError(page_serializer.errors) #throws errors if any
return book
That should allow the nested serializer to do the validation instead of writing validation inside the create method of the parent serializer and violating DRY.
When you call serializer.is_valid(raise_exception=True/False) it automatically call validate functions of nested serializer. When you call serializer.save(**kwargs) serializer passes validated data into your create(self, validated_data) or update(self, instance, validated_data) functions of serializer. Moreover, in validated data your ForeignKey fields returned an object.
def create(self, validated_data):
pages_data = validated_data.pop('pages') # [{'link': Linkobject, ...}]
book= self.Meta.model.objects.create(**validated_data)
for page_data in pages_data:
page = Page(book=book)
page_serializer = PageSerializer(page, data = page_data) # here you are sending object to validation again
if page_serializer.is_valid():
page_serializer.save()
else:
raise serializers.ValidationError(page_serializer.errors)
return book
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With