Introduction

Custom user managers handle the creation of user instances. They're essential when using AbstractBaseUser.

Key Concepts

BaseUserManager: Base class for user managers.

normalize_email(): Lowercases the domain part of email.

Real World Context

In a multi-tenant platform where users sign up with their work email, the custom user manager is where you enforce business rules like requiring a company domain, normalizing emails, and auto-assigning the user to the correct organization based on their email domain.

Deep Dive

Complete Manager Implementation

python
from django.contrib.auth.models import BaseUserManager

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        """Create and return a regular user."""
        if not email:
            raise ValueError('Email address is required')
        
        email = self.normalize_email(email)
        extra_fields.setdefault('is_active', True)
        
        user = self.model(email=email, **extra_fields)
        user.set_password(password)  # Hashes the password
        user.save(using=self._db)
        return user
    
    def create_superuser(self, email, password=None, **extra_fields):
        """Create and return a superuser."""
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        
        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')
        
        return self.create_user(email, password, **extra_fields)
    
    def get_by_natural_key(self, email):
        """Case-insensitive email lookup."""
        return self.get(email__iexact=email)

Using the Manager

python
class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    
    objects = CustomUserManager()  # Attach manager
    
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']  # For createsuperuser

Common Pitfalls

  1. Storing raw passwords instead of using set_password(): If you assign user.password = raw_password, the password is stored in plain text. Always use set_password(), which applies the configured hasher.
  2. Forgetting normalize_email(): Email addresses are case-sensitive in the local part (before @) per RFC 5321, but Django's normalize_email() lowercases the domain part to prevent duplicate accounts.
  3. Not overriding create_superuser(): The createsuperuser management command calls create_superuser(). If it falls back to create_user() without setting is_staff=True and is_superuser=True, your admin account will not have admin access.

Best Practices

  1. Always use set_password(): Never store plain text passwords.
  2. Use normalize_email(): Ensures consistent email format.
  3. Validate in manager: Check required fields.

Summary

Custom managers handle user creation. Use set_password() for security. Include validation for required fields.

✓ Completed