Difficulty: Advanced
While AbstractUser is sufficient for most projects (adding fields to the default User), some applications need fundamentally different user models. The most common case is using email as the primary identifier instead of username. This requires AbstractBaseUser, which provides only the bare minimum: password hashing and session management.
When using AbstractBaseUser, you must define several components: the model fields (including at least one unique identifier field), USERNAME_FIELD (the field used for authentication), REQUIRED_FIELDS (additional required fields for createsuperuser), is_active (required by the admin), is_staff (required for admin access), and a custom manager with create_user() and create_superuser() methods.
PermissionsMixin adds the permission-related fields and methods (groups, user_permissions, is_superuser, has_perm, has_module_perms) to your custom user model. Without it, your user model will not integrate with Django's permission system or the admin.
The custom UserManager must inherit from BaseUserManager and implement create_user() and create_superuser(). create_user() should normalize the email, create the user instance, hash the password with set_password(), and save to the database. create_superuser() should call create_user() and set is_staff and is_superuser to True.
After creating a custom user model, you need to: (1) set AUTH_USER_MODEL in settings.py, (2) create an admin class (usually extending BaseUserAdmin or UserAdmin), (3) create forms for the admin (UserCreationForm and UserChangeForm), and (4) run migrations. Remember, AUTH_USER_MODEL must be set before the first migration.
Custom authentication backends allow even more flexibility. By default, Django authenticates against the User model using username and password. You can create custom backends that authenticate using email, phone number, social accounts, or external services. Authentication backends are configured in settings.AUTHENTICATION_BACKENDS.
# accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
from django.utils import timezone
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, extra_fields):
if not email:
raise ValueError('The Email field must be set')
email = self.normalize_email(email)
user = self.model(email=email, extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', 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 CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=150, blank=True)
last_name = models.CharField(max_length=150, blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(default=timezone.now)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
def __str__(self):
return self.email
def get_full_name(self):
return f'{self.first_name} {self.last_name}'.strip()
def get_short_name(self):
return self.first_name
This email-based user model uses email as the primary identifier. USERNAME_FIELD='email' means email is used for login. REQUIRED_FIELDS are additional prompts for createsuperuser. The custom manager handles user creation with proper password hashing.
# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(BaseUserAdmin):
list_display = ['email', 'first_name', 'last_name', 'is_staff', 'is_active']
list_filter = ['is_staff', 'is_active', 'groups']
search_fields = ['email', 'first_name', 'last_name']
ordering = ['email']
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal Info', {'fields': ('first_name', 'last_name')}),
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
('Important dates', {'fields': ('last_login', 'date_joined')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'first_name', 'last_name', 'password1', 'password2'),
}),
)
Custom UserAdmin extends BaseUserAdmin with fieldsets tailored to your user model. add_fieldsets defines the form for creating new users. Since there is no username field, we use email throughout.
# accounts/backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, kwargs):
User = get_user_model()
email = kwargs.get('email', username)
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
# settings.py
AUTHENTICATION_BACKENDS = [
'accounts.backends.EmailBackend',
]
# Usage in views:
# user = authenticate(request, email='user@example.com', password='pass123')
# Or: user = authenticate(request, username='user@example.com', password='pass123')
A custom backend extends ModelBackend and overrides authenticate(). It looks up users by email and verifies the password. user_can_authenticate() checks is_active. Multiple backends can be listed in AUTHENTICATION_BACKENDS.
# settings.py
# Point to your custom user model
AUTH_USER_MODEL = 'accounts.CustomUser'
# Custom authentication backend
AUTHENTICATION_BACKENDS = [
'accounts.backends.EmailBackend',
]
# Auth URLs
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/dashboard/'
LOGOUT_REDIRECT_URL = '/'
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 8}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
AUTH_USER_MODEL must be set before the first migration. AUTHENTICATION_BACKENDS defines how authentication works. AUTH_PASSWORD_VALIDATORS enforce password strength requirements.
AbstractBaseUser, PermissionsMixin, UserManager, USERNAME_FIELD, Email Auth