Python Django Custom User Model Guide

Written by

in

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, or date_of_birth without 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_FIELD tells Django which field to treat as the unique identifier.
  • REQUIRED_FIELDS lists fields that will be prompted for when creating a superuser via createsuperuser.
  • We keep username for 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:

  1. Run python manage.py makemigrations myapp to generate the custom user migration.
  2. Run python manage.py migrate to apply the migration. If you already have a database with the default auth_user table, 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, swapping AUTH_USER_MODEL is painful. Always decide on a custom model at project start.
  • Forgotten AUTH_USER_MODEL reference: Third‑party apps that reference auth.User directly will break. Use settings.AUTH_USER_MODEL in foreign keys: user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE).
  • Missing manager methods: When using AbstractBaseUser, you must provide create_user and create_superuser in the custom manager; otherwise createsuperuser will fail.
  • Admin password reset links: Ensure the admin’s password reset form uses the correct field name (email instead of username) 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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *