Django休息框架M2M序列化程序保存嵌套对象

时间:2018-04-30 12:38:18

标签: django django-rest-framework

我有两个模型:任务和场景。事先创建任务,然后通过包含一些现有任务来创建方案。重要的是在一个场景中,任务必须按照特定的顺序排列,而不是根据他们的ID。

class Task(models.Model):
    stakeholder = models.ForeignKey(User, related_name='tasks', blank=True, )
    project = models.ForeignKey(Project, related_name='project_tasks' )
    title = models.CharField(max_length=50, blank=True, null = True, )
    ...

class Scenario(models.Model):
    stakeholder = models.ForeignKey(User, related_name='scenarios', blank=True,)
    tasks = models.ManyToManyField(Task, blank=True)

串行器:

class TaskSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    class Meta:
        model = Task
        fields = '__all__'

class ScenarioSerializer(serializers.ModelSerializer):
    tasks = TaskSerializer(many=True, required=False)
    class Meta:
        model = Scenario
        fields = '__all__'

    def get_or_create_task(self, data):
        qs  = Task.objects.filter(pk=data.get('id'))
        if qs.exists():
            return qs.first()
        task = Task.objects.create(**data)
        return task

    def add_tasks(self, instance, tasks):
        for task_data in tasks:
            task = self.get_or_create_task(task_data)
            instance.tasks.add(task)

    def create(self, validated_data):
        tasks = validated_data.pop('tasks')
        instance = Scenario.objects.create(**validated_data)
        self.add_tasks(instance, tasks)
        return instance

    def update(self, instance, validated_data):
        tasks = validated_data.pop('tasks', [])
        instance = super().update(instance, validated_data)
        self.add_tasks(instance, tasks)
        return instance

我的要求很少:

在检索场景时,我想检索相应的任务对象而不仅仅是它们的ID,这就是为什么行id = serializers.IntegerField()存在于TaskSerializer中。

使用此代码成功创建了一个新任务,但是任务更新在例如/api/tasks/1时失败,因为它还期望请求正文中的id。如果没有id = serializers.IntegerField(),任务创建和更新都会成功。

此外,在TaskSerializer中没有id = serializers.IntegerField()方案创建成功并且包含的​​任务以正确的顺序返回,比如task3,task1,Task5。但是,在创建场景时包含的任何任务都会在数据库中再次创建(当然还有连续的ID)。

取消注释id = serializers.IntegerField()会导致成功创建场景,并且不会自动创建额外任务(这很好),但场景会返回其ID中的任务,即如果使用Task3,task1,task5创建场景然后当你获得一个场景时,你会得到task1,task3,task5。

为什么会这样?

额外注意:我不打算在创建场景时创建任务,事先总是创建任务,然后通过将一些现有任务附加到场景来创建场景

1 个答案:

答案 0 :(得分:1)

您可以尝试将tasks设置为read_only并从context获取数据,并保存您可能使用的创建订单extra select

class ScenarioSerializer(serializers.ModelSerializer):
    tasks = serializers.SerializerMethodField()

    class Meta:
        model = Scenario
        fields = '__all__'

    def get_tasks(self, obj):
        qs = obj.tasks.extra(
            select={'creation_seq': 'scenario_scenario_tasks.id'}
            ).order_by("creation_seq")
        return TaskSerializer(qs, many=True).data

    def get_or_create_task(self, data):
        qs = Task.objects.filter(pk=data.get('id'))
        if qs.exists():
            return qs.first()
        serializer = TaskSerializer(data=data)
        serializer.is_valid(raise_exception=True)
        task = serializer.save()
        return task

    def add_tasks(self, instance, data):
        tasks = data.get('tasks', [])
        for task_data in tasks:
            task = self.get_or_create_task(task_data)
            instance.tasks.add(task)

    def create(self, validated_data):
        instance = super().create(validated_data)
        # for python 2
        # instance = super(ScenarioSerializer, self).create(validated_data)
        data = self.context.get('request').data
        #      HERE ^^^^^^^^^^
        self.add_tasks(instance, data)
        return instance

    def update(self, instance, validated_data):
        instance = super().update(instance, validated_data)
        # for python 2
        # instance = super(ScenarioSerializer, self).update(instance, validated_data)
        data = self.context.get('request').data
        #      HERE ^^^^^^^^^^
        self.add_tasks(instance, data)
        return instance

drfdoc-demo您可以查看drfdoc

基于类的视图的完整示例