When you start a new Django project, the default User model works fine for simple sites, but real‑world applications often demand extra fields, alternative authentication methods, or custom validation rules. That’s where a custom user model shines. In this guide we’ll walk through why you should replace the built‑in model, how to design a robust custom user, and step‑by‑step code snippets that you can copy‑paste into your own project. By the end, you’ll have a fully functional user model that scales with your business needs and improves SEO‑friendly URLs, admin usability, and security.
Why Replace Django’s Default User Model?
Before diving into the implementation, it’s important to understand the motivations behind a custom user model. The default django.contrib.auth.models.User is intentionally simple, but it has several limitations:
- Fixed fields: Username, email, first name, last name, and password only.
- Username‑centric login: Many modern apps prefer email‑based authentication.
- Hard to extend later: Adding fields after migrations have been applied can cause circular dependencies.
- Internationalization challenges: Some locales need non‑ASCII usernames or additional profile data.
Creating a custom user model from day one avoids painful refactors and keeps your database schema clean.
Planning Your Custom User Model
A well‑planned model saves time and bugs. Follow these checklist items before writing any code:
- Identify required fields: e.g.,
email,full_name,date_of_birth,is_premium. - Choose the authentication identifier: email is the most common choice for SEO‑friendly login URLs.
- Decide on abstract vs. concrete inheritance: Use
AbstractBaseUserfor full control, orAbstractUserto keep most default behavior. - Plan admin integration: Custom admin classes make managing users easier for staff.
Step‑by‑Step Implementation
1. Create a New Django App for Authentication
Keeping authentication logic isolated improves maintainability.
python -m venv venv
source venv/bin/activate
pip install django
django-admin startproject mysite
cd mysite
python manage.py startapp accounts
2. Define the Custom User Model
In accounts/models.py extend AbstractBaseUser and PermissionsMixin. This gives you password handling and group/permission support while letting you define any fields you need.
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser, PermissionsMixin, BaseUserManager
)
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)
extra_fields.setdefault('is_active', 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('email address', unique=True)
full_name = models.CharField(max_length=150, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
is_staff = models.BooleanField(
default=False,
help_text='Designates whether the user can log into the admin site.'
)
is_active = models.BooleanField(
default=True,
help_text='Designates whether this user should be treated as active.'
)
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. Tell Django to Use the New Model
Add the following line to mysite/settings.py before any app that imports auth:
AUTH_USER_MODEL = 'accounts.CustomUser'
Now all built‑in authentication utilities (login, password reset, admin) will reference your custom model.
4. Create and Apply Migrations
python manage.py makemigrations accounts
python manage.py migrate
If you’re starting a fresh project, this will create the accounts_customuser table with your fields. For existing projects, you must create a data migration or use a third‑party library like django‑swap‑auth to avoid data loss.
5. Update the Admin Interface
A clean admin improves SEO workflows by letting staff edit user profiles directly.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
model = CustomUser
list_display = ('email', 'full_name', 'is_staff', 'is_active')
list_filter = ('is_staff', 'is_active')
ordering = ('email',)
search_fields = ('email', 'full_name')
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('full_name', 'date_of_birth')}),
('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')}
),
)
6. Adjust Authentication Forms (Optional)
If you want email‑based login forms, override the default authentication form:
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class EmailAuthenticationForm(AuthenticationForm):
username = forms.EmailField(label='Email', max_length=254)
Then point your login view to use EmailAuthenticationForm or configure it in settings.py with LOGIN_FORM_CLASS if you use a third‑party auth package.
7. Use the Custom Model in Views and Serializers
When working with Django Rest Framework (DRF) or generic class‑based views, reference the custom model via settings.AUTH_USER_MODEL to stay decoupled.
from django.conf import settings
from django.contrib.auth import get_user_model
User = get_user_model()
# Example DRF serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'full_name', 'date_of_birth')
Best Practices for a Secure and SEO‑Friendly User Model
Beyond the basic implementation, follow these guidelines to keep your authentication layer robust and search‑engine friendly:
- Normalize email case: Django’s
normalize_emailalready lowercases the domain part; consider storing the full address in lower case to avoid duplicate accounts. - Enforce strong passwords: Use
AUTH_PASSWORD_VALIDATORSinsettings.pyto require length, numeric, and special‑character checks. - Implement email verification: Send a tokenized link after registration; verified emails improve trust signals for SEO.
- Leverage
last_loginanddate_joined: These timestamps help you build active‑user metrics that can be displayed publicly (e.g., “Join date: Jan 2024”). - Use UUID as primary key (optional): For large‑scale apps, replace the default integer
idwith aUUIDFieldto make URLs harder to guess.
Common Pitfalls and How to Avoid Them
Even experienced Django developers run into snags when customizing the user model. Here are the most frequent issues and quick fixes:
Changing the User Model After Migrations
Once you have run manage.py migrate, swapping AUTH_USER_MODEL is risky. The safe route is:
- Create a new project or a fresh database for testing.
- Write a data migration that copies existing user data to the new model.
- Update foreign keys with
settings.AUTH_USER_MODELinForeignKeydefinitions.
Forgotten References in Third‑Party Packages
Some packages still import django.contrib.auth.get_user_model() correctly, but others hard‑code auth.User. Check the package documentation or fork the repo to replace the import.
Admin Password Reset Not Working
If you see “User has no attribute ‘email’” errors, ensure your CustomUserAdmin includes email in fieldsets and that USERNAME_FIELD = 'email' is set.
Leave a Reply