You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django REST中实现带through的多对多模型POST操作

解决Django REST框架中多对多Through模型的读写与嵌套返回问题

我之前刚好踩过这个坑,给你一步步拆解怎么实现需求:

1. 实现GET请求返回嵌套的Through关联信息

首先需要为你的CaseICD10Connection模型写一个序列化器,用来序列化中间表的关联信息;然后在KeyDateCase的序列化器中嵌套这个序列化器,指定对应的反向关联源。

from rest_framework import serializers
from .models import KeyDateCase, CaseICD10Connection, ICD10

# 先定义中间表的序列化器
class CaseICD10ConnectionSerializer(serializers.ModelSerializer):
    # 如果需要展示ICD10的详情,可以嵌套ICD10的序列化器
    icd10 = serializers.ModelSerializer(read_only=True, fields=['id', 'code', 'name'])

    class Meta:
        model = CaseICD10Connection
        fields = ['id', 'icd10', '其他需要展示的中间表字段']  # 替换成你的实际字段

# 再定义主模型的序列化器
class KeyDateCaseSerializer(serializers.ModelSerializer):
    # 嵌套中间表的序列化器,source是反向关联的默认名称(模型小写_set)
    diagnoses_all_icd_10_details = CaseICD10ConnectionSerializer(
        source='caseicd10connection_set',
        many=True,
        read_only=True
    )
    # 原多对多字段设为只读,因为我们通过中间表处理关联逻辑
    diagnoses_all_icd_10 = serializers.PrimaryKeyRelatedField(
        queryset=ICD10.objects.all(),
        many=True,
        read_only=True
    )

    class Meta:
        model = KeyDateCase
        fields = ['id', '其他主模型字段', 'diagnoses_all_icd_10_details']  # 替换成你的实际字段

这样当你发送GET请求时,就能在返回结果中看到嵌套的中间表关联信息了。

2. 实现Through模型的写入功能

默认的ModelSerializer对带through的多对多字段是只读的,所以需要重写序列化器的create和update方法,手动处理中间表的创建和更新。

class KeyDateCaseSerializer(serializers.ModelSerializer):
    # 保留上面的嵌套序列化器定义
    diagnoses_all_icd_10_details = CaseICD10ConnectionSerializer(
        source='caseicd10connection_set',
        many=True,
        read_only=True
    )
    # 新增一个字段用来接收前端传来的中间表数据,仅用于写入
    icd_connections = serializers.ListField(
        child=serializers.DictField(),
        write_only=True
    )

    class Meta:
        model = KeyDateCase
        fields = ['id', '其他主模型字段', 'diagnoses_all_icd_10_details', 'icd_connections']

    def create(self, validated_data):
        # 先取出传入的中间表数据,从validated_data中移除
        icd_connections_data = validated_data.pop('icd_connections')
        # 创建主模型实例
        key_date_case = KeyDateCase.objects.create(**validated_data)
        # 逐个创建中间表关联
        for connection_data in icd_connections_data:
            # 确保connection_data包含icd10_id和中间表的其他必填字段
            CaseICD10Connection.objects.create(
                case=key_date_case,
                **connection_data
            )
        return key_date_case

    def update(self, instance, validated_data):
        # 取出传入的中间表数据(如果有的话)
        icd_connections_data = validated_data.pop('icd_connections', None)
        # 更新主模型字段
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        # 如果有传入新的中间表数据,先删除旧关联,再创建新的
        if icd_connections_data is not None:
            CaseICD10Connection.objects.filter(case=instance).delete()
            for connection_data in icd_connections_data:
                CaseICD10Connection.objects.create(
                    case=instance,
                    **connection_data
                )
        return instance

前端传入数据示例

发送POST/PUT请求时,前端需要按照下面的格式传入数据:

{
    "其他主模型字段": "对应值",
    "icd_connections": [
        {
            "icd10": 1,  // ICD10的ID
            "中间表其他字段": "对应值"
        },
        {
            "icd10": 2,
            "中间表其他字段": "对应值"
        }
    ]
}

注意事项

  • 确保中间表的字段(除了外键)都有合理的默认值或者前端会传入对应数据,避免创建时出错。
  • 如果中间表有唯一约束(比如一个病例不能重复关联同一个ICD10),要在序列化器或者模型层做校验,防止重复创建。
  • 嵌套序列化器中的source要对应正确的反向关联名称,如果你在ForeignKey里设置了related_name,就用你设置的名称,而不是默认的模型名小写_set。

内容的提问来源于stack exchange,提问作者Gurkenkönig

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:22:14