Django cookbook · Beginner
Start a project with a custom user model
Use email as the login field and keep room to add profile fields later without a painful migration.
Approach
Define a custom user model before the first migration, even if it only subclasses AbstractUser. Point
AUTH_USER_MODEL at it and always refer to users through get_user_model() or settings.AUTH_USER_MODEL.
Solution
# 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=None, **extra):
return super().create_user(username=email, email=email, password=password, **extra)
def create_superuser(self, email, password=None, **extra):
return super().create_superuser(username=email, email=email, password=password, **extra)
class User(AbstractUser):
email = models.EmailField(unique=True)
display_name = models.CharField(max_length=100, blank=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = EmailUserManager()
# settings.py
AUTH_USER_MODEL = "accounts.User"
# orders/models.py
from django.conf import settings
class Order(models.Model):
customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="orders")
Pitfalls
- Switching to a custom user model after the first migration; it requires rewriting migrations and data.
- Importing
django.contrib.auth.models.Userdirectly in other apps. - Case-sensitive email uniqueness; normalise emails (lowercase) on save or use a functional unique constraint.
Variations
- Keep profile data in a separate one-to-one
Profilemodel if it's large or owned by another app. - Add a
UniqueConstraint(Lower("email"), name="unique_email_ci")for case-insensitive uniqueness.
Further reading: Substituting a custom user model
Reference: https://docs.djangoproject.com/en/6.0/topics/auth/customizing/#substituting-a-custom-user-model
More Django recipes
- Add a required column without downtime
- Add full-text search with PostgreSQL
- Add health checks and structured logging
- Avoid N+1 queries with select_related and prefetch_related
- Cache expensive queries and invalidate on change
- Call external APIs concurrently in async views
- Configure settings from environment variables
- Enforce rules with database constraints