Reputation: 3291
Supposing some standard Django relational setup like this:
models.py
class Book(models.Model):
title = models.CharField(max_length=30)
class Page(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
text = models.CharField(max_length=100)
I'd like to create a book and all its pages with one request. If we start with serializers like this:
serializers.py
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = '__all__'
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title', 'pages')
pages = PageSerializer(many=True)
Then the problem is that the PageSerializer now requires a book foreign key. But I don't know the key of the book until I've created the book, which is only after I've sent the POST request. So I cannot include the book pk in the POST data that the client sends.
An obvious solution is to override the create function on the Book serializer. But then I am still faced with the problem that the validators will say that the book field is required and the POST data will fail to validate.
I could make book a not-required field on the PageSerialzer. But this seems very bad. The book field IS required. And the BookSerializer create method will be able to supply it. It's just the client that doesn't know it.
So my suspicion is that the best way to do this is to leave book as required on the PageSerializer, but somehow make it so that the validators on the BookSerializer don't check for whether that is in the POST data when I post to BookSerializer.
Is this the correct way to achieve what I want? And if so, how do I do it? Thank you.
Upvotes: 0
Views: 3335
Reputation: 363
I'd link your ViewSet to a BookCreateSerializer, and from this specific serializer I'd then add a function to not only verify the received data but make sure you link the parent's id to the child's one during creation.
IMPORTANT NOTE
This works if a parent only has one child, not sure about when passing multiple children.
Here is what is could look like.
BookCreateSerializer
:
class BookCreateSerializer(serializers.ModelSerializer):
"""
Serializer to create a new Book model in DB
"""
pages = PageCreateSerializer()
class Meta:
model = Book
fields = [
'title',
'pages'
]
def create(self, validated_data):
page_data = validated_data.pop('page')
book = Book.objects.create(**validated_data)
Page.objects.create(book=book, **page_data)
return book
PageCreateSerializer
class PageCreateSerializer(serializers.ModelSerializer):
"""
Serializer to create a new Page model in DB
"""
class Meta:
model = Page
fields = [
'book',
'text'
]
To make sure that your Book instance understands what a page
field is in the serializer, you have to define a related_name
in its child's Model (Page). The name you choose is up to you. It could look like:
class Page(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='page')
text = models.CharField(max_length=100)
Upvotes: 2
Reputation: 96
Why not try handling it in the create viewset. You can validate the data for the Book object first, before creating it. Then validate the data for the Page object using the created Book object and the other data sent from the request to the page.
Upvotes: 3