自定义模型场景下Serializer返回None?原因排查求助
问题排查:自定义User模型登录时password无法从serializer.data获取
问题描述
开发自定义User模型的登录功能时,发现UserLoginSerializer的serializer.data中password始终为None。请求数据request.data包含密码,但serializer.data仅返回email,最终导致authenticate返回None。
相关代码
views.py
class UserLoginView(APIView): def post(self, request, format=None): serializer = UserLoginSerializer(data=request.data,) if serializer.is_valid(raise_exception=True): email = serializer.data.get('email') password = serializer.data.get('password') print(password) print(request.data) print(serializer.data) user = authenticate(email=email, password=password) print(user) return Response({'msg':'successful login'}, status=status.HTTP_200_OK)
Serializer.py
class UserLoginSerializer(serializers.ModelSerializer): email = serializers.EmailField(max_length=255) class Meta: model = User fields = ['email', 'password',] extra_kwargs={ 'password':{'write_only': True}, }
Models.py
class UserManager(BaseUserManager): def create_user(self, email, number, name, password=None): if not email: raise ValueError('Users must have an email address') if not number: raise ValueError('Users must have a phone number') user = self.model( email = self.normalize_email(email), number = number, name = name, ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, number, name, password=None): user = self.create_user( email, number = number, password = password, name = name, ) user.is_admin = True user.save(using=self._db) return user class User(AbstractBaseUser): email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) number=models.CharField(max_length=10) name=models.CharField(max_length=30) is_admin = models.BooleanField(default=False) objects = UserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name','number'] def __str__(self): return self.email def has_perm(self, perm, obj=None): return self.is_admin def has_module_perms(self, app_label): return True @property def is_staff(self): return self.is_admin
输出结果
None {'email': 'g@gmail.com', 'password': '123'} {'email': 'g@gmail.com'} None [06/Feb/2023 18:53:52] "POST /api/user/login/ HTTP/1.1" 200 26
原因分析
write_only: True的作用:在UserLoginSerializer的extra_kwargs中设置'password':{'write_only': True},表示该字段仅用于接收请求输入,不会被序列化输出到serializer.data中,所以从serializer.data取password必然得到None。serializer.data的特性:serializer.data是序列化后的输出数据,对于write_only字段,Django REST Framework会自动排除,不会包含在其中。authenticate的要求:authenticate需要传入明文密码进行比对,但serializer.data即使包含密码,也只会是数据库中存储的哈希值,无法直接用于验证。
解决方案
方法1:从request.data直接获取明文密码
class UserLoginView(APIView): def post(self, request, format=None): serializer = UserLoginSerializer(data=request.data,) if serializer.is_valid(raise_exception=True): email = serializer.data.get('email') # 直接从请求数据中获取明文密码 password = request.data.get('password') user = authenticate(email=email, password=password) if user: return Response({'msg':'successful login'}, status=status.HTTP_200_OK) else: return Response({'msg':'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
方法2:从serializer.validated_data获取验证后的密码(更规范)
class UserLoginView(APIView): def post(self, request, format=None): serializer = UserLoginSerializer(data=request.data,) if serializer.is_valid(raise_exception=True): # 从验证后的输入数据中取值 email = serializer.validated_data.get('email') password = serializer.validated_data.get('password') user = authenticate(email=email, password=password) if user: return Response({'msg':'successful login'}, status=status.HTTP_200_OK) else: return Response({'msg':'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
关键注意事项
- 不要移除
password字段的write_only: True配置,这是安全最佳实践,避免密码被返回给前端。 authenticate方法会自动处理明文密码与数据库哈希密码的比对,无需手动处理哈希逻辑。
内容的提问来源于stack exchange,提问作者Adesh Yadav
相关产品推荐
相关产品推荐

