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

如何在Django中通过学院名称关联User外键并修复注册序列化问题

问题描述

我要给User模型添加外键字段并在User序列化器中使用该外键,目前已在User模型中关联了Institute外键,尝试在AdminRegisterSerializer中添加institute_name字段,并通过AdminRegisterAPIView的POST请求传入学院名称来创建管理员用户,但运行报错。以下是相关代码及已尝试的修改:


模型代码(models.py)

class User(AbstractBaseUser, PermissionsMixin, BaseModel):
    STUDENT = '1'
    INSTRUCTOR = '2'
    ADMIN = '3'
    ROLE_CHOICES = (
        (STUDENT, 'student'),
        (INSTRUCTOR, 'instructor'),
        (ADMIN, 'admin')
    )
    id = models.UUIDField(primary_key = True, default = uuid.uuid4, editable = False)
    email = models.EmailField(('email address'),max_length=255)
    username = models.CharField(max_length=255, unique=True ,null=True, blank=True)
    phone_no = encrypt(models.CharField(('phone number'), max_length=15))
    role= models.CharField(max_length=20,choices=ROLE_CHOICES, blank=True, null=True)
    institute = models.ForeignKey(Institute, on_delete=models.SET_NULL, null=True, related_name='institute')
    is_active = encrypt(models.BooleanField(default=True))
    is_staff = encrypt(models.BooleanField(default=False))
    is_superuser = encrypt(models.BooleanField(default=False))

    objects = UserManager()
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email', 'phone_no']

class Institute(BaseModel):
    id = models.UUIDField(primary_key = True, default = uuid.uuid4, editable = False)
    name = encrypt(models.CharField(max_length=255))
    company_website = models.CharField(max_length=255, blank=True)
    brand_name = models.CharField(max_length=255, blank=True)
    registered_address = models.CharField(max_length=255, blank=True)
    company_pan = models.CharField(max_length=255, blank=True)
    company_tan = models.CharField(max_length=255, blank=True)
    company_gstin = models.CharField(max_length=255, blank=True)

    def institute_id(self):
        return self.id.__str__()

序列化器代码(serializers.py)

class AdminRegisterSerializer(ModelSerializer):
    email = serializers.EmailField(required=True)
    password = serializers.CharField(write_only=True, style={'input_type': 'password'}, required=True)
    institute_name = serializers.CharField(source='User.institute')
    class Meta:
        model = User
        fields = ['email', 'username', 'password', 'institute_name']

视图代码(views.py)

class AdminRegisterAPIView(CreateAPIView):
    """该API用于创建新的管理员用户。此前创建用户时密码未哈希导致无法登录,因此开发此API。
    创建完成后,超级管理员可为此用户分配其他权限。"""
    
    serializer_class = AdminRegisterSerializer
    # permission_classes = [IsAdminUser]  

    def post(self, request, *args, **kwargs):
        data = request.data
        email, username=  data['email'], data['username']
        password, institute = data['password'], data['institute']
        user_obj = User.objects.filter(username=username).first()
        if not user_obj:
            user = User.objects.create(username=username, email=email, institute = institute)
            user.set_password(password)
            user.save()
            return Response(data = {'success' : '管理员用户注册成功.'}, status=status.HTTP_200_OK)
        else:
            return Response({"error": "该用户名已存在."}, status=status.HTTP_400_BAD_REQUEST)

已尝试的序列化器修改

institute_name = serializers.CharField(source='User.institute')
class Meta:
    model = User
    fields = ['email', 'username', 'password', 'institute_name']

问题分析与解决方案

核心问题

  1. 序列化器source配置错误:source='User.institute'写法错误,User实例的外键字段是institute,要关联学院名称应指向institute.name,且当前写法无法实现通过名称创建关联的逻辑。
  2. 视图逻辑错误:直接将请求中的institute字符串名称赋值给User的institute外键字段,外键需要的是Institute实例或ID,类型不匹配导致报错。
  3. 未利用DRF序列化器的验证与创建逻辑:视图直接操作数据库,跳过了序列化器的验证流程,不符合DRF的设计规范。

修复后的序列化器

class AdminRegisterSerializer(ModelSerializer):
    email = serializers.EmailField(required=True)
    password = serializers.CharField(write_only=True, style={'input_type': 'password'}, required=True)
    # 用write_only接收前端传入的学院名称
    institute_name = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['email', 'username', 'password', 'institute_name']

    def create(self, validated_data):
        # 取出学院名称并从字典中移除,避免创建User时传入无效字段
        institute_name = validated_data.pop('institute_name')
        # 根据名称查找学院实例,若需要自动创建可改用get_or_create
        try:
            institute = Institute.objects.get(name=institute_name)
        except Institute.DoesNotExist:
            raise serializers.ValidationError({"institute_name": "该学院不存在"})
        
        # 创建用户并设置密码、角色、关联学院
        user = User.objects.create(**validated_data)
        user.set_password(validated_data['password'])
        user.role = User.ADMIN  # 自动设置为管理员角色
        user.institute = institute
        user.save()
        return user

修复后的视图

class AdminRegisterAPIView(CreateAPIView):
    """
    该API用于创建新的管理员用户。此前创建用户时密码未哈希导致无法登录,因此开发此API。
    创建完成后,超级管理员可为此用户分配其他权限。
    """
    serializer_class = AdminRegisterSerializer
    # permission_classes = [IsAdminUser]  # 启用后仅超级管理员可创建管理员账号

说明

  • 序列化器中通过write_only=True让institute_name只接收前端传入的数据,不返回给前端。
  • 重写create方法处理学院名称到实例的转换,同时自动设置用户角色为管理员,避免手动传入。
  • 视图移除自定义的post方法,使用CreateAPIView的默认逻辑,确保序列化器的验证和创建流程被正确执行。
  • 若需要支持不存在的学院自动创建,可将Institute.objects.get改为institute, _ = Institute.objects.get_or_create(name=institute_name)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:37:09