Django Rest Framework ModelSerializer自定义用户主键字段验证异常问题及需求咨询
问题原因分析
咱们先把你遇到的问题拆解开来看:
为什么已存在的手机号会触发"已存在"错误?
你的User模型里phone_number设为主键(primary_key=True),主键字段天生带有唯一约束。而ModelSerializer会自动根据模型的字段规则生成对应验证器——这里就给phone_number自动加上了UniqueValidator,它的核心逻辑就是检查数据库中是否已有该值,防止重复数据插入。
所以不管你是用这个Serializer做登录还是创建用户,它都会执行这个唯一检查,导致已存在的手机号直接验证失败。为什么不存在的手机号能通过验证?
不存在的手机号满足UniqueValidator的要求(数据库里没有重复值),加上密码字段的格式符合验证规则,整个is_valid()自然就返回True了。
疑问1的解决:获取phone_number的值
当serializer.is_valid()返回False时,validated_data是空的——因为验证没通过,不会生成验证后的数据。但你可以直接从原始输入数据里提取,也就是serializer.initial_data:
phone_number = serializer.initial_data.get('phone_number', None)
initial_data是你传入Serializer的原始请求数据,不管验证是否通过都会完整保留,所以能直接拿到手机号的值。
更适配登录场景的Serializer写法
既然这个Serializer是用来做登录的,而非创建用户,完全不需要ModelSerializer自带的唯一验证。建议换成普通的Serializer,自定义验证逻辑,更贴合登录的业务需求:
class UserLoginSerializer(serializers.Serializer): phone_number = serializers.CharField( validators=[MinLengthValidator(10)], max_length=10 ) password = serializers.CharField( validators=[MinLengthValidator(8)], max_length=225 ) def validate(self, attrs): # 自定义验证:检查用户是否存在、密码是否正确 phone_number = attrs.get('phone_number') password = attrs.get('password') try: user = User.objects.get(phone_number=phone_number) except User.DoesNotExist: raise serializers.ValidationError({"phone_number": "该手机号未注册"}) # 注意:必须用check_password验证,因为密码是哈希存储的 if not user.check_password(password): raise serializers.ValidationError({"password": "密码错误"}) # 将用户对象存入attrs,后续视图可直接调用 attrs['user'] = user return attrs
修改后带来的好处:
- 不会再触发唯一约束验证,已存在的手机号能正常通过格式校验
- 可以主动控制登录的验证逻辑,精准返回用户需要的错误提示
- 验证通过后,
validated_data里会包含phone_number和user对象,方便后续生成token等操作
另外提个小建议:你的User模型里不需要自己定义password字段——AbstractBaseUser已经自带了该字段,且默认实现了密码哈希逻辑。自己定义可能会和默认逻辑冲突,建议删掉自定义的password字段,只保留AbstractBaseUser的默认字段即可。
内容的提问来源于stack exchange,提问作者Piyush Kumar

