Search code examples
djangoformsdjango-authenticationloginview

Override Django LoginView Error Message With Custom AuthenticationForm


I have a class-based view that subclasses LoginView.

from django.contrib.auth.views import LoginView

class CustomLoginView(LoginView):

def get_success_url(self):
    url = self.get_redirect_url()
    return url or reverse_lazy('knowledgebase:user_home', kwargs={
        'username':self.request.user.username,
    })

I want to override the error message if a user's email is not yet active because they have to click a link sent to their email address. The current default message looks like this:

override LoginView error message

Instead of saying:

Please enter a correct email address and password. Note that both fields may be case-sensitive.

I want to say something to the effect of:

Please confirm your email so you can log in.

I tried:

accounts/forms.py

from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import gettext as _

class PickyAuthenticationForm(AuthenticationForm):
    def confirm_login_allowed(self, user):
        if not user.is_active:
            raise forms.ValidationError(
                _("Please confirm your email so you can log in."),
                code='inactive',
            )

accounts/views.py

class CustomLoginView(LoginView): # 1. <--- note: this is a class-based view

    form_class = PickyAuthenticationForm # 2. <--- note: define form here?

    def get_success_url(self):
        url = self.get_redirect_url()
        return url or reverse_lazy('knowledgebase:user_home', kwargs={
            'username':self.request.user.username,
        })

The result is absolutely no effect when I try to log in with a user that does exist, but hasn't verified their email address yet.

AuthenticationForm docs.


Solution

  • Method - 1

    Django uses ModelBackend as default AUTHENTICATION_BACKENDS and which does not authenticate the inactive users.

    This is also stated in Authorization for inactive users sections,

    An inactive user is one that has its is_active field set to False. The ModelBackend and RemoteUserBackend authentication backends prohibits these users from authenticating. If a custom user model doesn’t have an is_active field, all users will be allowed to authenticate.

    So, set AllowAllUsersModelBackend as your AUTHENTICATION_BACKENDS in settings.py

    # settings.py
    
    AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
    

    How much does it affect my Django app?

    It doesn't affect anything other than the authentication. If we look into the source code of AllowAllUsersModelBackend class we can see it just allowing the inactive users to authenticate.


    Method - 2

    Personally, I don't recommend this method since method-1 is the Django way of tackling this issue.

    Override the clean(...) method of PickyAuthenticationForm class and call the AllowAllUsersModelBackend backend as,

    from django.contrib.auth.backends import AllowAllUsersModelBackend
    
    
    class PickyAuthenticationForm(AuthenticationForm):
        def clean(self):
            username = self.cleaned_data.get('username')
            password = self.cleaned_data.get('password')
    
            if username is not None and password:
                backend = AllowAllUsersModelBackend()
                self.user_cache = backend.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
    
        def confirm_login_allowed(self, user):
            if not user.is_active:
                raise forms.ValidationError(
                    "Please confirm your email so you can log in.",
                    code='inactive',
                )

    Result Screenshot

    Screenshot