Django rest framework M2M序列化程序保存嵌套对象

Django rest framework M2M序列化程序保存嵌套对象,django,django-rest-framework,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'

我有两个模型:任务和场景。任务是预先创建的,而场景则是通过包含少量现有任务来创建的。重要的是,在场景中,任务必须按照特定的顺序安排,而不是根据它们的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,这就是为什么TaskSerializer中存在行
id=serializers.IntegerField()

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

另外,如果没有
id=serializers.IntegerField()
,则TaskSerializer场景创建成功,并以正确的顺序返回包含的任务,例如task3、task1、Task5。但是,无论场景创建时包含哪些任务,都会在数据库中再次创建这些任务(当然是使用连续ID)

取消注释
id=serializers.IntegerField()
会成功创建场景,并且不会自动创建额外的任务(这很好),但场景会按其id的顺序返回任务,即如果场景是使用Task3、task1、task5创建的,那么当您获得场景时,会返回task1、Task3、task5

为什么会这样


额外说明:我不打算在场景创建时创建任务,任务总是在创建之前创建,场景将在创建之后通过附加一些现有任务来创建

您可以尝试将
任务设置为
只读
,并从
上下文中获取数据,要保存创建的订单,您可以使用
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基于类视图的完整示例

任务排序的标准是什么?创建场景时,在请求中发送任务的顺序,应该保留该顺序。e、 g.
{tasks:[{id=5,…},{id=2,…},{id=4,…}]}
那么应该保留顺序。当我在TaskSerializer中注释
id=serializers.IntegerField()
时,顺序将保留。您应该在代码中控制顺序。您可以为中间表(
)创建一个模型,添加一个时间戳字段并在类中设置默认顺序。要在更新操作中删除场景中所有存在的任务,您可以使用
instance.tasks.all().delete()
谢谢!成功了:)