Skip to main content

Customizing the User Model

To extend or replace the default authentication model in this project, you can either subclass AbstractUser for simple field additions or AbstractBaseUser for a completely custom structure.

Extending the Default User Model

If you want to keep the default fields (username, first_name, last_name, etc.) but add extra information like a date of birth, subclass AbstractUser from django.contrib.auth.models.

from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models

class ExtensionUser(AbstractUser):
date_of_birth = models.DateField()

# Use the standard UserManager or a custom one
custom_objects = UserManager()

# Add the new field to REQUIRED_FIELDS so createsuperuser prompts for it
REQUIRED_FIELDS = AbstractUser.REQUIRED_FIELDS + ["date_of_birth"]

This approach inherits the PermissionsMixin, providing full support for groups and permissions out of the box.

Creating a Completely Custom User Model

For specialized requirements, such as using an email address as the primary identifier instead of a username, inherit from AbstractBaseUser in django.contrib.auth.base_user.

1. Define a Custom Manager

You must provide a manager that implements create_user and create_superuser. Use BaseUserManager to access utility methods like normalize_email.

from django.contrib.auth.base_user import BaseUserManager

class CustomUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None, **fields):
if not email:
raise ValueError("Users must have an email address")

user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
**fields
)
user.set_password(password)
user.save(using=self._db)
return user

def create_superuser(self, email, password, date_of_birth, **fields):
u = self.create_user(
email, password=password, date_of_birth=date_of_birth, **fields
)
u.is_admin = True
u.save(using=self._db)
return u

2. Define the User Model

The model must define USERNAME_FIELD and REQUIRED_FIELDS.

from django.contrib.auth.base_user import AbstractBaseUser
from django.db import models

class CustomUser(AbstractBaseUser):
email = models.EmailField(verbose_name="email address", max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
date_of_birth = models.DateField()
first_name = models.CharField(max_length=50)

custom_objects = CustomUserManager()

# The field used for authentication
USERNAME_FIELD = "email"

# Fields prompted for during createsuperuser (excluding USERNAME_FIELD and password)
REQUIRED_FIELDS = ["date_of_birth", "first_name"]

@property
def is_staff(self):
return self.is_admin

Configuring the Custom Model

After defining your model, you must tell the project to use it by updating your settings file. This should ideally be done at the start of a project before running any migrations.

# settings.py
AUTH_USER_MODEL = 'myapp.CustomUser'

Key Implementation Details

The USERNAME_FIELD

The USERNAME_FIELD is a string describing the name of the field on the user model that is used as the unique identifier. This field must be unique. In AbstractUser, this defaults to username. In the CustomUser example above, it is set to email.

Required Properties

AbstractBaseUser provides the core logic for password management and session auth hashing. However, it defines is_authenticated and is_anonymous as properties that always return True and False respectively.

# django/contrib/auth/base_user.py
@property
def is_authenticated(self):
"""Always return True. Way to tell if user is authenticated in templates."""
return True

@property
def is_anonymous(self):
"""Always return False. Way of comparing User objects to anonymous users."""
return False

Troubleshooting and Gotchas

  • Property vs Method: is_authenticated and is_anonymous must be attributes or properties. If you implement them as methods, they will always evaluate to True in a boolean context (e.g., if user.is_authenticated:), which is a security risk.
  • Uniqueness: The field specified in USERNAME_FIELD must have unique=True set in its model definition.
  • Manager Methods: If you use a custom manager, ensure it implements create_user and create_superuser with signatures that match the fields defined in your model's REQUIRED_FIELDS.
  • Normalization: Always use self.normalize_email(email) in your manager to ensure email addresses are stored consistently (lowercasing the domain part).
  • Password Handling: Never store raw passwords. Always use user.set_password(password) which uses the make_password utility found in django.contrib.auth.hashers.