3π
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.
1π
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
- [Django]-%d format: a number is required, not NoneType for is_valid()
- [Django]-How do I call templates with same names at different directories in Django?
- [Django]-Filtering Django models by existence of a related object
- [Django]-Django Form Field to support US or International phone numbers?
- [Django]-Django AbstractBaseUser vs BaseUserManager when creating custom user