Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deserialize nested objects with Django Rest Framework

Say I have Django models like this:

class Book(models.Model):
  title = models.CharField(max_length=150)
  author = models.CharField(max_length=150) 

class Chapter(models.Model):
  book = models.ForeignKey(Book, related_name='chapters')
  title = models.CharField(max_length=150)
  page_num = models.IntegerField()

and Django Rest Framework classes like this:

class ChapterSerializer(serializers.ModelSerializer):
  class Meta:
    model = Chapter
    fields = ('id', 'title', 'page_num')

class BookSerializer(serializers.ModelSerializer):
  chapters = ChapterSerializer(many=True)

  class Meta:
    model = Book
    fields = ('id', 'title', 'author', 'chapters')

  def create(validated_data):
    chapters = validated_data.pop('chapters')
    book = Book(**validated_data)
    book.save()
    serializer = ChapterSerializer(data=chapters, many=True)
    if serializer.is_valid(raise_exception=True):
        chapters = serializer.save()

class BookCreate(generics.CreateAPIView):
  serializer = BookSerializer(data=request.data)
  if serializer.is_valid(raise_exception=True):
    serializer.save()
  # Do some other stuff

and I POST some JSON like this:

{
  title: "Test book",
  author: "Test author",
  chapters: [
    {title: "Test chapter 1", page_num: 1},
    {title: "Test chapter 2", page_num: 5}
  ]
}

I get an exception because chapter doesn't have a book associated with it. If I add book as one of the fields of ChapterSerializer, then the JSON will fail to validate because the BookSerializer in BookCreate won't validate because it will expect a book id for the chapters, but the book hasn't been created yet. How do I resolve this situation?

Is there a way to have the BookSerializer to validate its own fields and not to validate its chapter's?

like image 896
mathew Avatar asked Jun 10 '15 19:06

mathew


2 Answers

You can pass additional arguments on .save. So I think you just need to pass the newly created book instance to the serializer, e.g.

def create(validated_data):
    chapters = validated_data.pop('chapters')
    book = Book(**validated_data)
    book.save()
    serializer = ChapterSerializer(data=chapters, many=True)
    if serializer.is_valid(raise_exception=True):
        chapters = serializer.save(book=book)
like image 135
Todor Avatar answered Nov 15 '22 03:11

Todor


I think, you should not create another Serializer inside the create()method of a serializer because this is redundant. The validation has already been done by the serializer if you defined it to be the serializer for the model referenced by this field:

class BookSerializer(serializers.ModelSerializer):
    # here you define the serializer to validate your input
    chapters = ChapterSerializer(many=True)

instead you can just create the object, the data has already been validated by the call to is_valid() of the initial serializer. you need to pass the book to the create()method anyways:

def create(validated_data):
    chapters_data = validated_data.pop('chapters')
    book = Book.objects.create(**validated_data)
    for chapter_data in chapters_data:
        Chapter.objects.create(book=book, **chapter_data)
like image 44
trixn Avatar answered Nov 15 '22 05:11

trixn