pythondjangodjango-rest-frameworkdjango-serializer

DRF: Serialize field with different serializers and many=True


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


Solution

  • 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:)