Django newbie here.
I wrote simplified login form which takes email and password. It works great if both email and password are supplied, but if either is missing i get KeyError exception. According to django documentation this should never happen:
By default, each Field class assumes the value is required, so if you pass an empty value -- either None or the empty string ("") -- then clean() will raise a ValidationError exception
I tried to write my own validators for fields (clean_email and clean_password), but it doesn't work (ie I get KeyError exception). What am I doing wrong?
class LoginForm(forms.Form):
email = forms.EmailField(label=_(u'Your email'))
password = forms.CharField(widget=forms.PasswordInput, label=_(u'Password'))
def clean_email(self):
data = self.cleaned_data['email']
if not data:
raise forms.ValidationError(_("Please enter email"))
return data
def clean_password(self):
data = self.cleaned_data['password']
if not data:
raise forms.ValidationError(_("Please enter your password"))
return data
def clean(self):
try:
username = User.objects.get(email__iexact=self.cleaned_data['email']).username
except User.DoesNotExist:
raise forms.ValidationError(_("No such email registered"))
password = self.cleaned_data['password']
self.user = auth.authenticate(username=username, password=password)
if self.user is None or not self.user.is_active:
raise forms.ValidationError(_("Email or password is incorrect"))
return self.cleaned_data
You could leverage Django's built-in way to override how Authentication happens by setting
AUTHENTICATION_BACKENDS in your settings.py
Here's my EmailAuthBackend:
#settings.py
AUTHENTICATION_BACKENDS = (
'auth_backend.auth_email_backend.EmailBackend',
'django.contrib.auth.backends.ModelBackend',
)
#auth_email_backend.py
from django.contrib.auth.backends import ModelBackend
from django.forms.fields import email_re
from django.contrib.auth.models import User
class EmailBackend(ModelBackend):
"""
Authenticate against django.contrib.auth.models.User
"""
def authenticate(self, **credentials):
return 'username' in credentials and \
self.authenticate_by_username_or_email(**credentials)
def authenticate_by_username_or_email(self, username=None, password=None):
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
if user:
return user if user.check_password(password) else None
else:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
#forms.py
#replaces the normal username CharField with an EmailField
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class LoginForm(AuthenticationForm):
username = forms.EmailField(max_length=75, label='Email')
next = forms.CharField(widget=forms.HiddenInput)
Hope that helps!
With Django 2.0 it's even simpler to achive this.
Create your own UserModel
class User(AbstractBaseUser, PermissionsMixin):
and set the USERNAME_FIELD = 'email'
For reference: Documentation
Related
By default django uses the USERNAME_FIELD for authentication but it takes only one field. But i want my signup form to let users sign up by using phone number or by their email, like it happens in facebook . Can anybody help me how can i achieve this ?
First of all your phone_number field should be unique and you have to create a custom backend for this and register in settings and the backend checks the user by username or phone_number then check the password of the user and if credentials are correct then login user. For example,
#backends.py
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.contrib.auth import get_user_model
User = get_user_model()
class UserNamePhoneAuthBackend(ModelBackend):
"""UserName or Phone Authentication Backend.
Allows user sign in with email or phone then check password is valid
or not and return user else return none
"""
def authenticate(self, request, username_or_phone=None, password=None, role=None):
try:
user = User.objects.get(
(Q(username=username_or_phone) | Q(phone_number=username_or_phone)))
except ObjectDoesNotExist:
return None
else:
if user.check_password(password):
return user
else:
return None
def get_user(self, user_id):
try:
return CustomUser.objects.get(id=user_id)
except ObjectDoesNotExist:
return None
#settings
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"path.for.UserNamePhoneAuthBackend",
)
#views.py
from django.contrib.auth import authenticate
from django.contrib.auth import login
class UserLogin(ApiView):
def post(self, request):
username_or_phone = request.data.get("username_or_phone")
password = request.data.get("password")
user = authenticate(
username_or_phone=username_or_phone,
password=password)
if user is not None:
login(user)
# return token for user
else:
return Response("Invalid Credentials")
I want to login with email not username like this, please help
class loginUser(View):
def get(self, request):
lF = loginForm
return render(request, 'UserMember/login.html', {'lF': lF})
def post(self, request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return render(request, 'UserMember/private.html')
else:
return HttpResponse('login fail')
First thing is to create a default email field.
# models.py
class CustomUser(AbstractUser):
email = models.EmailField(_('email address'), unique=True)
# settings.py (remember to migrate)
AUTH_USER_MODEL = 'accounts.CustomUser' # new
Next, create your custom email backend:
# backends.py (in-app)
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = UserModel.objects.get(
Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
UserModel().set_password(password)
except MultipleObjectsReturned:
return User.objects.filter(email=username).order_by('id').first()
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
user = UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
# settings.py (migrate again)
AUTH_USER_MODEL = 'accounts.CustomUser'
AUTHENTICATION_BACKENDS = ['accounts.backends.EmailBackend'] # new
If you plan on using Django's default register/login forms, do:
# form.py
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import get_user_model
from django import forms
class RegisterForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ('email', 'username', 'password1', 'password2')
class LoginForm(AuthenticationForm):
username = forms.CharField(label='Email / Username')
And then it's only the views and URLs to handle.
Ref
you need to work with allAuth
Follow this Link
hope it will be helpful.
I am new to django and I wanted to authenticate user on email or username with password hence I wrote a custom authentication as shown in documentation but it doesn't seem to be called and I have no idea what do I do?
settings.py
AUTHENTICATION_BACKENDS = ('accounts.backend.AuthBackend',)
views.py
def login(request):
if request.method == 'POST':
username_or_email = request.POST['username']
password = request.POST['password']
user = authenticate(username=username_or_email, password=password)
print(user)
if user is not None:
return reverse('task:home')
else:
messages.error(request, "Username or password is invalid")
return render(request, 'accounts/login.html')
else:
return render(request, 'accounts/login.html')
backend.py
from django.contrib.auth.models import User
from django.db.models import Q
class AuthBackend(object):
supports_object_permissions = True
supports_anonymous_user = False
supports_inactive_user = False
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
def authenticate(self, username, password):
print('inside custom auth')
try:
user = User.objects.get(
Q(username=username) | Q(email=username) )
print(user)
except User.DoesNotExist:
return None
print(user)
if user.check_password(password):
return user
else:
return None
I wrote this print statements in my class to check if they are being called and being written in console. However, they are not being printed and the print statement in views.py prints None
You need to extend the ModelBackend from django.contrib.auth.backends
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
User = get_user_model()
class AuthBackend(ModelBackend):
supports_object_permissions = True
supports_anonymous_user = False
supports_inactive_user = False
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
def authenticate(self, request, username=None, password=None):
print('inside custom auth')
try:
user = User.objects.get(
Q(username=username) | Q(email=username) )
print(user)
except User.DoesNotExist:
return None
print(user)
if user.check_password(password):
return user
else:
return None
And also in settings.py don't forget to add your custom backend authentication
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'accounts.backend.AuthBackend'
]
Another Possible Solution
From you code what I am seeing is that you want your email should treat as user_name of User model. You can easily modify Django's AbstructUser model like following
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
# your necessary additional fields
USERNAME_FIELD = 'email' # add this line
Now email field will treat as an user_name field. No need to add custom authentication-backend
import sys
from django.contrib.auth import get_user_model
from accounts.models import Token
User = get_user_model()
class PasswordlessAuthenticationBackend:
def authenticate(self, uid=None):
print('uuuuuuuuu')
print('uid', uid, file=sys.stderr)
if not Token.objects.filter(uid=uid).exists():
print('no token found', file=sys.stderr)
return None
token = Token.objects.get(uid=uid)
print('got token', file=sys.stderr)
try:
user = User.objects.get(email=token.email)
print('got user', file=sys.stderr)
return user
except User.DoesNotExist:
print('new user', file=sys.stderr)
return User.objects.create(email=token.email)
def get_user(self, email):
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
In a django project i'm working on I wrote a function to validate the data from a login form. Now whenever I run the is_valid() method on a instance of the login form it returns false everytime. I've been trying to figure out what's causing this problem all day.
forms.py:
from django import forms
from django.contrib.auth import authenticate, password_validation
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User, Group
from .functions import *
from .models import *
class login_form(forms.Form):
username = forms.CharField(max_length=64, label='Username', widget=forms.TextInput(attrs={'placeholder': 'username...', 'class':'login-field'}))
password = forms.CharField(max_length=24, label='Password', widget=forms.PasswordInput(attrs={'placeholder': 'password...', 'class':'login-field'}))
def clean(self, *args, **kwargs):
username = self.cleaned_data.get('username', None)
password = self.cleaned_data.get('password', None)
user_obj = None
self._errors['username'] = None #clears default username error
self._errors['password'] = None #clears default password error
if username is not None:
try:
user_obj = User.objects.get(username=username)
if password_validation.validate_password(password, user_obj) is None:
self._errors['password'] = 'Password is incorrect. Please try again.'
except User.DoesNotExist:
self._errors['username'] = 'User does not exist. Please use valid credentials.'
else:
self._errors['username'] = 'Please type a valid username.'
return super(login_form, self).clean(*args, **kwargs)
views.py:
from django.shortcuts import render, redirect
from django.http import HttpRequest, JsonResponse, HttpResponse
from django.template import RequestContext
from django.conf.urls import patterns
from django.contrib import admin
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User, Group
from datetime import datetime
from .models import *
from .forms import *
from .functions import *
from os import *
import shutil
import json
def home(request):
if request.method == 'POST':
form = login_form(request.POST)
if form.is_valid():
username = form.data.get('username')
password = form.data.get('password')
authenticate_usr_obj = authenticate(username=username, password=password)
if authenticate_usr_obj is not None:
login(request, authenticate_usr_obj)
return redirect(dashboard)
else:
form = login_form()
try:
logout_successful = request.session.get('logout_complete', False)
request.session['logout_complete'] = False
except:
logout_successsful = False
context = {
'body_class': 'login',
'login': form,
'logout_successful': logout_successful
}
return render(request, 'development/index.html', context)
Just by reading throught Forms API docs I can see that in your example you did not clean data before you tried to get them in lines:
username = self.cleaned_data.get('username', None)
password = self.cleaned_data.get('password', None)
try to first use clean() to validate the data, like this:
cleaned_data = super(login_form, self).clean()
Then you can try to get them like this:
username = cleaned_data.get('username', None)
password = cleaned_data.get('password', None)
You can find more here
Version 2:
Ok, so first of all what I found in Django docs regarding the django.contrib.auth.password_validation.validate_password():
Integrating validation
validate_password(password, user=None, password_validators=None)
Validates a password. If all validators find the password valid, returns None. If one or more validators reject the password, raises a ValidationError with all the error messages from the validators.
The user object is optional: if it’s not provided, some validators may not be able to perform any validation and will accept any password.
So the line:
if password_validation.validate_password(password, user_obj) is None:
should be:
if password_validation.validate_password(password, user_obj) is not None:
Second of all:
Form.errors
>>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}`
In this dictionary, the keys are the field names, and the values are lists of Unicode strings representing the error messages. The error messages are stored in lists because a field can have multiple error messages.
So based on that, I would change lines where you define your custom messages to look more something like this:
self.errors['password'] = [u'Password is incorrect. Please try again.']
And the one last thing to remember is that clean method must return the cleaned_data.
Final version of forms.py code could look something like that:
def clean(self):
super(login_form,self).clean()
username = self.cleaned_data.get('username', None)
password = self.cleaned_data.get('password', None)
if username is not None:
try:
user_obj = User.objects.get(username=username)
if password_validation.validate_password(password, user_obj) is not None:
self.errors['password'] = [u'Password is incorrect. Please try again.']
except User.DoesNotExist:
self.errors['username'] = [u'User does not exist. Please use valid credentials.']
else:
self.errors['username'] = [u'Please type a valid username.']
return self.cleaned_data
Hopefully it helps this time.
Tested your code above and I've encountered error due to forms self._errors
`Nonetype` object is not iterable
And here is my suggestion
forms.py
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
class login_form(forms.Form):
username = forms.CharField(
max_length=64, label='Username',
widget=forms.TextInput(
attrs={'placeholder': 'username...', 'class': 'login-field'}))
password = forms.CharField(
max_length=24, label='Password',
widget=forms.PasswordInput(
attrs={'placeholder': 'password...', 'class': 'login-field'}))
def clean_username(self):
data = self.cleaned_data.get('username', None)
if not data:
raise ValidationError('Please type a valid username.',
code='invalid')
return data
def clean(self, *args, **kwargs):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
try:
user_obj = User.objects.get(username=username)
if not user_obj.check_password(password):
raise ValidationError(
'Password is incorrect. Please try again',
code='incorrect')
except User.DoesNotExist:
raise ValidationError(
'User does not exist. Please use valid credentials.',
code='invalid')
return super(login_form, self).clean(*args, **kwargs)
views.py
from django.shortcuts import render
from django.contrib.auth import authenticate, login
from django.contrib.auth import logout
from .forms import *
def home(request):
logout_successful = True
if request.method == 'POST':
form = login_form(request.POST)
# print form.errors
if form.is_valid():
username = form.data.get('username')
password = form.data.get('password')
authenticate_usr_obj = authenticate(
username=username, password=password)
if authenticate_usr_obj:
login(request, authenticate_usr_obj)
# print 'ok'
else:
form = login_form()
logout_successful = request.user.is_authenticated()
return render(request, 'base.html', {
'body_class': 'login',
'login': form,
'logout_successful': logout_successful
})
def logout_view(request):
logout(request)
I'm still a beginner so feel free to comment something
I wanted to insert the user details in auth_user table, but it gives the error of create_user() got an unexpected keyword argument 'first_name'
forms.py
from django import forms
from django.contrib.auth.models import User
from django.forms import ModelForm
from customer_reg.models import Customer
class Registration_Form(ModelForm):
first_name = forms.CharField(label=(u'First Name'))
last_name = forms.CharField(label=(u'Last Name'))
username = forms.CharField(label=(u'User Name'))
email = forms.EmailField(label=(u'Email Address'))
password = forms.CharField(label=(u'Password'), widget=forms.PasswordInput(render_value=False))
class Meta:
model=Customer
exclude=('user',)
def clean_username(self):
username=self.cleaned_data['username']
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError("The Username is already taken, please try another.")
def clean_password(self):
password=self.cleaned_data['password']
return password
views.py
from django.contrib.auth.models import User
from customer_reg.models import Customer
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from customer_reg.forms import Registration_Form
def CustomerRegistration(request):
if request.user.is_authenticated():
return HttpResponseRedirect('/customer_profile/')
if request.method == 'POST':
form = Registration_Form(request.POST)
if form.is_valid():
user=User.objects.create_user(first_name=form.cleaned_data['first_name'], last_name=form.cleaned_data['last_name'], username=form.cleaned_data['username'], email=form.cleaned_data['email'], password = form.cleaned_data['password'])
user.save()
customer=user.get_profile()
customer.birthday=form.cleaned_data['birthday']
customer.website=form.cleaned_data['website']
customer.store=form.cleaned_data['store']
customer.welcomemail=form.cleaned_data['welcomemail']
customer.save()
return HttpResponseRedirect('/customer_profile/')
else:
return render_to_response('customer_register.html',{'form':form} , context_instance=RequestContext(request))
else:
''' user is not submitting the form, show them a blank registration form '''
form = Registration_Form()
context={'form':form}
return render_to_response('customer_register.html',context , context_instance=RequestContext(request))
If I edit the views.py as
user=User.objects.create_user(username=form.cleaned_data['username'], email=form.cleaned_data['email'], password = form.cleaned_data['password'])
then it works successfully
I have already tried firstname as well as first_name
any idea where I have done the mistake
The create_user manager method only accepts three arguments, username, email (optional), and password (optional).
Once you have created a user, you can modify the other fields, then save again.
user=User.objects.create_user(username=form.cleaned_data['username'], email=form.cleaned_data['email'], password = form.cleaned_data['password'])
user.first_name = form.cleaned_data['first_name']
user.last_name = form.cleaned_data['last_name']
user.save()
If you want to be able to register using admin interface you gonna have to change the admin.py inside your app