PAYATHON 2026

如何在 Django 注册时同时创建用户和角色资料?

数据小许

结论

可以在一次请求中创建用户及其资料。应由一个明确的流程负责创建资料:可以在自定义注册序列化器的 create() 中创建,也可以由信号创建空资料,再由序列化器更新。避免信号和注册序列化器同时创建同一条资料记录。

对于需要接收嵌套资料字段的注册接口,把校验和保存统一放在注册序列化器中通常更直接。将用户和资料的写入放在同一个数据库事务中,可以避免用户创建成功、资料保存失败后留下不完整记录。

精简注册载荷

user 是由注册流程确定的关联对象,客户端不应传入。嵌套资料序列化器应排除该字段,或将其设为只读;保存资料时,由服务端传入刚创建的用户。

当前示例的 role 是 admin,但请求中同时提交了教师和学生资料。接口应明确每个角色允许提交哪些资料,并拒绝角色不匹配或同时提交多种角色资料的请求。

在自定义序列化器中创建资料

以下示例假设 User.TEACHER、User.STUDENT 是项目中定义的角色值,请替换为实际使用的常量。资料序列化器不应包含可写的 user 字段。

from django.db import transaction
from rest_framework import serializers


class CustomUserCreateSerializer(UserCreateSerializer):
    teacher_profile = TeacherProfileSerializer(required=False)
    student_profile = StudentProfileSerializer(required=False)

    class Meta(UserCreateSerializer.Meta):
        model = User
        fields = (
            "id",
            "email",
            "password",
            "role",
            "first_name",
            "last_name",
            "teacher_profile",
            "student_profile",
        )

    def validate(self, attrs):
        attrs = super().validate(attrs)
        role = attrs.get("role")
        teacher_data = attrs.get("teacher_profile")
        student_data = attrs.get("student_profile")

        if teacher_data and student_data:
            raise serializers.ValidationError(
                "一次注册只能提交一种角色资料。"
            )
        if teacher_data and role != User.TEACHER:
            raise serializers.ValidationError(
                {"teacher_profile": "该资料与用户角色不匹配。"}
            )
        if student_data and role != User.STUDENT:
            raise serializers.ValidationError(
                {"student_profile": "该资料与用户角色不匹配。"}
            )
        return attrs

    @transaction.atomic
    def create(self, validated_data):
        teacher_data = validated_data.pop("teacher_profile", None)
        student_data = validated_data.pop("student_profile", None)

        user = super().create(validated_data)

        if user.role == User.TEACHER and teacher_data is not None:
            TeacherProfile.objects.create(user=user, **teacher_data)
        elif user.role == User.STUDENT and student_data is not None:
            StudentProfile.objects.create(user=user, **student_data)

        return user

DRF 会先校验声明的嵌套序列化器,再调用 create()。示例中直接创建对应资料,并由服务端设置 user。如果创建用户或写入资料时抛出异常,transaction.atomic 会回滚整个事务。

如果业务要求每个用户都必须有一条资料记录,可以在同一个事务中创建空资料;如果资料是可选的,则像示例一样,只在收到相应嵌套数据时创建。

处理 djoser 的密码确认序列化器

如果项目使用密码确认注册序列化器,也需要让它采用相同的嵌套字段、角色校验和资料创建逻辑。可以抽出公共基类或 mixin,避免两份 create() 逻辑逐渐不一致。保留对应 djoser 父类的密码处理逻辑,并确认自定义序列化器已按项目所用版本配置到 djoser 的用户创建序列化器设置中。

信号与文件上传

如果继续使用 post_save 信号创建资料,就不要再由注册序列化器创建一次。可以让信号创建空资料,再由注册流程更新,但这样会增加信号与注册逻辑之间的耦合。无论采用哪种方式,都应在事务中写入用户和资料,并确保每个用户的资料只创建一次。

如果 profile_picture 是图片文件字段,JSON 中的 "string" 只是字符串,并不等于上传文件。接口需要使用支持文件上传的请求格式(通常是 multipart/form-data),并确认当前解析器和序列化器能够接收嵌套字段。若客户端不便提交嵌套文件,可以先完成注册,再单独上传图片。

备注:内容仅供参考。