Creating a robust authentication system is one of the first challenges every Django developer faces. While Django’s default User model works for simple projects, real‑world applications often need extra fields, custom permissions, or a different login identifier such as an email address. That’s where a **custom user model** comes into play. In this guide we’ll walk through why you should replace the default model, how to design a custom user model with AbstractUser or AbstractBaseUser, and the exact steps to implement, test, and migrate it safely—all while keeping SEO‑friendly keywords like “Python Django custom user model” front and center.
Why Replace Django’s Default User Model?
Before diving into code, it’s important to understand the benefits of a custom user model. Replacing the default auth.User early in a project saves you from costly migrations later and gives you full control over authentication data.
- Flexibility: Add fields such as
phone_number,profile_picture, ordate_of_birthwithout creating separate profile tables. - Custom login identifiers: Use email, mobile number, or a UUID as the primary credential instead of a username.
- Better security: Implement custom password hashing, two‑factor authentication flags, or password‑expiry policies directly in the model.
- Future‑proofing: As your app scales, you’ll already have a model that can evolve with new business requirements.
Choosing the Right Base Class
Django offers two abstract base classes for custom users:
1. AbstractUser
Extends the built‑in User model and retains the same fields (username, first_name, last_name, etc.) while allowing you to add or modify fields. This is the simplest path if you only need a few extra attributes.
2. AbstractBaseUser
Provides only the core authentication features (password handling, permissions) and leaves you to define every field, including the identifier used for login. Choose this when you want a completely different schema, such as email‑only login.
Step‑by‑Step: Building a Custom User Model with AbstractUser
Below is a practical example that adds date_of_birth and bio fields while switching the login identifier to email.
# myapp/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
class CustomUser(AbstractUser):
email = models.EmailField(_('email address'), unique=True)
date_of_birth = models.DateField(null=True, blank=True)
bio = models.TextField(max_length=500, blank=True)
USERNAME_FIELD = 'email' # Use email to log in
REQUIRED_FIELDS = ['username'] # Username is still required for admin
def __str__(self):
return self.email
Key points to note:
USERNAME_FIELDtells Django which field to treat as the unique identifier.REQUIRED_FIELDSlists fields that will be prompted for when creating a superuser viacreatesuperuser.- We keep
usernamefor backward compatibility with the admin site, but you can remove it entirely if you prefer.
Step‑by‑Step: Building a Custom User Model with AbstractBaseUser
If you want a lean user table that only stores email, password, and a few custom fields, follow this pattern:
# myapp/models.py
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import 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, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('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=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
date_joined = models.DateTimeField(default=timezone.now)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] # Email & password are required by default
def __str__(self):
return self.email
This approach gives you full control over the database schema and is ideal for API‑first projects where a username is unnecessary.
Configuring Django to Use the Custom Model
Once your model is ready, tell Django to use it globally by adding the following line to settings.py:
# settings.py
AUTH_USER_MODEL = 'myapp.CustomUser'
Make sure myapp is listed before django.contrib.auth in INSTALLED_APPS if you plan to override admin templates later.
Running Migrations Correctly
Because the user model is a core part of Django’s auth system, you must create migrations before running any other app migrations. Follow this order:
- Run
python manage.py makemigrations myappto generate the custom user migration. - Run
python manage.py migrateto apply the migration. If you already have a database with the defaultauth_usertable, consider creating a fresh database or using a data migration strategy to move existing users.
Updating the Django Admin
To manage your new user model from the admin site, register it with a custom UserAdmin class. This ensures the admin respects the new USERNAME_FIELD and displays your extra fields.
# myapp/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
model = CustomUser
list_display = ('email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser', 'is_active')
ordering = ('email',)
search_fields = ('email', 'first_name', 'last_name')
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('first_name', 'last_name', 'date_of_birth', 'bio')}),
('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', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
admin.site.register(CustomUser, CustomUserAdmin)
Common Pitfalls & How to Avoid Them
- Changing the user model after migrations: Once you have run
migrate, swappingAUTH_USER_MODELis painful. Always decide on a custom model at project start. - Forgotten
AUTH_USER_MODELreference: Third‑party apps that referenceauth.Userdirectly will break. Usesettings.AUTH_USER_MODELin foreign keys:user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE). - Missing manager methods: When using
AbstractBaseUser, you must providecreate_userandcreate_superuserin the custom manager; otherwisecreatesuperuserwill fail. - Admin password reset links: Ensure the admin’s password reset form uses the correct field name (
emailinstead ofusername) by overriding the form if necessary.
Testing Your Custom User Model
Write unit tests to guarantee that authentication works as expected. Below is a minimal test case using Django’s TestCase:
# myapp/tests.py
from django.test import TestCase
from django.contrib.auth import get_user_model
class CustomUserModelTests(TestCase):
def setUp(self):
self.User = get_user_model()
def test_create_user_with_email(self):
user = self.User.objects.create_user(
email='test@example.com',
password='strongpassword123'
)
self.assertEqual(user.email, 'test@example.com')
self.assertTrue(user.check_password('strongpassword123'))
def test_superuser_flags(self):
admin = self.User.objects.create_superuser(
email='admin@example.com',
password='adminpass'
)
self.assertTrue(admin.is_staff)
self.assertTrue(admin.is_superuser)
Running python manage