Django自定义用户模型API登录报User not found错误求助
问题现象
基于Django开发用户登录API,127.0.0.1:8000/api/register接口可正常创建用户,但调用/api/login接口时返回400错误:
POST /api/login/
HTTP 400 Bad Request
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
错误响应体:
{ "non_field_errors": [ "User not found" ] }
已完成的配置操作:
- 在
settings.py中设置auth_user_model = 'account.AppUser' - 执行过
makemigrations和migrate命令 - 将原
managers.py中的AppUser与CustomUserManager移至models.py文件中
相关代码
account/models.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin from django.contrib.auth.base_user import BaseUserManager from django.utils.translation import gettext_lazy as _ from django.db import models JOB_TYPE = ( ('M', "Male"), ('F', "Female"), ) ROLE = ( ('employer', "Employer"), ('employee', "Employee"), ) '''class User(AbstractUser): username = None email = models.EmailField(unique=True, blank=False, error_messages={ 'unique': "A user with that email already exists.", }) role = models.CharField(choices=ROLE, max_length=10) gender = models.CharField(choices=JOB_TYPE, max_length=1) USERNAME_FIELD = "email" REQUIRED_FIELDS = [] def __str__(self): return self.email def get_full_name(self): return self.first_name+ ' ' + self.last_name objects = CustomUserManager() ''' class CustomUserManager(BaseUserManager): use_in_migrations = True """ Custom user model manager where email is the unique identifiers for authentication instead of usernames. """ def create_user(self, email, password=None): """ Create and save a User with the given email and password. """ if not email: raise ValueError('The Email must be set') if not password: raise ValueError('The Password must be set') email = self.normalize_email(email) user = self.model(email=email) user.set_password(password) user.save() return user def create_superuser(self, email, password=None): if not email: raise ValueError('An email is required.') if not password: raise ValueError('A password is required.') user = self.create_user(email, password) user.is_superuser = True user.save() return user class AppUser(AbstractBaseUser, PermissionsMixin): #user_id = models.AutoField(primary_key=True) user_id = models.AutoField(primary_key=True) email = models.EmailField(max_length=50, unique=True) username = models.CharField(max_length=50) is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] objects = CustomUserManager() def __str__(self): return self.username
account/serializers.py
from rest_framework import serializers from django.contrib.auth import get_user_model, authenticate #from django.core.exceptions import ValidationError UserModel = get_user_model() class UserRegisterSerializer(serializers.ModelSerializer): class Meta: model = UserModel fields = ['email', 'username', 'password'] def create_user(self, clean_data): user_obj = UserModel.objects.create_user( email=clean_data['email'], password=clean_data['password']) user_obj.username = clean_data['username'] user_obj.save() return user_obj class UserLoginSerializer(serializers.Serializer): email = serializers.EmailField() password = serializers.CharField() ''' def check_user(self, data): user = authenticate( request=self.context.get('request'), email=data['email'], password=data['password'] ) ''' def validate(self, data): email = data.get('email') password = data.get('password') if email and password: user = authenticate(request=self.context.get('request'), email=email, password=password) if not user: raise serializers.ValidationError('User not found') if not user.is_active: raise serializers.ValidationError('User account is not active.') return user class UserSerializer(serializers.ModelSerializer): class Meta: model = UserModel fields = ('email', 'username')
account/views.py
'''from django.contrib import auth from django.contrib import messages from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect from django.shortcuts import render, redirect , get_object_or_404 from django.urls import reverse, reverse_lazy from account.forms import * from jobapp.permission import user_is_employee def get_success_url(request): """ Handle Success Url After LogIN """ if 'next' in request.GET and request.GET['next'] != '': return request.GET['next'] else: return reverse('jobapp:home') def employee_registration(request): """ Handle Employee Registration """ form = EmployeeRegistrationForm(request.POST or None) if form.is_valid(): form = form.save() return redirect('account:login') context={ 'form':form } return render(request,'account/employee-registration.html',context) def employer_registration(request): """ Handle Employee Registration """ form = EmployerRegistrationForm(request.POST or None) if form.is_valid(): form = form.save() return redirect('account:login') context={ 'form':form } return render(request,'account/employer-registration.html',context) @login_required(login_url=reverse_lazy('accounts:login')) @user_is_employee def employee_edit_profile(request, id=id): """ Handle Employee Profile Update Functionality """ user = get_object_or_404(User, id=id) form = EmployeeProfileEditForm(request.POST or None, instance=user) if form.is_valid(): form = form.save() messages.success(request, 'Your Profile Was Successfully Updated!') return redirect(reverse("account:edit-profile", kwargs={ 'id': form.id })) context={ 'form':form } return render(request,'account/employee-edit-profile.html',context) def user_logIn(request): """ Provides users to logIn """ form = UserLoginForm(request.POST or None) if request.user.is_authenticated: return redirect('/') else: if request.method == 'POST': if form.is_valid(): auth.login(request, form.get_user()) return HttpResponseRedirect(get_success_url(request)) context = { 'form': form, } return render(request,'account/login.html',context) def user_logOut(request): """ Provide the ability to logout """ auth.logout(request) messages.success(request, 'You are Successfully logged out') return redirect('account:login') ''' from django.contrib.auth import get_user_model, login, logout from rest_framework.authentication import SessionAuthentication from rest_framework.views import APIView from rest_framework.response import Response from .serializers import UserRegisterSerializer, UserLoginSerializer, UserSerializer from rest_framework import permissions, status from .validations import custom_validation, validate_email, validate_password class UserRegister(APIView): permission_classes = (permissions.AllowAny,) def post(self, request): clean_data = custom_validation(request.data) serializer = UserRegisterSerializer(data=clean_data) if serializer.is_valid(raise_exception=True): user = serializer.create(clean_data) if user: return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(status=status.HTTP_400_BAD_REQUEST) class UserLogin(APIView): permission_classes = (permissions.AllowAny,) authentication_classes = (SessionAuthentication,) ## def post(self, request): data = request.data assert validate_email(data) assert validate_password(data) serializer = UserLoginSerializer(data=data) if serializer.is_valid(raise_exception=True): user = serializer.check_user(data) login(request, user) return Response(serializer.data, status=status.HTTP_200_OK) class UserLogout(APIView): permission_classes = (permissions.AllowAny,) authentication_classes = () def post(self, request): logout(request) return Response(status=status.HTTP_200_OK) class UserView(APIView): permission_classes = (permissions.IsAuthenticated,) authentication_classes = (SessionAuthentication,) ## def get(self, request): serializer = UserSerializer(request.user) return Response({'user': serializer.data}, status=status.HTTP_200_OK)
问题根源与修复步骤
1. 登录视图调用未定义方法
UserLogin视图中调用了serializer.check_user(data),但该方法在UserLoginSerializer中被注释,实际生效的是validate方法。
修复:
修改UserLogin视图的post方法,替换调用逻辑并传入请求上下文:
class UserLogin(APIView): permission_classes = (permissions.AllowAny,) authentication_classes = (SessionAuthentication,) def post(self, request): data = request.data assert validate_email(data) assert validate_password(data) serializer = UserLoginSerializer(data=data, context={'request': request}) if serializer.is_valid(raise_exception=True): user = serializer.validated_data login(request, user) return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
2. 登录序列化器逻辑漏洞
当前validate方法未处理email或password为空的情况,直接进入错误分支。
修复:
调整UserLoginSerializer的validate方法:
def validate(self, data): email = data.get('email') password = data.get('password') if not email or not password: raise serializers.ValidationError('邮箱和密码不能为空') user = authenticate(request=self.context.get('request'), email=email, password=password) if not user: raise serializers.ValidationError('User not found') if not user.is_active: raise serializers.ValidationError('用户账号未激活') return user
3. 注册序列化器字段处理不规范
UserRegisterSerializer自定义的create_user方法手动设置username,存在冗余且可能丢失字段。
修复:
修改注册序列化器,利用ModelSerializer自带逻辑:
class UserRegisterSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True) class Meta: model = UserModel fields = ['email', 'username', 'password'] def create(self, validated_data): user = UserModel.objects.create_user( email=validated_data['email'], username=validated_data['username'], password=validated_data['password'] ) return user
4. 用户管理器未处理必填字段
CustomUserManager的create_user方法未接收username,但AppUser的REQUIRED_FIELDS包含该字段。
修复:
更新用户管理器方法:
class CustomUserManager(BaseUserManager): use_in_migrations = True def create_user(self, email, username, password=None): if not email: raise ValueError('必须设置邮箱') if not username: raise ValueError('必须设置用户名') if not password: raise ValueError('必须设置密码') email = self.normalize_email(email) user = self.model(email=email, username=username) user.set_password(password) user.save() return user def create_superuser(self, email, username, password=None): if not email: raise ValueError('必须设置邮箱') if not username: raise ValueError('必须设置用户名') if not password: raise ValueError('必须设置密码') user = self.create_user(email, username, password) user.is_superuser = True user.is_staff = True user.save() return user
5. 验证工具类检查
确保validations.py中的custom_validation、validate_email、validate_password方法能正确校验输入格式,避免因前置验证失败导致认证流程中断。
内容的提问来源于stack exchange,提问作者Ahmed Hussain

