登录过程中带有 (user.is_active =False) 标志的用户的消息

Ale*_*ich 3 django django-forms django-authentication

我试图在登录过程中添加消息,对于拥有帐户但已停用的用户,如果他想进入,他必须激活它。

我使用 LoginView 控制器,它使用称为 AuthenticationForm 的内置标准表单

AuthenticationForm 有以下方法:


def confirm_login_allowed(self, user):
    """
    Controls whether the given User may log in. This is a policy setting,
    independent of end-user authentication. This default behavior is to
    allow login by active users, and reject login by inactive users.

    If the given user cannot log in, this method should raise a
    ``forms.ValidationError``.

    If the given user may log in, this method should return None.
    """
    if not user.is_active:
        raise forms.ValidationError(
            self.error_messages['inactive'],
            code='inactive',

# and list of error messages within this class

error_messages = {
    'invalid_login': _(
        "Please enter a correct %(username)s and password. Note that both "
        "fields may be case-sensitive."
    ),
    'inactive': _("This account is inactive."),
}
Run Code Online (Sandbox Code Playgroud)

因此,从技术上讲,如果不是 user.is_active - 它应该显示消息“inactive”,但在我的情况下,对于具有 is_active = False DB 表的未激活用户,它会显示消息“invalid_login”。我正在尝试 100% 正确的登录名和密码,但用户未处于活动状态,但它向我显示了“invalid_login”消息。然后我只需将 DB 中的 is_active 标志切换为 True,它就可以让我轻松进入。你知道为什么会这样吗?

最终目标是向拥有帐户但已停用的用户显示此消息“'inactive': _("This account is inactive.")”。(或自定义消息)从技术上讲它应该可以工作,但它没有。在此先感谢您,如果您发现这个问题很初级或很愚蠢,我们深表歉意。

尝试:


class AuthCustomForm(AuthenticationForm):
    def clean(self):
        AuthenticationForm.clean(self)
        user = ExtraUser.objects.get(username=self.cleaned_data.get('username'))
        if not user.is_active and user:
            messages.warning(self.request, 'Please Activate your account',
                             extra_tags="", fail_silently=True)
           # return HttpResponseRedirect(' your url'))

Run Code Online (Sandbox Code Playgroud)

最后有什么帮助:


class AuthCustomForm(AuthenticationForm):
    def clean(self):
        AuthenticationForm.clean(self)
        user = ExtraUser.objects.get(username=self.cleaned_data.get('username'))
        if not user.is_active and user:
            messages.warning(self.request, 'Please Activate your account',
                             extra_tags="", fail_silently=True)
           # return HttpResponseRedirect(' your url'))

Run Code Online (Sandbox Code Playgroud)

这是一种奇怪的方法,因为 DJANGO 内置代码应该可以工作。我不确定我没有解决我自己的错误,在这里之前犯过。也许我让事情变得更糟。

Mar*_*sey 10

这是一个很长的答案,但希望它会有用,并提供一些有关幕后工作方式的见解。

要了解为什么'inactive' ValidationError不为非活动用户引发 ,我们必须首先查看 是如何LoginView实现的,特别是它的post方法。

def post(self, request, *args, **kwargs):
    """
    Handle POST requests: instantiate a form instance with the passed
    POST variables and then check if it's valid.
    """
    form = self.get_form()
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)
Run Code Online (Sandbox Code Playgroud)

当LoginView收到POST包含表单数据的请求时调用此方法。使用请求中的数据get_form填充,然后检查表单,根据它是否有效返回不同的响应。我们关心表单检查,所以让我们深入研究该方法在做什么。AuthenticationFormPOSTis_valid

在Django文档做解释的形式和现场验证是如何工作的,所以我不会去太多的细节方面做得很好。基本上,我们需要知道的是,当is_valid调用表单的方法时,表单首先单独验证其所有字段,然后调用其clean方法进行任何表单范围的验证。

这是我们需要查看的AuthenticationForm实现方式,因为它定义了自己的clean方法。

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

    if username is not None and password:
        self.user_cache = authenticate(self.request, username=username, password=password)
        if self.user_cache is None:
            raise self.get_invalid_login_error()
        else:
            self.confirm_login_allowed(self.user_cache)

    return self.cleaned_data
Run Code Online (Sandbox Code Playgroud)

这就是confirm_login_allowed您确定的方法发挥作用的地方。我们看到用户名和密码被传递给authenticate函数。这将根据AUTHENTICATION_BACKENDS设置定义的所有身份验证后端检查给定的凭据(有关更多信息,请参阅Django 文档),User如果成功None则返回已验证用户的模型,否则返回。

authenticate然后检查结果。如果是None,则用户无法通过身份验证,并且'invalid_login' ValidationError会按预期引发。如果没有,则用户已通过身份验证,如果用户处于非活动状态,则confirm_login_allowed引发'inactive' ValidationError。


那为什么不'inactive' ValidationError加注呢?

这是因为非活动用户无法进行身份验证,因此authenticate返回None,这意味着get_invalid_login_error被调用而不是confirm_login_allowed。


为什么非活动用户无法通过身份验证?

为了看到这一点,我将假设您没有使用自定义身份验证后端,这意味着您的AUTHENTICATION_BACKENDS设置被设置为默认值:['django.contrib.auth.backends.ModelBackend']。这意味着这ModelBackend是唯一使用的身份验证后端,我们可以查看它的authenticate方法,即之前看到的authenticate函数在内部调用的方法。

def authenticate(self, request, username=None, password=None, **kwargs):
    if username is None:
        username = kwargs.get(UserModel.USERNAME_FIELD)
    if username is None or password is None:
        return
    try:
        user = UserModel._default_manager.get_by_natural_key(username)
    except UserModel.DoesNotExist:
        # Run the default password hasher once to reduce the timing
        # difference between an existing and a nonexistent user (#20760).
        UserModel().set_password(password)
    else:
        if user.check_password(password) and self.user_can_authenticate(user):
            return user
Run Code Online (Sandbox Code Playgroud)

我们对最后if一句话很感兴趣。

if user.check_password(password) and self.user_can_authenticate(user):
    return user
Run Code Online (Sandbox Code Playgroud)

对于我们的非活动用户,我们知道密码是正确的,因此check_password将返回True。这意味着它必须user_can_authenticate是返回False并导致不活动用户未通过身份验证的方法。等等,因为我们快到了……

def user_can_authenticate(self, user):
    """
    Reject users with is_active=False. Custom user models that don't have
    that attribute are allowed.
    """
    is_active = getattr(user, 'is_active', None)
    return is_active or is_active is None
Run Code Online (Sandbox Code Playgroud)

啊哈! user_can_authenticate返回Falseif user.is_activeisFalse导致用户未进行身份验证。


解决方案

我们可以将设置子类化ModelBackend、覆盖user_can_authenticate,并将AUTHENTICATION_BACKENDS设置指向这个新的子类。

应用程序/后端.py

from django.contrib.auth import backends


class CustomModelBackend(backends.ModelBackend):
    def user_can_authenticate(self, user):
        return True
Run Code Online (Sandbox Code Playgroud)

设置.py

AUTHENTICATION_BACKENDS = [
    'app.backends.CustomModelBackend',
]
Run Code Online (Sandbox Code Playgroud)


我认为这个解决方案比改变get_invalid_login_error.

然后,您可以'inactive' ValidationError通过子类化AuthenticationForm、覆盖error_messages并将 的authentication_form属性设置LoginView为这个新子类来覆盖消息。

from django.contrib.auth import forms as auth_forms, views as auth_views
from django.utils.translation import gettext_lazy as _


class CustomAuthenticationForm(auth_forms.AuthenticationForm):
    error_messages = {
        'invalid_login': _(
            "Please enter a correct %(username)s and password. Note that both "
            "fields may be case-sensitive."
        ),
        'inactive': _("CUSTOM INACTIVE MESSAGE."),
    }


class LoginView(auth_views.LoginView):
    authentication_form = CustomAuthenticationForm
Run Code Online (Sandbox Code Playgroud)