Overwriting the AuthenticationForm class to log in with email - python

I have an application in django 1.11 and I created a login by email instead of a username. In order to log in using the login form I had to overwrite the AuthenticationForm class and insert an email instead of username.
forms.py
UserModel = get_user_model()
Here I overwritted AuthenticationForm class and change username to email.
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
email = forms.EmailField(
label=_("Email address"),
max_length=254,
widget=forms.EmailInput(attrs={'autofocus': True}),
)
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
)
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."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
# Set the label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
if self.fields['email'].label is None:
self.fields['email'].label = capfirst(self.username_field.verbose_name)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email is not None and password:
self.user_cache = authenticate(self.request, email=email, 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
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',
)
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class LoginForm(AuthenticationForm):
email = forms.CharField(label='Email', max_length=50)
password = forms.CharField(label='Password', max_length=50)
field_order = ['email', 'password']
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
Is it as it is now, is it ok? Is it possible to do it shorter / better?

You don't need to override full Class as you extending AuthenticationForm class. You just need to override clean() and change USERNAME_FIELD to email

Related

KeyError at /api/login 'user'

This user keyword error is showing in my login api. I am not sure why. I am guessing user instance is not available in the login view. I am new and I dont know the reason.
This is my login view:
class LoginUserView(GenericAPIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserLoginSerializer(data=data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
token, created = Token.objects.get_or_create(user=user)
return response.Response({"token": token.key}, status=status.HTTP_200_OK)
This is my serializers:
class UserLoginSerializer(serializers.ModelSerializer):
email = serializers.EmailField(label='Email Address')
class Meta:
model = User
fields = [
'email', 'password',
]
extra_kwargs = {"password":
{"write_only": True}}
def validate(self, data):
# user = None
email = data.get("email", None)
password = data.get("password")
if not email:
raise serializers.ValidationError("Email is required for login")
if not password:
raise serializers.ValidationError("Password is required for login")
user = authenticate(email=email, password=password)
if not user:
raise serializers.ValidationError("This email is not valid/already exists")
return data
The serializer has no user in its validated data. Note that serializer.validated_data['user'] makes not much sense, since you never passed this to the validator.
You should add it to the data with:
def validate(self, data):
# user = None
email = data.get('email', None)
password = data.get('password')
if not email:
raise serializers.ValidationError('Email is required for login')
if not password:
raise serializers.ValidationError('Password is required for login')
user = authenticate(email=email, password=password)
if not user:
raise serializers.ValidationError('This email is not valid/already exists')
data['user'] = user
return data

Use email as authentication field and add email verification on django custom user model

I have this custom user model on my Django project. I want to make the email as authentication field instead of the username. Also, I want to perform an email verification.
models.py
class es_user(models.Model):
user = models.OneToOneField(User,related_name='es_user', on_delete=models.CASCADE),
is_activated = models.BooleanField(default=False)
def get_absolute_url(self):
return reverse('user_detail', kwargs={'id': self.pk })
view.py
def signup(request):
signup_form_instance = SignUpForm()
if request.method == "POST":
signup_form_instance = SignUpForm(request.POST)
if signup_form_instance.is_valid():
signup_form_instance2 = signup_form_instance.save(commit = False)
username = signup_form_instance2.username
password = signup_form_instance2.password
signup_form_instance2.password = make_password(signup_form_instance.cleaned_data['password'])
signup_form_instance2.save()
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request,user)
active_user = request.user
es_user_instance = es_user.objects.create(user= active_user)
# return index(request)
return redirect('index')
# return user_detail(request)#successful signup redirect or return
# return redirect('user_detail',id = [str(request.user.id) ])#kwargs={'id': request.user.id })
else:
print("SIGN UP FORM INVALID")
return render(request,'signup.html',{'signup_form':signup_form_instance})
forms.py
class SignUpForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'email', 'password')
# for adding bootstrap classes & placeholders
widgets = {
'username': TextInput(attrs={
'class':'form-control',
'placeholder': 'Username *'}),
'email': EmailInput(attrs={
'class':'form-control',
'placeholder': 'Your Email *'}),
'password': PasswordInput(attrs={
'class':'form-control',
'placeholder': 'Your Password *'}),
}
help_texts = {
'username': None,
}
# to remove labels in form
labels = {
'username': (''),
'email':(''),
'password':(''),
}
My project is near completion so I cannot change my user model anymore or even change its name. So is there a way I can add email verification and using email instead of username for authentication without changing my user model.
I've seen a solution for a similar problem in this post . But I cannot use it since I use my custom user model es_user. is there a way in which I can edit it for my problem
To use Email as authentication, you have to use make new python file Backend.py and inside it write
class AuthenticationBackend(backends.ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
usermodel = get_user_model()
print(usermodel)
try:
user = usermodel.objects.get(Q(username__iexact=username) | Q(
email__iexact=username))
if user.check_password(password):
return user
except user.DoesNotExist:
pass
and add this AuthenticationBackend in settings.py as
AUTHENTICATION_BACKENDS = (
'users.backend.AuthenticationBackend',
'django.contrib.auth.backends.ModelBackend',
)
This will let you authenticate user with both username and password.
For second part of your question,Please follow:
Django Custom User Email Account Verification

auth is returned as none when trying to log in user when signing up

I am trying to solve the use case where when user signs up then the user is automatically logged in and return the token of that user so that i can that token in cookies from frontend. However, I get issue "Email already registered". when i debug my code using pdb, i found that auth = authenticate(username=email, password=password) is returning None.
How can I authenticate user during signup and pass token of that user?
Here is how i am doing
class Register(graphene.Mutation):
'''
Mutation to register a user
'''
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
password_repeat = graphene.String(required=True)
success = graphene.Boolean()
errors = graphene.List(graphene.String)
email = graphene.String()
def mutate(self, info, email, password, password_repeat):
if password == password_repeat:
try:
serializer = RegistrationSerializer(data={
'email': email,
'password': password,
'is_active': False
})
if serializer.is_valid():
user = serializer.save()
auth = authenticate(username=email, password=password)
import pdb
pdb.set_trace()
# login user and pass the token
login(info.context, auth)
return Register(success=bool(user.id), email=user.email)
else:
print("error", serializer.errors)
except Exception:
errors = ["email", "Email already registered"]
return Register(success=False, errors=errors)
errors = ["password", "Passwords don't match."]
return Register(success=False, errors=errors)
class Login(graphene.Mutation):
"""
Mutation to login a user
"""
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
success = graphene.Boolean()
errors = graphene.List(graphene.String)
token = graphene.String()
user = graphene.Field(UserQuery)
def mutate(self, info, email, password):
user = {'email': email, 'password': password}
serializer = JSONWebTokenSerializer(data=user)
if serializer.is_valid():
token = serializer.object['token']
user = serializer.object['user']
print('user', user)
return Login(success=True, user=user, token=token)
else:
print("serializers errors", serializer.errors)
return Login(
success=False,
token=None,
errors=['email', 'Unable to login with provided credentials.']
)
RegistrationSerializer
class RegistrationSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'is_active', 'password', )
def create(self, validated_data):
print("validated_data", validated_data)
user = User.objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
I am using graphql instead of REST API and I am using graphene-django for graphql.

Overwriting rest-auth RegisterSerializer, add age validation

I'm trying to validate age for user during creation by rest-auth. I managed to add field and save it during registration, but now I'm having hard time to validate if age is < 18.
Someone could point me at the way I should do it?
I have tried with validation through my AbstractUser model, with #property method, and it was raising ValidationError during registration, but the User account was saving anyway, and i couldn't access to user detail view because of the ValidationError, so I came to the conclusion that I would just prefer to prevent registration through validation, but it isn't working in my case.
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
min_length=allauth_settings.USERNAME_MIN_LENGTH,
required=allauth_settings.USERNAME_REQUIRED
)
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
date_of_birthday = serializers.DateField() ### ADDED BY ME
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
def validate_username(self, username):
username = get_adapter().clean_username(username)
return username
def validate_email(self, email):
email = get_adapter().clean_email(email)
if allauth_settings.UNIQUE_EMAIL:
if email and email_address_exists(email):
raise serializers.ValidationError(
_("A user is already registered with this e-mail address."))
return email
def validate_age(self, date_of_birthday): ### ADDED BY ME
age = relativedelta(datetime.now(), date_of_birthday).years
if age < 18:
raise serializers.ValidationError('Must be at least 18 years old to register.')
else:
return age
def validate_password1(self, password):
return get_adapter().clean_password(password)
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(_("The two password fields didn't match."))
return data
def custom_signup(self, request, user):
pass
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username', ''),
'date_of_birthday': self.validated_data.get('date_of_birthday', ''), ### ADDED BY ME
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', '')
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
self.custom_signup(request, user)
setup_user_email(request, user, [])
user.date_of_birth = self.cleaned_data.get('date_of_birthday') ### ADDED BY ME
user.save() ### ADDED BY ME
return user
You can validated serializer fields by defining methods prefixing validate_ to the field name. In your case, replace validate_age with validate_date_of_birthday
def validate_date_of_birthday(self, date_of_birthday):
age = relativedelta(datetime.now(), date_of_birthday).years
if age < 18:
raise serializers.ValidationError('Must be at least 18 years old to register.')
else:
return date_of_birthday

check_password() from a user again

I have the following form. How can I check the password from the user again, before the user can change his email address finally? Even if the user is logged in, I just want to be sure that it is really the user. Just a security thing.
How do I do it with .check_password()?
'EmailChangeForm' object has no attribute 'user'
/home/craphunter/workspace/project/trunk/project/auth/user/email_change/forms.py in clean_password, line 43
from django import forms
from django.db.models.loading import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
super(EmailChangeForm, self).__init__(*args, **kwargs)
self.user = user
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
def __init__(self, username=None, *args, **kwargs):
"""Constructor.
**Mandatory arguments**
``username``
The username of the user that requested the email change.
"""
self.username = username
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_email(self):
"""Checks whether the new email address differs from the user's current
email address.
"""
email = self.cleaned_data.get('email')
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email
I would refactor your code to look something like this:
View:
#login_required
def view(request, extra_context=None, ...):
form = EmailChangeForm(user=request.user, data=request.POST or None)
if request.POST and form.is_valid():
send_email_change_request(request.user,
form.cleaned_data['email'],
https=request.is_secure())
return redirect(success_url)
...
Password validation goes to form:
class EmailChangeForm(Form):
email = ...
old_password = CharField(..., widget=Password())
def __init__(self, user, data=None):
self.user = user
super(EmailChangeForm, self).__init__(data=data)
def clean_old_password(self):
password = self.cleaned_data.get('password', None)
if not self.user.check_password(password):
raise ValidationError('Invalid password')
Extract logic from view:
def send_email_change_request(user, new_email, https=True):
site = cache.get_model('sites', 'Site')
email = new_email
verification_key = generate_key(user, email)
current_site = Site.objects.get_current()
site_name = current_site.name
domain = current_site.domain
protocol = 'https' if https else 'http'
# First clean all email change requests made by this user
qs = EmailChangeRequest.objects.filter(user=request.user)
qs.delete()
# Create an email change request
change_request = EmailChangeRequest(
user = request.user,
verification_key = verification_key,
email = email
)
change_request.save()
# Prepare context
c = {
'email': email,
'site_domain': 'dev.tolisto.de',
'site_name': 'tolisto',
'user': self.user,
'verification_key': verification_key,
'protocol': protocol,
}
c.update(extra_context)
context = Context(c)
# Send success email
subject = "Subject" # I don't think that using template for
# subject is good idea
message = render_to_string(email_message_template_name, context_instance=context)
send_mail(subject, message, None, [email])
Don't put complicated things inside views (such as rendering and sending email).
I feel like you answered your own question : )
The docs on the check_password method are here:
https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser.check_password
success = user.check_password(request.POST['submitted_password'])
if success:
# do your email changing magic
else:
return http.HttpResponse("Your password is incorrect")
# or more appropriately your template with errors
Since you're already passing in request.user into your form constructor (looks like you've overriden __init__ for your own reasons) you could put all of your logic in the form without any trouble.
class MyForm(forms.Form):
# ...
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.user = user
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
update after seeing your forms
OK. The main problem is that __init__ has been defined twice, making the first statement useless. Second problem I see is that we'd be doing multiple queries for user when we really don't have to.
We've strayed from your original question quite a bit, but hopefully this is a learning experience.
I've changed only a few things:
Removed the extra __init__ definition
Changed __init__ to accept a User instance instead of a text username
Removed the query for User.objects.get(username=username) since we're passing in a user object.
Just remember to pass the form constructor user=request.user instead of username=request.user.username
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user=None, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
def clean_email(self):
email = self.cleaned_data.get('email')
# no need to query a user object if we're passing it in anyways.
user = self.user
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email
Finally since we're talking about good practice here, I'd recommend following through with Skirmantas suggestions about moving your current view code to a form method so you can simply call myform.send_confirmation_email.
Sounds like a good exercise!
thanks again to Yuji. It works when I don't have in my first def __init__ the variable user. I also added in def clean_password the first 2 lines from the def clean_email
from django import forms
from django.db.models.loading import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_password(self):
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
valid = user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
def __init__(self, username=None, *args, **kwargs):
"""Constructor.
**Mandatory arguments**
``username``
The username of the user that requested the email change.
"""
self.username = username
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_email(self):
"""Checks whether the new email address differs from the user's current
email address.
"""
email = self.cleaned_data.get('email')
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email

Categories