Django: Change User Model
Three weeks into my Django project, I realized I needed to add a phone number field to the User model. Simple, right? Just add the field and migrate.
Wrong.
Django’s default User model is hard to customize after you’ve created your first migrations. I tried to switch to a custom user model and got:
ValueError: Dependency on app with no migrations: accounts
auth.0001_initial depends on accounts.CustomUser but accounts has no migrations.
Then I tried to create migrations:
python manage.py makemigrations
SystemCheckError: AUTH_USER_MODEL refers to model 'accounts.CustomUser'
that has not been installed
The solution? I had to start over. Drop the database. Delete all migrations. Start fresh with a custom user model from the beginning.
“If you’re starting a new project, it’s highly recommended to set up a custom user model, even if the default User model is sufficient for you.”
The reason: Changing user models after you’ve created migrations is extremely difficult. It requires:
- Dropping the database
- Deleting all migrations
- Recreating everything
- Losing all data (unless you write complex data migrations)
Or you live with the default User model forever and use a separate Profile model for extra fields.
Common Custom User Model Mistakes
Mistake 1: Creating Custom User After Migrations
Problem:
# You already ran initial migrations
python manage.py migrate
# Now you try to add custom user:
# accounts/models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
phone = models.CharField(max_length=20)
# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
# Try to migrate:
python manage.py makemigrations
# Error: Dependencies broken!
What happens: Django’s auth system already created tables using the default User model. You can’t just switch to a different model mid-project.
The only real fix:
# Nuclear option - start over
rm db.sqlite3
find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
find . -path "*/migrations/*.pyc" -delete
# Now create migrations fresh:
python manage.py makemigrations
python manage.py migrate
This deletes all your data. In production, this isn’t an option.
Mistake 2: Wrong Base Class
The Problem:
# accounts/models.py
from django.db import models
# ❌ Wrong - inheriting from models.Model
class CustomUser(models.Model):
email = models.EmailField(unique=True)
password = models.CharField(max_length=128)
# Missing all the auth functionality!
What happens: Your user model doesn’t have Django’s authentication methods. No check_password(), no is_authenticated, no groups or permissions.
The fix:
# ✅ Correct - inherit from AbstractUser or AbstractBaseUser
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
# Keeps all Django auth functionality
# Add your custom fields
phone = models.CharField(max_length=20, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
Mistake 3: Not Setting AUTH_USER_MODEL
The problem:
# You created CustomUser model but forgot to tell Django:
# accounts/models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
phone = models.CharField(max_length=20)
# settings.py - Missing AUTH_USER_MODEL!
What happens: Django still uses the default User model. Your CustomUser table gets created but nothing uses it.
The fix:
# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser' # ✅ Add this
# Format: 'app_name.ModelName'
Mistake 4: Using get_user_model() Incorrectly
The problem:
# models.py
from django.contrib.auth.models import User # ❌ Hard-coded
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE) # ❌ Wrong
What happens: If you switch to a custom user model later, this foreign key points to the wrong model.
The fix:
# models.py
from django.conf import settings
class Post(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL, # ✅ Correct
on_delete=models.CASCADE
)
Or in code (not models):
from django.contrib.auth import get_user_model
User = get_user_model() # ✅ Gets whatever AUTH_USER_MODEL is set to
# Now use User normally:
user = User.objects.get(email='test@example.com')
Mistake 5: Wrong Manager in CustomUser
The problem:
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
email = models.EmailField(unique=True)
# ❌ Missing: USERNAME_FIELD and required field settings
What happens: You can’t create users with User.objects.create_user() or login fails because Django doesn't know which field is the username.
The fix:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Email is required')
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)
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)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
objects = CustomUserManager() # ✅ Custom manager
USERNAME_FIELD = 'email' # ✅ Login with email
REQUIRED_FIELDS = [] # ✅ No extra required fields for createsuperuser
def __str__(self):
return self.email
The Right Way: Custom User From Start
Step 1: Create New Django Project
django-admin startproject myproject
cd myproject
python manage.py startapp accounts
Step 2: Create Custom User Model FIRST
Before running any migrations:
# accounts/models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
"""
Custom user model that extends Django's default User.
Add custom fields here from the start.
"""
# Example custom fields:
phone = models.CharField(max_length=20, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.email or self.username
Step 3: Configure Settings
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts', # ✅ Your accounts app
]
# ✅ Set custom user model BEFORE first migration
AUTH_USER_MODEL = 'accounts.CustomUser'
Step 4: Create and Run Migrations
# Now create migrations
python manage.py makemigrations
# Output should show:
# Migrations for 'accounts':
# accounts/migrations/0001_initial.py
# - Create model CustomUser
# Run migrations
python manage.py migrate
# Create superuser (tests that it works)
python manage.py createsuperuser
Step 5: Update Admin
# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
# Add custom fields to admin
fieldsets = UserAdmin.fieldsets + (
('Custom Fields', {'fields': ('phone', 'date_of_birth', 'bio')}),
)
add_fieldsets = UserAdmin.add_fieldsets + (
('Custom Fields', {'fields': ('phone', 'date_of_birth', 'bio')}),
)
Using Email as Username
Many projects want users to login with email instead of username:
# accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.db import models
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Email address is required')
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=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
# Custom fields
phone = models.CharField(max_length=20, blank=True)
objects = CustomUserManager()
USERNAME_FIELD = 'email' # Login with email
REQUIRED_FIELDS = [] # createsuperuser will prompt for email only
class Meta:
verbose_name = 'user'
verbose_name_plural = 'users'
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
Migrating Existing Project to Custom User
If you already have migrations, here’s the painful process:
Option 1: Start Fresh (Development Only)
# WARNING: Deletes all data!
# 1. Backup data if needed
python manage.py dumpdata > backup.json
# 2. Delete database
rm db.sqlite3
# 3. Delete all migrations
find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
# 4. Create custom user model
# (See examples above)
# 5. Set AUTH_USER_MODEL in settings
AUTH_USER_MODEL = 'accounts.CustomUser'
# 6. Create fresh migrations
python manage.py makemigrations
python manage.py migrate
# 7. Create superuser
python manage.py createsuperuser
Option 2: Use Profile Model (Easier)
If you can’t start over, use a separate Profile model:
# accounts/models.py
from django.contrib.auth.models import User
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
phone = models.CharField(max_length=20, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
bio = models.TextField(blank=True)
def __str__(self):
return f'{self.user.username} Profile'
# Auto-create profile when user is created
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
Usage:
# Access profile:
user = User.objects.get(username='john')
phone = user.profile.phone
# Update profile:
user.profile.phone = '555-1234'
user.profile.save()
Option 3: Data Migration (Advanced)
For production with existing data:
# Create empty migration
python manage.py makemigrations accounts --empty
# Edit the migration file:
from django.db import migrations
from django.contrib.auth.models import User
def migrate_users_to_custom(apps, schema_editor):
# This is complex and error-prone
# Only attempt if you know what you're doing
pass
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.RunPython(migrate_users_to_custom),
]
Forms and Authentication
Update your forms to use custom user:
# accounts/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomUserCreationForm(UserCreationForm):
email = forms.EmailField(required=True)
phone = forms.CharField(max_length=20, required=False)
class Meta:
model = User
fields = ('username', 'email', 'phone', 'password1', 'password2')
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
Testing With Custom User
# tests.py
from django.test import TestCase
from django.contrib.auth import get_user_model
User = get_user_model()
class UserTests(TestCase):
def test_create_user(self):
user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
self.assertEqual(user.email, 'test@example.com')
self.assertTrue(user.check_password('testpass123'))
self.assertTrue(user.is_active)
self.assertFalse(user.is_staff)
self.assertFalse(user.is_superuser)
def test_create_superuser(self):
admin = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='admin123'
)
self.assertEqual(admin.email, 'admin@example.com')
self.assertTrue(admin.is_staff)
self.assertTrue(admin.is_superuser)
Quick Reference
AbstractUser vs AbstractBaseUser
- Use AbstractUser when:
- You want to keep username, first_name, last_name fields
- You just want to add extra fields
- You want Django’s default behavior
-
from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): phone = models.CharField(max_length=20) - Use AbstractBaseUser when:
- You want complete control
- You want to use email instead of username
- You want to remove default fields
-
from django.contrib.auth.models import AbstractBaseUser class CustomUser(AbstractBaseUser): email = models.EmailField(unique=True) # Define everything yourself
Always Use get_user_model()
# ❌ Don't do this:
from django.contrib.auth.models import User
# ✅ Do this in code:
from django.contrib.auth import get_user_model
User = get_user_model()
# ✅ Do this in models:
from django.conf import settings
ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
Deployment Checklist
Before deploying with custom user:
- Custom user model created
- AUTH_USER_MODEL set in settings
- All models use settings.AUTH_USER_MODEL for foreign keys
- Custom admin configuration added
- Forms updated to use get_user_model()
- Tests created and passing
- Migrations created and tested on fresh database
- createsuperuser works correctly
Key Takeaways
- Always create custom user model at project start — even if you don’t need it yet
- Use AbstractUser for simple customization (keeps all default fields)
- Use AbstractBaseUser for complete control (define everything yourself)
- Set AUTH_USER_MODEL before first migration — can’t easily change it later
- Use settings.AUTH_USER_MODEL in foreign keys, not direct import
- Use get_user_model() in code to get user model
- Profile model is easier if you already have migrations
- Test on fresh database before deploying
The Bottom Line
Django’s user model is hard to change after you start. The solution is simple: create a custom user model from day one, even if you don’t need custom fields yet.
It takes 5 minutes to set up. It saves days of pain later.
Start every new Django project with:
# 1. Create accounts app
python manage.py startapp accounts
# 2. Create CustomUser inheriting from AbstractUser
# 3. Set AUTH_USER_MODEL = 'accounts.CustomUser'
# 4. Then run first migration
This way, when you need to add user fields later, you just add them to your CustomUser model and migrate. No pain, no data loss, no starting over.