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

Django自定义登录:邮箱正常手机号无法登录,form.is_valid()返回False

自定义用户认证:手机号登录form验证失败问题解决

问题描述

实现支持邮箱/手机号登录的自定义用户认证功能时,邮箱登录一切正常,但手机号登录无反应。查看views.py发现form.is_valid()返回False,尽管backends.py能正确返回对应用户对象,仍无法完成登录。

相关代码

views.py

def login_request(request):
    user = request.user
    if user.is_authenticated:
        return redirect("account:home")
    if request.method == "POST":
        form = AccountAuthenticationForm(request.POST)
        print('line 28 views', request.POST)
        print('line 29 views', form.is_valid()) 
        if form.is_valid():
            email = request.POST['email']
            print('line 31 email:', email)
            password = request.POST['password']
            user = authenticate(username=email, password=password)
            if user:
                login(request, user)
                print('fine')
                return redirect("account:home")
            else:
                print(user, 'line38')
    form = AccountAuthenticationForm()
    return render(request, "account/login.html", {"login_form": form})

models.py

class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_("email address"), unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)
    first_name = models.CharField(max_length=20, null=True, blank=True)
    last_name = models.CharField(max_length=20, null=True, blank=True)
    phone = PhoneNumberField(unique=True, null=True, blank=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

forms.py

class AccountAuthenticationForm(forms.ModelForm):
    password = forms.CharField(label='Password', widget=forms.PasswordInput)
    email = forms.CharField(label='Email or phone number')

    class Meta:
        model = CustomUser
        fields = ('email', 'password',)

    def clean(self):
        if self.is_valid():
            email = self.cleaned_data['email']
            password = self.cleaned_data['password']
            print('line 48', email, password)
            if not authenticate(username=email, password=password):
                raise forms.ValidationError("Invalid login")

backends.py

class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = CustomUser.objects.get(Q(email=username) | Q(phone=username))
            print('line 12', username, 'username')
            print('line 13', user)
        except CustomUser.DoesNotExist:
            CustomUser().set_password(password)
        except MultipleObjectsReturned:
            return CustomUser.objects.filter(email=username).order_by('id').first()
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                print('line 20 backends.py', user)
                return user

    def get_user(self, user_id):
        try:
            user = CustomUser.objects.get(pk=user_id)
        except CustomUser.DoesNotExist:
            return None

        return user if self.user_can_authenticate(user) else None

settings.py配置

AUTHENTICATION_BACKENDS = ('account.backends.EmailBackend',)

原因分析

  1. 表单字段格式验证失败:AccountAuthenticationForm继承自ModelForm,模型中email字段是EmailField,表单会默认执行邮箱格式校验。输入手机号时不符合邮箱格式,直接导致字段验证失败,form.is_valid()返回False,后续clean逻辑无法执行。
  2. clean方法逻辑错误:clean方法中调用self.is_valid()属于循环调用——clean本身是表单验证流程的一部分,此时验证尚未完成,调用is_valid()会触发重复验证,进一步导致失败。
  3. backends.py异常处理无效:CustomUser.DoesNotExist分支中CustomUser().set_password(password)无意义,找不到用户时应直接返回None。

解决方案

方案1:改用Form类实现登录表单(推荐)

登录表单无需绑定模型,改用forms.Form更合适,同时修正字段命名和验证逻辑:

# forms.py修改后
class AccountAuthenticationForm(forms.Form):
    login_identifier = forms.CharField(label='Email or phone number')
    password = forms.CharField(label='Password', widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        login_identifier = cleaned_data.get('login_identifier')
        password = cleaned_data.get('password')

        if login_identifier and password:
            user = authenticate(username=login_identifier, password=password)
            if not user:
                raise forms.ValidationError("Invalid login credentials")
        return cleaned_data

同时修改views.py中获取字段的代码:

# views.py中POST分支修改
if form.is_valid():
    login_identifier = request.POST['login_identifier']
    password = request.POST['password']
    user = authenticate(username=login_identifier, password=password)
    # 后续逻辑不变

方案2:保留ModelForm,自定义字段验证

若坚持使用ModelForm,需重写字段验证逻辑,允许邮箱或手机号格式:

# forms.py修改后
class AccountAuthenticationForm(forms.ModelForm):
    password = forms.CharField(label='Password', widget=forms.PasswordInput)
    email = forms.CharField(label='Email or phone number')

    class Meta:
        model = CustomUser
        fields = ('email', 'password',)

    def clean_email(self):
        identifier = self.cleaned_data.get('email')
        # 根据实际需求调整正则规则
        email_pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        # 示例:匹配11位国内手机号
        phone_pattern = r'^\d{11}$'
        
        if not re.match(email_pattern, identifier) and not re.match(phone_pattern, identifier):
            raise forms.ValidationError("Please enter a valid email or phone number")
        return identifier

    def clean(self):
        cleaned_data = super().clean()
        identifier = cleaned_data.get('email')
        password = cleaned_data.get('password')

        if identifier and password:
            user = authenticate(username=identifier, password=password)
            if not user:
                raise forms.ValidationError("Invalid login credentials")
        return cleaned_data

修正backends.py异常处理

# backends.py中异常分支修改
except CustomUser.DoesNotExist:
    return None  # 找不到用户直接返回None

内容的提问来源于stack exchange,提问作者Matthew Williams

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 10:37:18