Search code examples
djangodjango-rest-framework

Creating foreign fields in Django right after the object creation


I am getting a JSON input with the fields description and choices for a Question model. The Choice model also has a foreign key field pointing to Question with a related name of choices. However, in the JSON, the Choice will come without the question_id field, in order to be assigned after the creation of the Question

I am getting all kind of errors when trying to create a Question and then assigning all incoming to it. Searched a lot for the error Direct assignment to the forward side and didn't found anything that solves my problem

CreateChoiceSerializer does not takes any question_id to be created, I solve the problem of assigning the Question by using the context field

I can't change the related name and the JSON input fields, if this was a possibility I'd have already done it

class CreateQuestionSerializer(serializers.ModelSerializer):
    description = serializers.CharField(max_length=1024, allow_blank=False, required=True)
    choices = CreateChoiceSerializer(many=True, required=True)

    def create(self, validated_data):
        choices = validated_data.pop('choices')

        question = Question.objects.create(**validated_data)

        choices_serializer = CreateChoiceSerializer(data=choices, many=True, context={'question': question})
            if choices_serializer.is_valid():
                choices_serializer.save()
        
        return question


class CreateChoiceSerializer(serializers.ModelSerializer):
    description = serializers.CharField(max_length=1024, allow_blank=False, required=True, allow_null=False)

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

    def create(self, validated_data):
        question = self.context.get('question')
        choice = Choice.objects.create(question=question, **validated_data)
        return choice

EDIT:

Models:

class Question(models.Model):
    id = models.AutoField(primary_key=True)
    description = models.CharField(max_length=1024, blank=False, null=False)


class Choice(models.Model):
    id = models.AutoField(primary_key=True)
    question = models.ForeignKey(Question,
                                 related_name='choices',
                                 on_delete=models.PROTECT)
    description = models.CharField(max_length=1024, blank=False, null=False)

Whenever I set the CreateChoiceSerializer field required to false, Django raises an error Direct assignment to the reverse side of a related set is prohibited


Solution

  • If I were you, I use these serializers to create questions with related choices.

    class CreateChoiceSerializer(serializers.ModelSerializer):
        class Meta:
            model = Choice
            fields = "__all__"
    
    class CreateQuestionSerializer(serializers.ModelSerializer):
        description = serializers.CharField(max_length=1024, allow_blank=False, required=True)
        choices = CreateChoiceSerializer(many=True, required=True)
    
        def create(self, validated_data):
            choices = validated_data.pop('choices')
    
            question = Question.objects.create(**validated_data)
    
            choices_model = []
            for choice in choices:
                choices_model.append(Choice(question_id=question.id, **choice))
            Choice.objects.bulk_create(choices_model)
            return question
    

    If you call the function QuestionSerializer.is_valid, it will validate the choices' value too. So it doesn't need to pass the values to ChoiceSerializer and validate them again in function QuestionSerializer.create.

    I use bulk_create function to insert all the choices at once.

    suggestion: you can use atomic transaction in the function create to be sure that the question and related choices are always created safely, not part of them.

    note: the script that I wrote may not exactly fit your code. because I don't know what your models are.