User Model

Difficulty: Advanced

Django ships with a built-in User model in django.contrib.auth that provides fields for username, password, email, first_name, last_name, is_active, is_staff, is_superuser, last_login, and date_joined. The password is stored as a hash using PBKDF2 by default, never as plain text.

The built-in User model is sufficient for many applications, but you should always reference it indirectly using settings.AUTH_USER_MODEL (in models, for ForeignKey) or get_user_model() (in code that runs at runtime). This allows you to swap the User model later without changing every reference throughout your codebase.

Django provides several ways to work with users. User.objects.create_user() creates a regular user with a properly hashed password. User.objects.create_superuser() creates a superuser. The authenticate() function verifies credentials and returns the user or None. The check_password() and set_password() methods handle password verification and setting.

The User model includes important methods: is_authenticated (True for real users, False for AnonymousUser), has_perm() checks specific permissions, has_perms() checks multiple permissions, and get_all_permissions() returns all permissions. The user object is automatically attached to every request as request.user by the authentication middleware.

Django strongly recommends creating a custom user model at the start of every new project, even if you do not need extra fields initially. Changing the User model after the initial migration is extremely difficult because it requires migrating all existing tables that reference User. Starting with a custom model (even an empty one that just extends AbstractUser) gives you the flexibility to add fields later.

The AbstractUser class provides the same fields and functionality as the default User model but can be extended with additional fields. AbstractBaseUser is a more bare-bones base class that only provides password hashing and session management - you must define all other fields yourself. Most projects should use AbstractUser unless they need a fundamentally different user model (like using email instead of username for login).

Code examples

Working with the Built-in User Model

from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout

# Create a user
user = User.objects.create_user(
    username='john',
    email='john@example.com',
    password='securepassword123',
)

# Create a superuser
admin = User.objects.create_superuser(
    username='admin',
    email='admin@example.com',
    password='adminpass123',
)

# Authenticate
user = authenticate(username='john', password='securepassword123')
if user is not None:
    print('Authentication successful')
    # login(request, user)  # In a view

# Check and set password
user.check_password('securepassword123')  # True
user.set_password('newpassword456')
user.save()

# User properties
print(user.is_authenticated)   # True
print(user.is_staff)           # False
print(user.is_superuser)       # False
print(user.get_full_name())    # 'John Doe'

create_user() hashes the password automatically. authenticate() verifies credentials. set_password() hashes the new password (you must call save() after). Never store or compare passwords as plain text.

Referencing the User Model

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models

# In models: use settings.AUTH_USER_MODEL (string reference)
class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,  # NOT User directly
        on_delete=models.CASCADE,
    )
    title = models.CharField(max_length=200)

# In runtime code: use get_user_model() (actual model class)
User = get_user_model()
users = User.objects.filter(is_active=True)

# WHY:
# settings.AUTH_USER_MODEL returns a string like 'auth.User'
# or 'accounts.CustomUser' - works at class definition time
#
# get_user_model() returns the actual model class
# - works at runtime, after all apps are loaded
#
# NEVER do this:
# from django.contrib.auth.models import User  # Hardcoded!

settings.AUTH_USER_MODEL is for model definitions (ForeignKey, M2M). get_user_model() is for runtime code (queries, forms). Both ensure your code works with custom user models. Never import User directly.

Custom User with AbstractUser

# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    # Add custom fields
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', blank=True)
    date_of_birth = models.DateField(null=True, blank=True)
    phone = models.CharField(max_length=20, blank=True)

    def __str__(self):
        return self.username

# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'

# 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):
    fieldsets = UserAdmin.fieldsets + (
        ('Profile', {'fields': ('bio', 'avatar', 'date_of_birth', 'phone')}),
    )
    add_fieldsets = UserAdmin.add_fieldsets + (
        ('Profile', {'fields': ('bio', 'avatar')}),
    )

AbstractUser gives you all built-in User fields plus your custom ones. Set AUTH_USER_MODEL in settings.py BEFORE running the first migration. Extend UserAdmin to include custom fields in the admin.

Custom User with AbstractBaseUser

# accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models

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)
    name = models.CharField(max_length=200)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(auto_now_add=True)

    objects = CustomUserManager()

    USERNAME_FIELD = 'email'       # Field used for login
    REQUIRED_FIELDS = ['name']     # Required for createsuperuser

    def __str__(self):
        return self.email

AbstractBaseUser is for fundamentally different user models (e.g., email-based login). You must define USERNAME_FIELD, REQUIRED_FIELDS, is_active, is_staff, and a custom manager. PermissionsMixin adds permission support.

Key points

Concepts covered

User, AbstractUser, AUTH_USER_MODEL, get_user_model, UserManager