Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

instance expected, got OrderedDict Django Rest Framework writable nested serializers

I am creating a survey kind of app, so i have three models Form, Questiosn, Choices[for multiple choice questions]

I followed this tutorial http://www.django-rest-framework.org/api-guide/relations/#nested-relationships

It works fine for 1 level nested relations, but for 2 levels it gives

TypeError: 'Choice' instance expected, got OrderedDict([(u'title', u'option1')])

class ChoiceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Choice
        fields = ['title']

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

    class Meta:
    model = Question
    fields = ['title', 'type', 'required','order','choices']

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

class FormSerializer(serializers.ModelSerializer):
    questions = QuestionSerializer(many=True)

    class Meta:
    model = Form
    fields = ['title', 'description', 'created', 'active', 'hash','questions']
    read_only_fields = ['active','hash']

    def create(self, validated_data):
    questions_data = validated_data.pop('questions')
    form = Form.objects.create(**validated_data)
    for question_data in questions_data:
        Question.objects.create(form=form, **question_data)
    return form

EDIT

Solved using the manual way, In FormSerializer override the create method,

@transaction.atomic
    def create(self, validated_data):
        try:
            with transaction.atomic():
                questions_data = validated_data.pop('questions')
                form = Form.objects.create(**validated_data)
                for question_data in questions_data:
                    question = Question.objects.create(form=form,
                                                       title=question_data['title'],
                                                       type=question_data['type'],
                                                       required=question_data['required'])
                    if question.type == Question.RADIO or question.type == Question.CHECKBOX:
                        choices_data = question_data.pop('choices')
                        for choice_data in choices_data:
                            choice = Choice.objects.create(question=question, title=choice_data['title'])
                return form
        except Exception, e:
            raise serializers.ValidationError("Cannot Save Form %s" % e)
like image 340
kartik saraswat Avatar asked Oct 20 '16 23:10

kartik saraswat


1 Answers

I also struggled with this and I believe the proper way to handle this is:

class ChoiceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Choice
        fields = ['title']

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

    class Meta:
        model = Question
        fields = ['title', 'type', 'required','order','choices']


class FormSerializer(serializers.ModelSerializer):
    questions = QuestionSerializer(many=True)

    class Meta:
        model = Form
        fields = ['title', 'description', 'created', 'active', 
                 'hash','questions']
        read_only_fields = ['active','hash']

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

An easy way to screw this up is to not pop choices before creating the Question object. When you do that, you'll get an instance expected, got OrderedDict( 500 error.

Note also that you do not need to define create() on the QuestionSerializer. All child processing is done at the top level.

like image 171
Rob Avatar answered Nov 03 '22 11:11

Rob