DRF: Serialize field with different serializers and many=True

44 views Asked by At

I have the following models in my django rest framework app:

class Question(models.Model):
    class Type(models.TextChoices):
        TEXT = 'text', 'text question'
        RADIO = 'radio', 'choosing one option quesiton'
        CHOICE = 'check', 'choosing multiple options question'
    
    type = models.CharField(max_length=5, choices=Type.choices)
    title = models.CharField()
    description = models.TextField(blank=True, default='')
    
    def __str__(self) -> str:
        return f'{self.type}: {self.title}'

class TextQuestion(Question):
    answer = models.CharField()

class RadioQuestion(Question):
    variants = fields.ArrayField(models.CharField())
    answer = models.CharField()

class ChoiceQuestion(Question):
    variants = fields.ArrayField(models.CharField())
    answers = fields.ArrayField(models.CharField())

class Test(models.Model):
    name = models.CharField()
    creator = models.ForeignKey(User, on_delete=models.CASCADE)
    questions = models.ManyToManyField(Question)```

I want to write serializer for test serialization, but I dont know how to serialize questions field. Because I have different serializer for every question type: What should I do? Maybe it`s better to reorganize my models somehow?

I already writed serializers for all question types:

class TextQuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = TextQuestion
        fields = '__all__'

class RadioQuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = RadioQuestion
        fields = '__all__'

class ChoiceQuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = ChoiceQuestion
        fields = '__all__'

I thought about using serializer.SerializerMethodField() but it this doesnt work with many=True

2

There are 2 answers

2
parsarezaee On BEST ANSWER

I had this issue like this, you can use serializer.SerializerMethodField() and write a serializer for test with isinstance(). For example:

class TestSerializer(serializers.ModelSerializer):
questions = serializers.SerializerMethodField()

class Meta:
    model = Test
    fields = ['id', 'name', 'creator', 'questions']

def get_questions(self, obj):
    questions = obj.questions.all()
    serialized_questions = []
    for question in questions:
        if isinstance(question, TextQuestion):
            serialized_question = TextQuestionSerializer(question).data
            #continiue other tasks
        serialized_questions.append(serialized_question)
    return serialized_questions ##read_only 


class TestSerializer(serializers.ModelSerializer):
questions = serializers.ListField(child=serializers.DictField())

class Meta:
    model = Test
    fields = '__all__'

def create(self, validated_data):
    questions_data = validated_data.pop('questions')
    test = Test.objects.create(**validated_data)
    for question_data in questions_data:
        question_type = question_data.pop('type')
        if question_type == 'text':
            TextQuestion.objects.create(test=test, **question_data)
            #continiue other tasks
    return test

def update(self, instance, validated_data):
    instance.name = validated_data.get('name', instance.name)
    instance.description = validated_data.get('description', instance.description)
    instance.save()

    questions_data = validated_data.get('questions', [])
    for question_data in questions_data:
        question_type = question_data.pop('type')
        question_id = question_data.pop('id', None)
        if question_id:
            if question_type == 'text':
                question = TextQuestion.objects.get(id=question_id)
                #continiue other tasks
            for key, value in question_data.items():
                setattr(question, key, value)
            question.save()
    return instance

You can check this and if it doesn't work let me know:)

0
Gosha On

Big thanks to parsarezaee,I finally solved my problem. I had to write def to_representation(self, instance: Test): to make this code work. This is how final solution looks like:

class TestSerializer(serializers.ModelSerializer):
    questions = serializers.ListField(child=QuestionSerializer())

    class Meta:
        model = Test
        fields = '__all__'

    def create(self, validated_data):
        questions_data = validated_data.pop('questions')
        test = Test.objects.create(**validated_data)
        for question_data in questions_data:
            question_type = question_data['type']
            if question_type == 'text':
                created_question = TextQuestion.objects.create(**question_data)
            elif question_type == 'radio':
                created_question = RadioQuestion.objects.create(**question_data)
            created_question.save()
            test.questions.add(created_question)
        return test

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.description = validated_data.get('description', instance.description)
        instance.save()

        questions_data = validated_data.get('questions', [])
        for question_data in questions_data:
            question_type = question_data.pop('type')
            question_id = question_data.pop('id', None)
            if question_id:
                if question_type == 'text':
                    question = TextQuestion.objects.get(id=question_id)
                elif question_type == 'radio':
                    question = RadioQuestion.objects.get(id=question_id)
                for key, value in question_data.items():
                    setattr(question, key, value)
                question.save()
        return instance

    def to_representation(self, instance: Test):
        ret = OrderedDict()
        ret['id'] = instance.id
        ret['name'] = instance.name
        ret['creator'] = instance.creator.id
        ret['questions'] = []
        for question in instance.questions.all():
            qrepr = OrderedDict()
            qrepr['id'] = question.id
            qrepr['title'] = question.title
            qrepr['description'] = question.description
            qrepr['type'] = question.type
            if question.type == 'text':
                qrepr["answer"] = question.textquestion.answer
            elif question.type == 'radio':
                qrepr["answer"] = question.radioquestion.answer
                qrepr["variants"] = question.radioquestion.variants
            ret['questions'].append(qrepr)
        return ret