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

Django DRF序列化器设置depth=1时如何正常POST创建数据

问题根源

DRF序列化器的depth参数仅用于控制嵌套序列化的只读输出深度,当设置depth≥1时,所有外键、多对多关联字段都会被自动替换为只读的嵌套序列化器,不会接收前端传入的主键值作为写入参数。此时传入的course_name:3、student:1会被序列化器忽略,最终写入数据库时外键字段为空,触发非空约束报错。
尝试的course_name.id这类点语法传参本身也不被DRF默认支持,加之外键字段已被设为只读,自然无法正常创建记录。


最优实现方案

不建议强行让depth=1的序列化器同时支持写入,最稳妥、最符合DRF设计规范的方案是在同一个序列化器中显式区分读写字段,既保留GET请求的嵌套返回结构,又支持POST时传入外键ID创建记录,代码如下:

from rest_framework import serializers
from .models import Course, CourseEnrolment, UserAccount
from datetime import datetime


# 关联模型的简要只读序列化器,用于GET时嵌套返回
class CourseBriefSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = ['id', 'name', 'description', 'prerequisite', 'fee']
        read_only_fields = fields


class UserBriefSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserAccount
        fields = ['id', 'email', 'first_name', 'last_name', 'phone']
        read_only_fields = fields


class CourseEnrolmentSerializer(serializers.ModelSerializer):
    # 只读嵌套字段,GET请求时返回关联详情
    course_name = CourseBriefSerializer(read_only=True)
    student = UserBriefSerializer(read_only=True)
    # 只写字段,POST/PUT时接收前端传的外键ID
    course_id = serializers.PrimaryKeyRelatedField(
        queryset=Course.objects.all(),
        write_only=True,
        source='course_name'  # 写入时自动映射到course_name外键字段
    )
    student_id = serializers.PrimaryKeyRelatedField(
        queryset=UserAccount.objects.all(),
        write_only=True,
        source='student'  # 写入时自动映射到student外键字段
    )

    class Meta:
        model = CourseEnrolment
        fields = [
            'id', 'schedule', 'enrolment_date',
            'course_name', 'student',
            'course_id', 'student_id'
        ]

调用方式

POST创建选课记录时传参格式如下:

{
    "course_id": 3,
    "schedule": "June 23, 2022",
    "student_id": 1
}

GET请求返回的结构和之前用depth=1得到的结果完全一致,不需要调整前端的列表/详情页解析逻辑。


额外注意事项

模型里的enrolment_date字段存在写法问题:

# 错误写法:服务启动时就会固定时间,所有记录的报名时间都一样
enrolment_date = models.DateTimeField(default=datetime.now())
# 正确写法:传函数引用,创建记录时才会调用生成当前时间
enrolment_date = models.DateTimeField(default=datetime.now)

可选替代方案

如果不想修改序列化器字段,也可以在视图层根据请求方法选择不同的序列化器:

  • GET请求使用带depth=1的序列化器返回嵌套数据
  • POST/PUT/PATCH请求使用不带depth的基础序列化器接收外键ID
    但这种方案会让前后端字段逻辑不统一,优先推荐前面的同序列化器区分读写字段的实现。

内容的提问来源于stack exchange,提问作者Mart

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:45:30