Module 5: Custom User Model & Authentication Setup
🎯 What You Will Learn in This Module
By the end of this tutorial, you will:
- Understand why every Django project should start with a custom user model
- Know the difference between
AbstractUserandAbstractBaseUser - Build a CustomUser model with email-based login, roles, and verification
- Configure
USERNAME_FIELDandREQUIRED_FIELDScorrectly - Register your custom user in the Django Admin
- Set
AUTH_USER_MODELinsettings.pybefore your first migration - Avoid the painful mistake of switching user models mid-project
📖 1. Why You MUST Start with a Custom User Model
Django ships with a built-in User model. It works fine for quick prototypes, but here's the hard truth:
🚨 Changing your user model after your first migration is extremely painful. It requires rebuilding your entire database from scratch.
The built-in User model has these limitations:
- Uses
usernameas the primary login field (modern apps prefer email) - Limited fields: no
role,phone,is_verified, oravatar - No easy way to add fields without creating a separate Profile model
The golden rule: Even if you don't need custom fields today, start with a custom user model. Future-you will be grateful.
🏗️ 2. AbstractUser vs. AbstractBaseUser
Django provides two base classes for building custom users:
| Base Class | What It Provides | Best For |
|---|---|---|
AbstractUser | Full default user implementation (username, email, first_name, last_name, password, groups, permissions) | Most projects. You extend it by adding a few extra fields. |
AbstractBaseUser | Only the password and last_login fields. You build everything else from scratch. | Highly custom authentication (e.g., login with phone number only). |
For 95% of projects, use AbstractUser. It gives you all the standard Django auth machinery while letting you add your own fields.
📝 3. Creating Your Custom User Model
Step 1: Update users/models.py
Replace the contents of your users app (created in Module 1) with this:
1# users/models.py 2 3from django.contrib.auth.models import AbstractUser 4from django.db import models 5 6 7class CustomUser(AbstractUser): 8 """ 9 Custom user model that uses email for login instead of username. 10 Includes role-based access and email verification. 11 """ 12 13 # Override email to enforce uniqueness 14 email = models.EmailField(unique=True) 15 16 # Track whether the user has verified their email address 17 is_verified = models.BooleanField(default=False) 18 19 # Role-based access control 20 ROLE_CHOICES = [ 21 ('reader', 'Reader'), 22 ('author', 'Author'), 23 ('moderator', 'Moderator'), 24 ('admin', 'Admin'), 25 ] 26 role = models.CharField( 27 max_length=20, 28 choices=ROLE_CHOICES, 29 default='reader' 30 ) 31 32 # Tell Django to use email as the unique identifier for login 33 USERNAME_FIELD = 'email' 34 35 # Fields required when creating a user via createsuperuser or forms 36 REQUIRED_FIELDS = ['username', 'first_name', 'last_name'] 37 38 def __str__(self): 39 return self.email
Key Concepts Explained
USERNAME_FIELD = 'email'
This tells Django: "When someone logs in, ask for their email, not their username." The email field becomes the primary identifier.
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
These fields are required when creating a user via the command line (createsuperuser) or some forms.
💡 Important:
USERNAME_FIELD(which isREQUIRED_FIELDS— doing so causes Django to ask for the email twice duringcreatesuperuser.
is_verified
A custom boolean flag. We'll use this in later modules to enforce email verification before allowing full access.
role
Uses Django's choices system to restrict input to predefined values. This is perfect for role-based permissions without the complexity of Django's Group system.
⚙️ 4. Registering the Model in Django Admin
Django Admin is your built-in backoffice. By default, it won't know how to display your custom user properly. You need to unregister the default User admin and register your own.
1# users/admin.py 2 3from django.contrib import admin 4from django.contrib.auth.admin import UserAdmin 5from .models import CustomUser 6 7 8@admin.register(CustomUser) 9class CustomUserAdmin(UserAdmin): 10 """ 11 Custom admin configuration for the CustomUser model. 12 Extends the default UserAdmin to include our new fields. 13 """ 14 15 # Columns shown in the user list page 16 list_display = [ 17 'email', 'username', 'first_name', 'last_name', 18 'role', 'is_verified', 'is_staff', 'date_joined' 19 ] 20 21 # Filters available in the right sidebar 22 list_filter = ['role', 'is_verified', 'is_staff', 'is_active', 'date_joined'] 23 24 # Field organization when editing an existing user 25 fieldsets = UserAdmin.fieldsets + ( 26 ('Additional Info', { 27 'fields': ('role', 'is_verified') 28 }), 29 ) 30 31 # Fields shown when creating a new user in admin 32 add_fieldsets = UserAdmin.add_fieldsets + ( 33 ('Additional Info', { 34 'fields': ('role',) 35 }), 36 ) 37 38 # Search functionality 39 search_fields = ['email', 'username', 'first_name', 'last_name'] 40 41 # Default ordering 42 ordering = ['-date_joined']
What this does:
list_display: Controls which columns appear in the admin user listlist_filter: Adds filter boxes for quick filteringfieldsets: Organizes fields into collapsible sections when editing a useradd_fieldsets: Controls the "Add User" form layoutordering: Sorts users by newest first
🔧 5. Configuring settings.py
This is the most critical step. You must tell Django to use your custom model instead of the default one.
Open myproject/settings.py and add this line at the bottom:
1# myproject/settings.py 2 3# ... existing settings ... 4 5# Tell Django to use our custom user model 6AUTH_USER_MODEL = 'users.CustomUser'
Why the format app_name.ModelName?
Django needs to know which app contains the model. Here, users is the app label and CustomUser is the model class name.
🚨 CRITICAL TIMING: You must set
AUTH_USER_MODELbefore running your first migration. If you runmigratewith the default user model, then later switch to a custom one, Django's auth system will break and you'll need to delete your database and start over.
🗄️ 6. Creating Migrations & Superuser
Now that your model is defined and configured, let's create the database tables.
Step 1: Create Migrations
1python manage.py makemigrations users
You should see output like:
Migrations for 'users':
users/migrations/0001_initial.py
- Create model CustomUser
Step 2: Apply Migrations
1python manage.py migrate
Step 3: Create a Superuser
1python manage.py createsuperuser
Because we set USERNAME_FIELD = 'email', Django will prompt:
Email address: admin@example.com
Username: admin
First name: Admin
Last name: User
Password:
Password (again):
Superuser created successfully.
Notice it asks for email first, then username. This confirms your custom authentication is working.
Step 4: Test in Admin
Run the server:
1python manage.py runserver
Visit http://127.0.0.1:8000/admin/ and log in with your superuser email and password. You should see:
- The Users section with your new fields (
role,is_verified) - Email shown as the primary identifier in the list view
🧪 7. Practice Task — Extend Your Custom User
Task Requirements
Enhance your CustomUser model by adding:
- A
phonefield (CharField, max_length 15, optional) - Ensure
date_joinedis visible and properly handled
💡 Note:
date_joinedis already inherited fromAbstractUser. Your task is to ensure it's visible in the admin list view and properly utilized.
Updated Model
1# users/models.py 2 3from django.contrib.auth.models import AbstractUser 4from django.db import models 5 6 7class CustomUser(AbstractUser): 8 email = models.EmailField(unique=True) 9 is_verified = models.BooleanField(default=False) 10 11 ROLE_CHOICES = [ 12 ('reader', 'Reader'), 13 ('author', 'Author'), 14 ('moderator', 'Moderator'), 15 ('admin', 'Admin'), 16 ] 17 role = models.CharField( 18 max_length=20, 19 choices=ROLE_CHOICES, 20 default='reader' 21 ) 22 23 # NEW FIELD: Phone number 24 phone = models.CharField(max_length=15, blank=True) 25 26 # date_joined is inherited from AbstractUser automatically 27 # It is defined as: date_joined = models.DateTimeField(auto_now_add=True) 28 29 USERNAME_FIELD = 'email' 30 REQUIRED_FIELDS = ['username', 'first_name', 'last_name'] 31 32 class Meta: 33 ordering = ['-date_joined'] 34 35 def __str__(self): 36 return self.email
Updated Admin
1# users/admin.py 2 3from django.contrib import admin 4from django.contrib.auth.admin import UserAdmin 5from .models import CustomUser 6 7 8@admin.register(CustomUser) 9class CustomUserAdmin(UserAdmin): 10 list_display = [ 11 'email', 'username', 'first_name', 'last_name', 12 'phone', 'role', 'is_verified', 'is_staff', 'date_joined' 13 ] 14 list_filter = ['role', 'is_verified', 'is_staff', 'is_active', 'date_joined'] 15 16 fieldsets = UserAdmin.fieldsets + ( 17 ('Additional Info', { 18 'fields': ('role', 'is_verified', 'phone') 19 }), 20 ) 21 22 add_fieldsets = UserAdmin.add_fieldsets + ( 23 ('Additional Info', { 24 'fields': ('role', 'phone') 25 }), 26 ) 27 28 search_fields = ['email', 'username', 'first_name', 'last_name', 'phone'] 29 ordering = ['-date_joined']
After making these changes:
1python manage.py makemigrations 2python manage.py migrate
🧠 8. Referencing the User Model Correctly
Once you use a custom user model, never import User directly anywhere in your project (including other apps' models.py).
The Wrong Way ❌
1from django.contrib.auth.models import User # Don't do this!
The Right Way ✅
1from django.contrib.auth import get_user_model 2 3User = get_user_model()
Or in ForeignKey fields, use the string reference:
1author = models.ForeignKey( 2 'users.CustomUser', # or settings.AUTH_USER_MODEL 3 on_delete=models.CASCADE 4)
Or import settings:
1from django.conf import settings 2 3author = models.ForeignKey( 4 settings.AUTH_USER_MODEL, 5 on_delete=models.CASCADE 6)
Why this matters: Using get_user_model() ensures that if you ever change AUTH_USER_MODEL in the future, all your code still works.
❌ Common Mistakes & How to Fix Them
| Mistake | Error / Symptom | Solution |
|---|---|---|
Setting AUTH_USER_MODEL after first migration | IntegrityError, Relation already exists, migration conflicts | Delete database and migration files, start fresh |
Including email in REQUIRED_FIELDS | createsuperuser asks for email twice | Remove email from REQUIRED_FIELDS |
Forgetting unique=True on email | Multiple users can register with same email | Add email = models.EmailField(unique=True) |
Not running makemigrations after model changes | Admin shows old fields or throws errors | Always run makemigrations then migrate |
Importing User directly | ImportError or wrong user model referenced | Use get_user_model() everywhere |
Wrong AUTH_USER_MODEL format | LookupError: No installed app | Use format: 'app_label.ModelName' (e.g., ) |
✅ Module 5 Summary
| Concept | Key Takeaway |
|---|---|
| Custom User Model | Always create one at the start of every project |
| AbstractUser | Extend the default user. Best for most projects. |
| AbstractBaseUser | Build from scratch. For highly custom auth. |
USERNAME_FIELD | Which field is used for login (email vs username) |
REQUIRED_FIELDS | Extra fields required during createsuperuser |
AUTH_USER_MODEL | Tells Django which model to use system-wide |
get_user_model() | The safe way to reference the user model anywhere |
| Admin Registration | Extend UserAdmin to include custom fields |
is_verified | Custom flag for email verification (used in Module 12) |
role | Simple role-based access using choices |
🚪 What's Next?
In Module 6, we will master the Django Admin Panel. You'll learn to customize list views, add inline editors, create custom admin actions (like bulk export to CSV), and optimize admin queries with select_related. We'll also build a rich admin interface for your blog's posts, categories, and comments.
Before proceeding, make sure:
- Your
CustomUsermodel is created inusers/models.py AUTH_USER_MODEL = 'users.CustomUser'is set insettings.py- You've run
makemigrationsandmigratesuccessfully - You created a superuser and can log into
/admin/ - You can see your custom fields (
role,is_verified,phone) in the admin - You understand why
get_user_model()is better than importingUserdirectly
Your authentication foundation is solid. Ready to build a powerful admin dashboard? 🎛️🚀