First, Collin's code works if you just want a workaround to the default authentication class. However, if you mean having User Authentication with email as the primary attribute, then you have to override the default authentication/user object. Django currently allows custom user models, forms, etc. The following code works for me if you want to work with email through out the project instead of the default username:
#here, I basically create a user class that can be authenticated using email and password through a user manager and a user model object
class MyUserManager(BaseUserManager):
"""
A custom user manager to deal with emails as unique identifiers for auth
instead of usernames. The default that's used is "UserManager"
"""
def _create_user(self, email, password, first_name=None, last_name=None, is_active=True, is_staff=False, is_admin=False, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')
email = self.normalize_email(email)
user = self.model(email=email, firstName=first_name, lastName=last_name)
user.set_password(password)
user.staff = is_staff
user.admin = is_admin
user.is_superuser = extra_fields.get('is_superuser', False)
user.is_active = is_active
user.save()
return user
def create_user(self, email, password = None, first_name=None, last_name=None, is_active=True, is_staff=False, is_admin=False, **extra_fields):
user_obj =self.model.objects.filter(email=email)
if(user_obj.exists() and not None):
return user_obj[0]
return self._create_user(email, password = password, first_name=first_name, last_name=last_name, is_active=is_active, is_staff=is_staff, is_admin=is_admin, **extra_fields)
def create_staffuser(self, email, first_name=None, last_name=None, password=None):
user = self.create_user(
email,
password=password,
first_name = first_name,
last_name = last_name,
is_staff=True
)
return user
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_admin', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(email, password, **extra_fields)
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(verbose_name='email address', max_length=255, unique = True, null = True)
is_active = models.BooleanField(verbose_name='active', default=True)
staff = models.BooleanField(verbose_name= 'staff status', default = False)
admin = models.BooleanField(verbose_name= 'admin status', default = False)
firstName = models.CharField(max_length=32, default='', null=True, blank=True)
lastName = models.CharField(max_length=32, default='', null=True, blank=True)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
phone = models.CharField(validators=[phone_regex],max_length = 15, null=True, blank = True)
USERNAME_FIELD = 'email'
objects = MyUserManager()
def __str__(self):
return self.email
#settings.py make sure to include AUTH_USER_MODEL= '<path to your user model>'
#forms.py
#note that the form styling is embedded into the form as widgets (you might want something different).
class UserCreateFormbyEmail(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
'password_mismatch': _("The two password fields didn't match."),
}
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput,
help_text=_("Enter the same password as before, for verification."),
)
class Meta:
model = get_user_model()
fields = ("email", "firstName", "lastName")
field_classes = {'email': UsernameField,}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._meta.model.USERNAME_FIELD in self.fields:
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': True})
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def clean_email(self):
email = self.cleaned_data.get('email')
if(email and get_user_model().objects.filter(email=email).exists()):
raise forms.ValidationError('This email address has been registered')
return email
def _post_clean(self):
super()._post_clean()
# Validate the password after self.instance is updated with form data
# by super().
password = self.cleaned_data.get('password2')
if password:
try:
password_validation.validate_password(password, self.instance)
except forms.ValidationError as error:
self.add_error('password2', error)
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserAuthenticatebyEmail(AuthenticationForm):
""" because we override the default user class, the username field is now email, not strings"""
username = UsernameField(required=True,label="Username",
widget=forms.TextInput(attrs={
'class': 'form-control login-field',
'name': 'email',
'placeholder': 'Email'}))
password = ReadOnlyPasswordHashField(required=True,label="Password",
widget=forms.PasswordInput(attrs={
'class': 'form-control login-field',
'name': 'password',
'placeholder':'Username'}))