Django自定义登录表单is_valid校验失败,无法完成邮箱二次验证
Django邮箱二次验证中AuthenticationForm校验失败问题排查与解决
问题回顾
开发Django邮箱二次验证功能时,UserLoginForm(继承自AuthenticationForm)的form.is_valid()始终校验不通过,返回两个错误:
Please enter a correct %(username)s and password. Note that both fields may be case-sensitive.This account is inactive.
已确认输入的邮箱/密码正确,且数据库中用户的is_active字段为True。
核心原因分析
- 未配置用户模型的登录标识字段:自定义
User模型继承AbstractUser但未设置USERNAME_FIELD = 'email',导致Django默认使用username字段作为登录标识,而表单输入的是邮箱,AuthenticationForm校验时无法匹配到用户。 - AuthenticationForm初始化未传入request参数:
AuthenticationForm的clean方法依赖request参数调用authenticate,未传入会导致校验逻辑异常。 - 视图重复调用authenticate:
form.is_valid()已完成用户认证校验,视图中再次调用属于冗余操作,且可能掩盖表单本身的错误来源。
具体修复步骤
1. 修正自定义User模型
在User模型中添加USERNAME_FIELD和REQUIRED_FIELDS,指定邮箱作为登录唯一标识:
from django.contrib.auth.models import AbstractUser from django.db import models from django.utils.translation import gettext_lazy as _ class User(AbstractUser): email = models.EmailField(_("email_address"), unique=True) class RoleChoise(models.TextChoices): standard = "ST", "standart" admin = "AD", "admin" worker = "WK", "worker" inactive = "IA", "banned" role = models.CharField(choices=RoleChoise.choices, default=RoleChoise.standard, max_length=2) # 关键配置:指定邮箱为登录标识 USERNAME_FIELD = 'email' # 必填字段:原username字段需加入(因为默认REQUIRED_FIELDS包含email,现在email作为USERNAME_FIELD) REQUIRED_FIELDS = ['username']
执行数据库迁移:
python manage.py makemigrations python manage.py migrate
2. 调整登录表单(可选优化)
USERNAME_FIELD设置后,AuthenticationForm的username字段会自动关联到邮箱,保留自定义样式即可:
from django.contrib.auth.forms import AuthenticationForm from django import forms class UserLoginForm(AuthenticationForm): username = forms.EmailField( widget=forms.EmailInput(attrs={ "class": "form_input login_input", "placeholder": "email", })) password = forms.CharField( required=True, widget=forms.PasswordInput( attrs={ "class": "form_input password_input", "placeholder": "password", } ), label="", )
3. 修复视图逻辑
- 初始化
UserLoginForm时传入request参数 - 直接通过
form.get_user()获取已认证的用户,无需重复调用authenticate - 优化登录成功后的跳转逻辑:
from django.views.generic.edit import FormView from django.shortcuts import render, redirect from django.urls import reverse_lazy from django.contrib.auth import login from .forms import UserLoginForm, OTPCodeForm from .models import OtpModel class LoginOrSendCodeView(FormView): template_name = 'accounts/login.html' form_class = UserLoginForm success_url = reverse_lazy('login') def post(self, request, *args, **kwargs): if "OTP_code" in request.POST: # 你的OTP验证码处理逻辑 ... else: # 传入request参数给AuthenticationForm form = UserLoginForm(request.POST, request=request) if form.is_valid(): user = form.get_user() if user.role != "IA": if user.two_factor_authentication: otp_model_object = OtpModel(user=user) otp_model_object.save() otp_model_object.send_otp_in_mail() return render(request, 'accounts/login.html', {'form': OTPCodeForm()}) else: login(request, user) # 登录成功后跳转至目标页面 return redirect(self.success_url) else: form.add_error(None, "Пользователь заблокирован") # 表单无效时返回带错误的表单 return render(request, self.template_name, {"form": form})
4. 验证认证后端(确保逻辑正确)
你的EmailBackend逻辑没问题,但可以优化为兼容更多场景:
from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model class EmailBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): UserModel = get_user_model() try: # 直接使用username参数(此时已对应邮箱) user = UserModel.objects.get(email=username) except UserModel.DoesNotExist: return None if user.check_password(password) and self.user_can_authenticate(user): return user return None def get_user(self, user_id): UserModel = get_user_model() try: return UserModel.objects.get(pk=user_id) except UserModel.DoesNotExist: return None
验证方法
- 重启Django服务
- 使用正确的邮箱和密码登录,此时
form.is_valid()应能通过校验,触发OTP验证码发送逻辑 - 若仍有错误,可在视图中打印
form.errors查看具体错误详情:
# 在form.is_valid()前添加 print(form.errors)
内容的提问来源于stack exchange,提问作者Тв0р0бушек
相关产品推荐
相关产品推荐

