Custom user models

Django · Advanced · 12 min

Changing the user model after the first migration is painful, so it's a day-one decision.

Django's docs recommend that every new project define a custom user model, even an empty one, because switching later means rewriting migrations and foreign keys across apps.

The easy version extends AbstractUser, keeping username, password, permissions and the admin integration:

class User(AbstractUser):
    pass

and in settings: AUTH_USER_MODEL = "accounts.User", set before the first migrate.

To log in with email instead of a username, either keep AbstractUser and set USERNAME_FIELD = "email" with a unique email field and REQUIRED_FIELDS = [] (also removing the username field), or build on AbstractBaseUser and PermissionsMixin with a custom manager implementing create_user and create_superuser.

Referencing the user model correctly:

  • in model fields: models.ForeignKey(settings.AUTH_USER_MODEL, ...)
  • in other code: get_user_model() returns the active class
  • never from django.contrib.auth.models import User in reusable code

Register it in the admin with admin.site.register(User, UserAdmin) (or a UserAdmin subclass with your fields).

For extra per-user data that isn't needed for authentication, such as a bio or preferences, a separate Profile model with a OneToOneField to the user keeps the user table lean, and can be added at any time.

Example

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

class EmailUserManager(UserManager):
    def _create_user(self, email, password, **extra):
        user = self.model(email=self.normalize_email(email), **extra)
        user.set_password(password)
        user.save(using=self._db)
        return user
    def create_user(self, email, password=None, **extra):
        return self._create_user(email, password, **extra)
    def create_superuser(self, email, password=None, **extra):
        return self._create_user(email, password, is_staff=True, is_superuser=True, **extra)

class User(AbstractUser):
    username = None
    email = models.EmailField(unique=True)
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []
    objects = EmailUserManager()

# settings.py  (before the first migrate!)
AUTH_USER_MODEL = "accounts.User"
# elsewhere: author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Remember

  • Define a custom user model at project start; set AUTH_USER_MODEL before the first migration
  • Extend AbstractUser for the easy path
  • Reference users via settings.AUTH_USER_MODEL and get_user_model()

Watch out for

  • Importing django.contrib.auth.models.User directly in apps, which breaks with a custom user model.

Check yourself

  1. How should a ForeignKey to the user be declared in a reusable app?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.djangoproject.com/en/6.0/topics/auth/customizing/#substituting-a-custom-user-model

Builds on

Unlocks

Nothing builds on this yet.