如何在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']
问题分析与解决方案
核心问题
- 序列化器source配置错误:
source='User.institute'写法错误,User实例的外键字段是institute,要关联学院名称应指向institute.name,且当前写法无法实现通过名称创建关联的逻辑。 - 视图逻辑错误:直接将请求中的
institute字符串名称赋值给User的institute外键字段,外键需要的是Institute实例或ID,类型不匹配导致报错。 - 未利用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
相关产品推荐
相关产品推荐

