Opened 2 years ago

Closed 2 years ago

#20886 closed Bug (invalid)

auth.forms.AuthenticationForm incompatible with auth.backends.ModelBackend when using custom user model

Reported by: anentropic Owned by: nobody
Component: contrib.auth Version: 1.5
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description

If you have a custom user model which does not have a field called username you are supposed to be able to use a different field (eg email) and specify USERNAME_FIELD = 'email' on your custom model.

I'm trying to use the built-in login view. It looks fine when I bring up the page, it has recognised my USERNAME_FIELD and displays 'Email address' as the label.

However I can't login. I have looked into why - it's because there is this code in auth.forms.AuthenticationForm

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

        if username and password:
            self.user_cache = authenticate(username=username,
                                           password=password)

...where username here is the name of the field on the form and is not wrong. The problem is when it calls authenticate(username=username...

The authenticate method of ModelBackend looks like:

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)

which tries to integrate our custom USERNAME_FIELD... but we can see we're doing it wrong back in AuthenticationForm.

Change History (1)

comment:1 Changed 2 years ago by anentropic

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to invalid
  • Status changed from new to closed

Um sorry, my bad. I had saved plaintext into the password field for my test user, which is why authentication was failing.

The code above looked wrong at first glance, but actually it's okay because in ModelBackend the next lines do this:

        try:
            user = UserModel._default_manager.get_by_natural_key(username)

and the get_by_natural_key method will now make use of USERNAME_FIELD like it should do.

Note: See TracTickets for help on using tickets.
Back to Top