Building a robust authentication system is often the first step when launching a new Django project, and the default User model may not always fit your unique business requirements. In this comprehensive guide we’ll walk you through everything you need to know about creating a Python Django custom user model—from the initial decision‑making process to the final testing stage. By the end of this article you’ll have a production‑ready custom user model that scales with your app, improves security, and boosts SEO relevance for keywords like “custom user model guide” and “Django authentication”.
Why Choose a Custom User Model?
Before you dive into code, it’s worth understanding the real benefits of replacing Django’s built‑in User model:
- Flexibility: Add fields such as
phone_number,date_of_birth, orprofile_imagewithout creating separate profile tables. - Future‑proofing: Avoid costly migrations later—once you switch, changing back is painful.
- Cleaner authentication flow: Use
Emailorphoneas the primary login identifier instead of a username. - Better SEO alignment: Tailor URLs and user‑generated content to include relevant keywords, improving search engine visibility.
When to Implement the Custom User Model
The Django documentation is crystal clear: create your custom user model at the start of the project. If you wait until later, you’ll face complex data migrations and third‑party app compatibility issues. Here’s a quick decision matrix:
- **New project** – Implement custom model immediately.
- **Existing project without user data** – You can safely migrate, but plan for a maintenance window.
- **Existing project with live user data** – Consider a phased rollout or a separate authentication micro‑service.
Step‑by‑Step Guide to Building the Model
1. Create a Dedicated App
Isolating authentication logic keeps your project tidy. Run:
python manage.py startapp accounts
Then add 'accounts' to INSTALLED_APPS in settings.py.
2. Define the Custom User Model
In accounts/models.py extend AbstractBaseUser and PermissionsMixin. This gives you password handling and permission utilities out of the box.
from django.contrib.auth.models import (
AbstractBaseUser, PermissionsMixin, BaseUserManager
)
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, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True or \
extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_staff=True and is_superuser=True')
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField('email address', unique=True)
first_name = models.CharField('first name', max_length=30, blank=True)
last_name = models.CharField('last name', max_length=30, blank=True)
date_of_birth = models.DateField(null=True, 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 = [] # Email & password are required by default
class Meta:
verbose_name = 'user'
verbose_name_plural = 'users'
def __str__(self):
return self.email
3. Update Django Settings
Tell Django to use your new model:
# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
Also, configure authentication backends if you need email‑based login:
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
]
4. Create Custom Forms
Replace the default UserCreationForm and UserChangeForm with versions that understand your model.
# accounts/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ('email', 'first_name', 'last_name')
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('email', 'first_name', 'last_name', 'date_of_birth')
5. Register the Model in the Admin Site
Without proper admin registration, you’ll lose the ability to manage users from the Django admin.
# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ('email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_active')
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('first_name', 'last_name', 'date_of_birth')}),
('Permissions', {'fields': ('is_staff', 'is_active', 'groups', 'user_permissions')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')
}),
)
search_fields = ('email',)
ordering = ('email',)
6. Run Migrations
Finally, create and apply migrations:
python manage.py makemigrations accounts
python manage.py migrate
At this point, your project is using the custom user model for all authentication flows.
Best Practices & Common Pitfalls
Never Change AUTH_USER_MODEL After Migrations
Switching the user model mid‑project forces you to rewrite every foreign key that points to auth.User. If you must, use Django’s swappable_dependency and a data migration script.
Always Use get_user_model()
Hard‑coding CustomUser in other apps can break third‑party packages. Import the user model dynamically:
from django.contrib.auth import get_user_model
User = get_user_model()
Leverage AbstractUser for Minor Tweaks
If you only need to add a few fields and still want the default username field, subclass AbstractUser instead of AbstractBaseUser. This reduces boilerplate.
Secure Password Handling
- Never store raw passwords—always use
set_password()andcheck_password(). - Enable
AUTH_PASSWORD_VALIDATORSinsettings.pyfor strength enforcement. - Consider adding two‑factor authentication (2FA) for high‑risk accounts.
Testing Your Custom User Model
Automated tests guarantee that future changes won’t break authentication. Here’s a quick example using Django’s test framework:
# accounts/tests.py
from django.test import TestCase
from django.contrib.auth import get_user_model
class CustomUserModelTest(TestCase):
def setUp(self):
self.User = get_user_model()
self.user = self.User.objects.create_user(
email='test@example.com',
password='StrongPass!123',
first_name='Test',
last_name='User'
)
def test_user_creation(self):
self.assertEqual(self.user.email, 'test@example.com')
self.assertTrue(self.user.check_password('StrongPass!123'))
self.assertFalse(self.user.is_staff)
def test_superuser_creation(self):
admin = self.User.objects.create_superuser(
email='admin@example.com',
password='AdminPass!456'
)
self.assertTrue(admin.is_staff)
self.assertTrue(admin.is_superuser)
Run python manage.py test accounts to ensure everything works as expected.