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',)
原因分析
- 表单字段格式验证失败:
AccountAuthenticationForm继承自ModelForm,模型中email字段是EmailField,表单会默认执行邮箱格式校验。输入手机号时不符合邮箱格式,直接导致字段验证失败,form.is_valid()返回False,后续clean逻辑无法执行。 - clean方法逻辑错误:
clean方法中调用self.is_valid()属于循环调用——clean本身是表单验证流程的一部分,此时验证尚未完成,调用is_valid()会触发重复验证,进一步导致失败。 - 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
相关产品推荐
相关产品推荐

