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_authenticatedandis_anonymousmust be attributes or properties. If you implement them as methods, they will always evaluate toTruein a boolean context (e.g.,if user.is_authenticated:), which is a security risk. - Uniqueness: The field specified in
USERNAME_FIELDmust haveunique=Trueset in its model definition. - Manager Methods: If you use a custom manager, ensure it implements
create_userandcreate_superuserwith signatures that match the fields defined in your model'sREQUIRED_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 themake_passwordutility found indjango.contrib.auth.hashers.