You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

核心原因分析

  1. 未配置用户模型的登录标识字段:自定义User模型继承AbstractUser但未设置USERNAME_FIELD = 'email',导致Django默认使用username字段作为登录标识,而表单输入的是邮箱,AuthenticationForm校验时无法匹配到用户。
  2. AuthenticationForm初始化未传入request参数:AuthenticationForm的clean方法依赖request参数调用authenticate,未传入会导致校验逻辑异常。
  3. 视图重复调用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

验证方法

  1. 重启Django服务
  2. 使用正确的邮箱和密码登录,此时form.is_valid()应能通过校验,触发OTP验证码发送逻辑
  3. 若仍有错误,可在视图中打印form.errors查看具体错误详情:
# 在form.is_valid()前添加
print(form.errors)

内容的提问来源于stack exchange,提问作者Тв0р0бушек

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 11:22:24