Building a robust authentication system is one of the first challenges you face when starting a new Django project. While Django’s default User model works well for many simple applications, real‑world projects often require additional fields, different login identifiers, or custom permission logic. That’s where a custom user model comes in. In this comprehensive guide we’ll walk through every step of creating, configuring, and maintaining a custom user model in Django, complete with code snippets, best‑practice tips, and common pitfalls to avoid.
Why Choose a Custom User Model?
Before diving into the implementation, it’s important to understand why you might need a custom user model instead of extending the built‑in one.
- Additional profile data: Store phone numbers, avatars, or subscription status directly on the user record.
- Alternative authentication fields: Use email, phone number, or a UUID as the primary login identifier.
- Custom permissions or roles: Implement role‑based access control that differs from Django’s default groups.
- Future‑proofing: Adding a custom model at project start saves you from costly migrations later.
Because Django reads the user model configuration at startup, you must define your custom model before you run the first migrate. Switching later is possible but requires careful data migration and can be error‑prone.
Step‑by‑Step: Creating a Custom User Model
1. Set Up a Dedicated App
It’s a best practice to keep authentication logic isolated in its own app, for example accounts.
django-admin startapp accounts
2. Define a Custom User Manager
The manager handles user creation and ensures the required fields are set correctly. Use BaseUserManager as the parent class.
from django.contrib.auth.base_user import BaseUserManager
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 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)
3. Build the Custom User Model
Extend AbstractBaseUser and PermissionsMixin to inherit password handling and permission utilities.
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.utils import timezone
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_joined = models.DateTimeField('date joined', default=timezone.now)
is_active = models.BooleanField('active', default=True)
is_staff = models.BooleanField('staff status', default=False)
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
4. Tell Django About the New Model
Add the following line to your project’s settings.py before any app that imports django.contrib.auth:
AUTH_USER_MODEL = 'accounts.CustomUser'
5. Register the Model in the Admin Interface
Customizing the admin makes user management seamless for staff.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import CustomUser
from .forms import CustomUserCreationForm, CustomUserChangeForm
class CustomUserAdmin(BaseUserAdmin):
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')}),
('Permissions', {'fields': ('is_staff', 'is_active', 'groups', 'user_permissions')}),
('Important dates', {'fields': ('last_login', 'date_joined')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
search_fields = ('email',)
ordering = ('email',)
admin.site.register(CustomUser, CustomUserAdmin)
6. Create Custom Forms (Optional but Recommended)
Using dedicated forms ensures proper validation for the admin and any registration views.
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',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('email', 'first_name', 'last_name')
Running Migrations
Now that the model is defined, create and apply migrations.
python manage.py makemigrations accounts
python manage.py migrate
If you’re starting a fresh project, the migration will create the accounts_customuser table. For existing projects, consider using a data migration to copy data from auth_user to the new table before dropping the old model.
Using the Custom User Model in Your Code
Authentication Backends
The default authentication backend works out of the box because it relies on AUTH_USER_MODEL. However, if you need to authenticate with a phone number or a third‑party token, create a custom backend:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
User = get_user_model()
class EmailOrPhoneBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(email=username) # or phone_number=username
except User.DoesNotExist:
return None
if user.check_password(password):
return user
return None
Then add it to AUTHENTICATION_BACKENDS in settings.py.
Referencing the User Model
Never import the model directly with from accounts.models import CustomUser in reusable apps. Use Django’s helper:
from django.contrib.auth import get_user_model
User = get_user_model()
This approach respects the AUTH_USER_MODEL setting and keeps your code portable.
Testing Your Custom User Model
Write unit tests to guarantee that user creation, authentication, and admin integration behave as expected.
from django.test import TestCase
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomUserModelTests(TestCase):
def test_create_user_with_email(self):
user = User.objects.create_user(email='test@example.com', password='test123')
self.assertEqual(user.email, 'test@example.com')
self.assertTrue(user.check_password('test123'))
self.assertFalse(user.is_staff)
def test_create_superuser(self):
admin = User.objects.create_superuser(email='admin@example.com', password='admin123')
self.assertTrue(admin.is_staff)
self.assertTrue(admin.is_superuser)
Common Pitfalls & How to Avoid Them
- Changing the user model after migrations: If you forget to set
AUTH_USER_MODELbefore the first migration, you’ll need to create a new database or perform a complex data migration. - Hard‑coding the model name in foreign keys: Use
settings.AUTH_USER_MODELinForeignKeydefinitions, e.g.author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE). - Missing manager methods: Forgetting to implement
create_superuserwill break thecreatesuperusercommand. - Admin registration errors: Ensure the admin form classes reference the custom model; otherwise you’ll see “User has no attribute ‘username’”.
- Third‑party packages: Some older packages still assume
usernameexists. Check their documentation or fork them to useemailinstead.
Best Practices Checklist
- Define the custom model before running
manage.py migrate. - Always reference the user model via
get_user_model()orsettings.AUTH_USER_MODEL. - Provide a dedicated
Leave a Reply