Danijela Popović
Danijela Popović

Reputation: 122

Nested fields/objects creation with DRF

It's me again with questions about the polls API I'm working on.

I have three models: Poll, Question and Choice.

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()


class Poll(models.Model):
    title = models.CharField(max_length=200, verbose_name="Naslov ankete")
    description = models.TextField(verbose_name="Opis ankete")
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="polls", verbose_name="Autor")
    pub_date = models.DateTimeField(auto_now_add=True, verbose_name="Datum objavljivanja")

    class Meta:
        ordering = ["pub_date", "title"]
        verbose_name = "Anketa"
        verbose_name_plural = "Ankete"

    def __str__(self):
        return self.title


class Question(models.Model):
    poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="questions", verbose_name="Anketa")
    question_text = models.CharField(max_length=200, verbose_name="Tekst pitanja")

    class Meta:
        # ordering = ["question_text"]
        verbose_name = "Pitanje"
        verbose_name_plural = "Pitanja"

    def __str__(self):
        return self.question_text


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="choices", verbose_name="Pitanje")
    choice_text = models.CharField(max_length=200, verbose_name="Tekst opcije")
    votes = models.IntegerField(default=0, verbose_name="Glasovi")

    class Meta:
        # ordering = ["-votes"]
        verbose_name = "Opcija"
        verbose_name_plural = "Opcije"

    def __str__(self):
        return self.choice_text

Whatever I put in the serializers, I can't achieve the creation of questions and choices at the same time with the poll. Noteworthy: questions and choices ain't required fields (it's possible to create a poll without questions and add them later, and also create a question without choices and add them later).

How should my serializers look like if I want to use the following JSON, but still be able to create questions and choices independently?

{
    "title": "Favorite destinations",
    "description": "A few questions about traveling",
    "questions": [
        {"question_text": "Which destination seems better for you?", "choices": [{"choice_text": "London"}, {"choice_text": "Madrid"}, {"choice_text": "Rome"}, {"choice_text": "Paris"}, {"choice_text": "Berlin"}]},
        {"question_text": "When was the most recent occasion you travelled abroad?", "choices": [{"choice_text": "this month"}, {"choice_text": "less than three months ago"}, {"choice_text": "in the last six months"}, {"choice_text": "last year"}, {"choice_text": "in the last three years"}, {"choice_text": "I don't remember"}]},
        {"question_text": "Where of those would you rather travel?", "choices": [{"choice_text": "sea"}, {"choice_text": "mountains"}, {"choice_text": "desert"}, {"choice_text": "volcano"}]}
    ]
}

Upvotes: 1

Views: 52

Answers (2)

Danijela Popović
Danijela Popović

Reputation: 122

For the curious ones, here's the code created by customizing Reza's answer.

serializers.py

from rest_framework import serializers
from .models import Poll, Question, Choice


class ChoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Choice
        fields = ["id", "choice_text"]


class QuestionSerializer(serializers.ModelSerializer):
    choices = ChoiceSerializer(many=True, required=False)

    class Meta:
        model = Question
        fields = ["id", "question_text", "choices"]

    def create(self, validated_data):
        if "choices" in validated_data:
            choices_data = validated_data.pop("choices")
            question = Question.objects.create(**validated_data)
            for choice_data in choices_data:
                choice = Choice.objects.create(question=question, **choice_data)
            return question
        return Question.objects.create(**validated_data)


class PollSerializer(serializers.ModelSerializer):
    author = serializers.HiddenField(default=serializers.CurrentUserDefault())
    questions = QuestionSerializer(many=True, required=False)

    class Meta:
        model = Poll
        fields = "__all__"

    def create(self, validated_data):
        if "questions" in validated_data:
            questions_data = validated_data.pop("questions")
            poll = Poll.objects.create(**validated_data)
            for question_data in questions_data:
                if "choices" in question_data:
                    choices_data = question_data.pop("choices")
                    question = Question.objects.create(poll=poll, **question_data)
                    for choice_data in choices_data:
                        choice = Choice.objects.create(question=question, **choice_data)
            return poll
        return Poll.objects.create(**validated_data)


class VoteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Choice
        fields = ["votes"]

    def update(self, instance, validated_data):
        instance.votes += 1
        instance.save()
        return instance


class ChoiceWithVotesSerializer(serializers.ModelSerializer):
    class Meta:
        model = Choice
        fields = "__all__"


class QuestionWithVotesSerializer(serializers.ModelSerializer):
    choices = ChoiceWithVotesSerializer(many=True, required=False)

    class Meta:
        model = Question
        fields = "__all__"


class PollDetailsSerializer(serializers.ModelSerializer):
    questions = QuestionWithVotesSerializer(many=True)

    class Meta:
        model = Poll
        fields = "__all__"

There are also serializers showing the votes, used in the views with a custom IsPollAuthor permission - available only to the author of the poll. And, of course, a serializer to allow voting to the authenticated users. :)

Upvotes: 0

Reza Heydari
Reza Heydari

Reputation: 1211

You must use Nested Serializers like that :

class ChoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Choice
        fields = "__all__"

class QuestionSerializer(serializers.ModelSerializer):
    choices = ChoiceSerializer(many=True)
    class Meta:
        model = Question
        fields = "__all__"

class PollSerializer(serializers.ModelSerializer):
    questions = QuestionSerializer(many=True)
    class Meta:
        model = Poll
        fields = "__all__"

    def create(self, validated_data):
        questions_data = validated_data.pop('questions')
        poll = Poll.objects.create(**validated_data)
        for question_data in questions_data:
            choices_data = question_data.pop('choices')
            question = Question.objects.create(poll=poll, **question_data)
            for choice_data in choices_data:
                choices = Choice.objects.create(question=question, **choice_data)
        return poll

Upvotes: 1

Related Questions