Django Username or Email Authenticate

i’m struggling a bit with this problem, some says i have to override AuthenticationForm but i don’t know how cause i got a custom user registration and login, i’ll post it:

urls.py

urlpatterns = [
	path('login/', LoginView.as_view(), {'template_name': 'accounts/login.html'}, name='login'),

Note im using default loginview right now.

So in views.py i must have this

def login(request):
	return render(request, '/login.html')
    ...
    ...

Of course login.html is just like this

<div class='container'>
    <h1>Welcome</h1>
    <p>
        you can login here!
    </p>
    <h2>Login</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type='submit'>Login</button>
    </form>
</div>

In models.py my custom user is this one:

class ProfiloUtente(models.Model):
	user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)

	utente = models.ForeignKey(Utente, on_delete=models.CASCADE)
	studio = models.ManyToManyField(Studio)
	telefono_fisso = models.CharField(max_length=20, default=None, blank=True)
	telefono_mobile = models.CharField(max_length=20, default=None, blank=True)
	indirizzo = models.CharField(max_length=40, default=None, blank=True)
	citta = models.CharField(max_length=50, default=None, blank=True)
	cap = models.CharField(max_length=5, default=None, blank=True)
	provincia = models.CharField(max_length=30, default=None, blank=True)
	cod_fiscale = models.CharField(max_length=16, default=None, blank=True)
	p_iva = models.CharField(max_length=27, default=None, blank=True)
	def __str__(self):
		return self.user.username

And the fields i use from The default django user are these:
forms.py

class RegistrationForm(UserCreationForm):
	"""docstring for RegistrationForm"""
	email = forms.EmailField(required=True)
	first_name = forms.CharField(max_length=30, required=True)
	last_name = forms.CharField(max_length=100, required=True)


	class Meta: # define a metadata related to this class
		model = User
		fields = (
			'username',
			'email',
			'first_name',
			'last_name',
			'password1',
			'password2',

		)
	def save(self, commit=True):
		user = super(RegistrationForm, self).save(commit=False)
		user.email = self.cleaned_data['email']
		user.first_name = self.cleaned_data['first_name']
		user.last_name = self.cleaned_data['last_name']


		if commit:
			user.save() # running sql in database to store data
		return user

could someone help me figure out how could i manage to authenticate this user by using Email or Username?
Every help is appreciated!
Thanks in advance

The system provided LoginView takes a form as a named parameter -

urlpatterns = [
	path('login/', LoginView.as_view(authentication_form=MyAuthenticationForm), {'template_name': 'accounts/login.html'}, name='login'),

So then you can create your MyAuthenticationForm as extending AuthenticationForm with only overriding the clean method.

Ken

1 Like

Thanks Ken.
I just wrote it down

class MyAuthenticationForm(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)

			if self.user_cache is None:
				raise forms.ValidationError(
					self.error_messages['invalid_login'],
					code='invalid_login',
					params={'username': self.username_field.verbose_name},
				)
			else:
				self.confirm_login_allowed(self.user_cache)

		return self.cleaned_data

Should i add another if statement and check the mail field of that user?

Ok, i done something like this

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

		if '@' in username:
			if username and password:
				self.user_cache = authenticate(email=username,
											   password=password)
				if self.user_cache is None:
					raise forms.ValidationError(
						self.error_messages['invalid_login'],
						code='invalid_login',
						params={'username': self.username_field.verbose_name},
					)
				else:
					self.confirm_login_allowed(self.user_cache)


		else:
			if username and password:
				self.user_cache = authenticate(username=username,
											   password=password)
				if self.user_cache is None:
					raise forms.ValidationError(
						self.error_messages['invalid_login'],
						code='invalid_login',
						params={'username': self.username_field.verbose_name},
					)
				else:
					self.confirm_login_allowed(self.user_cache)

		return self.cleaned_data

But something is not working properly, seems like the field email is not accepted cause it runs on the if self.user_cache is None

One fact is that i didn’t set the email field of every user to be unique, but what im also doing wrong?

Nope you didn’t do anything wrong, it’s just we need to dig a little deeper.

So digging through the source code even further, the authenticate method calls the backend.authenticate, which for ModelBackend, calls its authenticate method, which requires the username be one of the parameters supplied.

But, all is not lost. You’re actually quite close to a workable solution.

Notice that the “real” work in authenticate is done by these lines:

 if user.check_password(password) and self.user_can_authenticate(user):
                return user

Assuming that you’re willing to tie this application to the standard User model and require the use of the Model for authentication, you can actually simplify your clean method to do something like this:

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

        if username and password:
            try:
                user = User.objects.get(Q(email=username)|Q(username=username))
                if user.check_password(password) and self.user_can_authenticate(user):
                    self.user_cache = user
            except User.DoesNotExist:
                self.user_cache = None

        if self.user_cache is None:
            raise forms.ValidationError(
                self.error_messages['invalid_login'],
                code='invalid_login',
                params={'username': self.username_field.verbose_name},
            )
		else:
			self.confirm_login_allowed(self.user_cache)

    	return self.cleaned_data

(Again, this is off-the-cuff - but should be reasonably close.)

Note: The real lesson here is that there’s a lot to be learned by digging into the source code for these processes to understand what’s happening below the surface. The solutions to many problems become clearer when you understand what’s going on.

Ken

@KenWhitesell You helping me a lot… many thanks!
Well, i don’t know, but after read the docs i figured it out like this, may you tell me if there is something wrong with this try? i’ll post the code, i think i made it simple enough to be understood

class MyAuthenticationForm(AuthenticationForm):
	def clean(self):
		username = self.cleaned_data.get('username')
		password = self.cleaned_data.get('password')
		ogg = User.objects.filter(email=username)

		if '@' in username:
			if username and password:
				self.user_cache = authenticate(username=ogg[0].username,
											   password=password)
				if self.user_cache is None:
					raise forms.ValidationError(
						self.error_messages['invalid_login'],
						code='invalid_login',
						params={'username': self.username_field.verbose_name},
					)
				else:
					self.confirm_login_allowed(self.user_cache)


		else:
			if username and password:
				self.user_cache = authenticate(username=username,
											   password=password)
				if self.user_cache is None:
					raise forms.ValidationError(
						self.error_messages['invalid_login'],
						code='invalid_login',
						params={'username': self.username_field.verbose_name},
					)
				else:
					self.confirm_login_allowed(self.user_cache)

		return self.cleaned_data

In this way, i automatically search for a user with that email associated, i grab the username if in the login the user POSTed his email (’@’).
In the backend nothing different happens, but does this makes sense?

That looks like a good approach too - but I would still simplify it:

class MyAuthenticationForm(AuthenticationForm):
    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')
        ogg = User.objects.filter(
            Q(email=username) | Q(username=username)
            ).first()

        if ogg and password:
            self.user_cache = authenticate(username=ogg.username,
                                           password=password)
            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
            else:
                self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data

Visually, you’ve got too much replicated code between the two branches of the if statement - which really isn’t needed anyway.
Also, take note of the first method. One of its effects is that it returns None if no objects was found, so you want to filter that case out as well.
(Your code would throw an error if someone entered an invalid name - ogg would be None, and None[0] throws an exception.)

Ken

I made something like that, but your is more clean, short and appropriate.
What does this line means ?

If you’re not familiar with the Q objects, now’s a good time to read about them. (Along with this reference)

Ken

Ok Thanks a lot!
Just to let you know, my final script is this one, seems working properly for all cases, not too long either.

class MyAuthenticationForm(AuthenticationForm):
	def clean(self):
		username = self.cleaned_data.get('username')
		password = self.cleaned_data.get('password')
		
		#if '@' in username:
		if username and password:
			if '@' in username:
				ogg = User.objects.filter(email=username)
				if len(ogg) == 1:
					self.user_cache = authenticate(username=ogg[0].username,
												   password=password)
			else:
				self.user_cache = authenticate(username=username,
										   password=password)
			if self.user_cache is None:
				raise forms.ValidationError(
					self.error_messages['invalid_login'],
					code='invalid_login',
					params={'username': self.username_field.verbose_name},
				)
			else:
				self.confirm_login_allowed(self.user_cache)
		return self.cleaned_data

Reading the docs you shared

Check out the case where someone enters in an email address that isn’t in your system.

This happens, just refused i think

Ah - cool, I see it now. Thanks.

1 Like

Thanks one last time! for your help and your time, have a nice night