Authentication and Authorization

Custom User Models

Django's default User model works well for many applications, but often you'll need to customize user authentication to fit your specific requirements. Understanding how to create and implement custom user models enables you to build authentication systems tailored to your application's unique needs.

Custom User Models

Django's default User model works well for many applications, but often you'll need to customize user authentication to fit your specific requirements. Understanding how to create and implement custom user models enables you to build authentication systems tailored to your application's unique needs.

Understanding Custom User Models

When to Use Custom User Models

# Reasons to create a custom user model
class CustomUserModelDecision:
    """Guide for deciding when to use custom user models"""
    
    @staticmethod
    def scenarios_requiring_custom_user():
        """Scenarios that require a custom user model"""
        
        scenarios = {
            'email_as_username': {
                'description': 'Use email address instead of username for login',
                'example': 'Modern web applications often prefer email login',
                'complexity': 'Medium'
            },
            'additional_required_fields': {
                'description': 'Add required fields to user registration',
                'example': 'Phone number, date of birth, organization',
                'complexity': 'Low'
            },
            'remove_unused_fields': {
                'description': 'Remove fields like username if not needed',
                'example': 'Email-only authentication systems',
                'complexity': 'Medium'
            },
            'different_authentication': {
                'description': 'Use different authentication methods',
                'example': 'Social security number, employee ID',
                'complexity': 'High'
            },
            'custom_permissions': {
                'description': 'Implement custom permission logic',
                'example': 'Role-based permissions, hierarchical access',
                'complexity': 'High'
            }
        }
        
        return scenarios
    
    @staticmethod
    def alternatives_to_custom_user():
        """Alternatives to creating a custom user model"""
        
        alternatives = {
            'user_profile': {
                'description': 'Create a separate profile model linked to User',
                'use_case': 'Adding optional fields without changing authentication',
                'pros': ['Simpler', 'No migration complexity', 'Backward compatible'],
                'cons': ['Extra database join', 'Cannot change required fields']
            },
            'proxy_model': {
                'description': 'Create a proxy model for custom methods',
                'use_case': 'Adding methods without changing database structure',
                'pros': ['No database changes', 'Easy to implement'],
                'cons': ['Cannot add fields', 'Limited customization']
            },
            'extend_user': {
                'description': 'Extend User with additional models',
                'use_case': 'Complex user data that doesn\'t fit in one model',
                'pros': ['Flexible', 'Maintains User model compatibility'],
                'cons': ['More complex queries', 'Multiple models to manage']
            }
        }
        
        return alternatives

# Example: User profile approach (alternative to custom user)
class UserProfile(models.Model):
    """User profile model as alternative to custom user"""
    
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    
    # Additional fields
    phone_number = models.CharField(max_length=15, blank=True)
    date_of_birth = models.DateField(null=True, blank=True)
    bio = models.TextField(max_length=500, blank=True)
    location = models.CharField(max_length=30, blank=True)
    website = models.URLField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', blank=True)
    
    # Preferences
    email_notifications = models.BooleanField(default=True)
    theme_preference = models.CharField(
        max_length=10,
        choices=[('light', 'Light'), ('dark', 'Dark')],
        default='light'
    )
    language = models.CharField(max_length=10, default='en')
    timezone = models.CharField(max_length=50, default='UTC')
    
    # Metadata
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return f"{self.user.username}'s Profile"
    
    def get_full_name(self):
        """Get user's full name"""
        return self.user.get_full_name()
    
    def get_age(self):
        """Calculate user's age"""
        if self.date_of_birth:
            today = timezone.now().date()
            return today.year - self.date_of_birth.year - (
                (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
            )
        return None

# Signal to create profile automatically
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    """Create user profile when user is created"""
    if created:
        UserProfile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    """Save user profile when user is saved"""
    if hasattr(instance, 'profile'):
        instance.profile.save()

Creating Custom User Models

Email-Based User Model

# Custom user model using email as username
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _

class EmailUserManager(BaseUserManager):
    """Manager for email-based user model"""
    
    def create_user(self, email, password=None, **extra_fields):
        """Create and return a regular user with email and password"""
        
        if not email:
            raise ValueError(_('The Email field must be set'))
        
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user
    
    def create_superuser(self, email, password=None, **extra_fields):
        """Create and return a superuser with email and password"""
        
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', 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)

class EmailUser(AbstractBaseUser, PermissionsMixin):
    """Custom user model that uses email instead of username"""
    
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=150, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    
    objects = EmailUserManager()
    
    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']
    
    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
    
    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)
    
    def get_full_name(self):
        """Return the first_name plus the last_name, with a space in between"""
        full_name = f'{self.first_name} {self.last_name}'
        return full_name.strip()
    
    def get_short_name(self):
        """Return the short name for the user"""
        return self.first_name
    
    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user"""
        send_mail(subject, message, from_email, [self.email], **kwargs)

# Settings configuration for custom user model
# In settings.py:
# AUTH_USER_MODEL = 'myapp.EmailUser'

Extended User Model with Additional Fields

# Extended user model with comprehensive fields
class ExtendedUser(AbstractUser):
    """Extended user model with additional fields"""
    
    # Contact information
    phone_number = models.CharField(
        _('phone number'), 
        max_length=15, 
        blank=True,
        help_text=_('Phone number in international format')
    )
    
    # Personal information
    date_of_birth = models.DateField(_('date of birth'), null=True, blank=True)
    gender = models.CharField(
        _('gender'),
        max_length=10,
        choices=[
            ('M', _('Male')),
            ('F', _('Female')),
            ('O', _('Other')),
            ('N', _('Prefer not to say')),
        ],
        blank=True
    )
    
    # Address information
    address_line_1 = models.CharField(_('address line 1'), max_length=255, blank=True)
    address_line_2 = models.CharField(_('address line 2'), max_length=255, blank=True)
    city = models.CharField(_('city'), max_length=100, blank=True)
    state = models.CharField(_('state/province'), max_length=100, blank=True)
    postal_code = models.CharField(_('postal code'), max_length=20, blank=True)
    country = models.CharField(_('country'), max_length=100, blank=True)
    
    # Professional information
    job_title = models.CharField(_('job title'), max_length=100, blank=True)
    company = models.CharField(_('company'), max_length=100, blank=True)
    department = models.CharField(_('department'), max_length=100, blank=True)
    
    # Profile information
    bio = models.TextField(_('biography'), max_length=500, blank=True)
    website = models.URLField(_('website'), blank=True)
    avatar = models.ImageField(_('avatar'), upload_to='avatars/', blank=True)
    
    # Preferences
    language = models.CharField(
        _('language'),
        max_length=10,
        choices=[
            ('en', _('English')),
            ('es', _('Spanish')),
            ('fr', _('French')),
            ('de', _('German')),
        ],
        default='en'
    )
    
    timezone = models.CharField(
        _('timezone'),
        max_length=50,
        default='UTC',
        help_text=_('User\'s preferred timezone')
    )
    
    theme_preference = models.CharField(
        _('theme preference'),
        max_length=10,
        choices=[
            ('light', _('Light')),
            ('dark', _('Dark')),
            ('auto', _('Auto')),
        ],
        default='light'
    )
    
    # Notification preferences
    email_notifications = models.BooleanField(_('email notifications'), default=True)
    sms_notifications = models.BooleanField(_('SMS notifications'), default=False)
    push_notifications = models.BooleanField(_('push notifications'), default=True)
    marketing_emails = models.BooleanField(_('marketing emails'), default=False)
    
    # Security settings
    two_factor_enabled = models.BooleanField(_('two-factor authentication'), default=False)
    totp_secret = models.CharField(_('TOTP secret'), max_length=32, blank=True)
    backup_codes = models.JSONField(_('backup codes'), default=list, blank=True)
    
    # Account status
    email_verified = models.BooleanField(_('email verified'), default=False)
    phone_verified = models.BooleanField(_('phone verified'), default=False)
    account_locked = models.BooleanField(_('account locked'), default=False)
    lock_reason = models.CharField(_('lock reason'), max_length=255, blank=True)
    
    # Timestamps
    last_password_change = models.DateTimeField(_('last password change'), null=True, blank=True)
    last_profile_update = models.DateTimeField(_('last profile update'), auto_now=True)
    
    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
    
    def get_full_address(self):
        """Get formatted full address"""
        address_parts = [
            self.address_line_1,
            self.address_line_2,
            self.city,
            self.state,
            self.postal_code,
            self.country
        ]
        return ', '.join(filter(None, address_parts))
    
    def get_age(self):
        """Calculate user's age"""
        if self.date_of_birth:
            today = timezone.now().date()
            return today.year - self.date_of_birth.year - (
                (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
            )
        return None
    
    def get_display_name(self):
        """Get display name for user"""
        full_name = self.get_full_name()
        return full_name if full_name.strip() else self.username
    
    def has_complete_profile(self):
        """Check if user has completed their profile"""
        required_fields = [
            'first_name', 'last_name', 'email', 'phone_number'
        ]
        
        for field in required_fields:
            if not getattr(self, field):
                return False
        
        return True
    
    def get_avatar_url(self):
        """Get avatar URL or default"""
        if self.avatar:
            return self.avatar.url
        return f"https://www.gravatar.com/avatar/{hashlib.md5(self.email.lower().encode()).hexdigest()}?d=identicon&s=150"
    
    def enable_two_factor(self):
        """Enable two-factor authentication"""
        if not self.totp_secret:
            import pyotp
            self.totp_secret = pyotp.random_base32()
        
        self.two_factor_enabled = True
        self.save(update_fields=['totp_secret', 'two_factor_enabled'])
    
    def disable_two_factor(self):
        """Disable two-factor authentication"""
        self.two_factor_enabled = False
        self.totp_secret = ''
        self.backup_codes = []
        self.save(update_fields=['two_factor_enabled', 'totp_secret', 'backup_codes'])
    
    def generate_backup_codes(self, count=10):
        """Generate backup codes for 2FA"""
        import secrets
        
        codes = []
        for _ in range(count):
            code = '-'.join([
                secrets.token_hex(2).upper() 
                for _ in range(3)
            ])
            codes.append(code)
        
        self.backup_codes = codes
        self.save(update_fields=['backup_codes'])
        
        return codes

Role-Based User Model

# Role-based user model with hierarchical permissions
class Role(models.Model):
    """Role model for role-based access control"""
    
    name = models.CharField(_('name'), max_length=50, unique=True)
    description = models.TextField(_('description'), blank=True)
    permissions = models.ManyToManyField(
        'auth.Permission',
        verbose_name=_('permissions'),
        blank=True,
    )
    
    # Hierarchy support
    parent_role = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='child_roles',
        verbose_name=_('parent role')
    )
    
    # Role properties
    is_system_role = models.BooleanField(_('system role'), default=False)
    is_active = models.BooleanField(_('active'), default=True)
    
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    
    class Meta:
        verbose_name = _('role')
        verbose_name_plural = _('roles')
        ordering = ['name']
    
    def __str__(self):
        return self.name
    
    def get_all_permissions(self):
        """Get all permissions including inherited from parent roles"""
        permissions = set(self.permissions.all())
        
        # Add permissions from parent roles
        current_role = self.parent_role
        while current_role:
            permissions.update(current_role.permissions.all())
            current_role = current_role.parent_role
        
        return permissions
    
    def has_permission(self, permission):
        """Check if role has specific permission"""
        return permission in self.get_all_permissions()

class RoleBasedUser(AbstractUser):
    """User model with role-based access control"""
    
    # Replace groups with roles
    roles = models.ManyToManyField(
        Role,
        verbose_name=_('roles'),
        blank=True,
        help_text=_('The roles this user belongs to.'),
        related_name='users'
    )
    
    # Additional fields
    employee_id = models.CharField(_('employee ID'), max_length=20, unique=True, null=True, blank=True)
    department = models.CharField(_('department'), max_length=100, blank=True)
    manager = models.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='subordinates',
        verbose_name=_('manager')
    )
    
    # Account status
    is_temporary = models.BooleanField(_('temporary account'), default=False)
    expires_at = models.DateTimeField(_('expires at'), null=True, blank=True)
    
    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
    
    def get_all_permissions(self, obj=None):
        """Get all permissions from roles"""
        permissions = set()
        
        for role in self.roles.filter(is_active=True):
            permissions.update(
                perm.content_type.app_label + '.' + perm.codename
                for perm in role.get_all_permissions()
            )
        
        # Add user-specific permissions
        permissions.update(super().get_all_permissions(obj))
        
        return permissions
    
    def has_perm(self, perm, obj=None):
        """Check if user has permission"""
        if self.is_active and self.is_superuser:
            return True
        
        return perm in self.get_all_permissions(obj)
    
    def has_role(self, role_name):
        """Check if user has specific role"""
        return self.roles.filter(name=role_name, is_active=True).exists()
    
    def add_role(self, role_name):
        """Add role to user"""
        try:
            role = Role.objects.get(name=role_name, is_active=True)
            self.roles.add(role)
            return True
        except Role.DoesNotExist:
            return False
    
    def remove_role(self, role_name):
        """Remove role from user"""
        try:
            role = Role.objects.get(name=role_name)
            self.roles.remove(role)
            return True
        except Role.DoesNotExist:
            return False
    
    def get_subordinates(self):
        """Get all subordinates in hierarchy"""
        subordinates = []
        
        def collect_subordinates(manager):
            direct_subordinates = manager.subordinates.all()
            subordinates.extend(direct_subordinates)
            
            for subordinate in direct_subordinates:
                collect_subordinates(subordinate)
        
        collect_subordinates(self)
        return subordinates
    
    def is_manager_of(self, user):
        """Check if this user is manager of another user"""
        return user in self.get_subordinates()
    
    def clean(self):
        """Validate user data"""
        super().clean()
        
        # Check account expiry
        if self.is_temporary and not self.expires_at:
            raise ValidationError(_('Temporary accounts must have an expiry date.'))
        
        # Prevent circular manager relationships
        if self.manager:
            current = self.manager
            while current:
                if current == self:
                    raise ValidationError(_('Circular manager relationship detected.'))
                current = current.manager

Custom User Forms and Admin

Forms for Custom User Models

# Forms for custom user models
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

class EmailUserCreationForm(UserCreationForm):
    """Form for creating EmailUser instances"""
    
    class Meta:
        model = EmailUser
        fields = ('email', 'first_name', 'last_name')
    
    def clean_email(self):
        """Validate email"""
        email = self.cleaned_data['email']
        
        if EmailUser.objects.filter(email=email).exists():
            raise forms.ValidationError(_('A user with this email already exists.'))
        
        return email

class EmailUserChangeForm(UserChangeForm):
    """Form for changing EmailUser instances"""
    
    class Meta:
        model = EmailUser
        fields = ('email', 'first_name', 'last_name', 'is_active', 'is_staff')

class ExtendedUserCreationForm(UserCreationForm):
    """Form for creating ExtendedUser instances"""
    
    email = forms.EmailField(required=True)
    first_name = forms.CharField(max_length=150, required=True)
    last_name = forms.CharField(max_length=150, required=True)
    phone_number = forms.CharField(max_length=15, required=False)
    
    class Meta:
        model = ExtendedUser
        fields = (
            'username', 'email', 'first_name', 'last_name', 
            'phone_number', 'password1', 'password2'
        )
    
    def clean_email(self):
        """Validate email uniqueness"""
        email = self.cleaned_data['email']
        
        if ExtendedUser.objects.filter(email=email).exists():
            raise forms.ValidationError(_('A user with this email already exists.'))
        
        return email
    
    def save(self, commit=True):
        """Save user with additional fields"""
        user = super().save(commit=False)
        user.email = self.cleaned_data['email']
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.phone_number = self.cleaned_data['phone_number']
        
        if commit:
            user.save()
        
        return user

class RoleBasedUserCreationForm(UserCreationForm):
    """Form for creating RoleBasedUser instances"""
    
    roles = forms.ModelMultipleChoiceField(
        queryset=Role.objects.filter(is_active=True),
        widget=forms.CheckboxSelectMultiple,
        required=False
    )
    
    class Meta:
        model = RoleBasedUser
        fields = (
            'username', 'email', 'first_name', 'last_name',
            'employee_id', 'department', 'manager', 'roles'
        )
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Exclude system roles from selection
        self.fields['roles'].queryset = Role.objects.filter(
            is_active=True, 
            is_system_role=False
        )
    
    def save(self, commit=True):
        """Save user with roles"""
        user = super().save(commit)
        
        if commit:
            user.roles.set(self.cleaned_data['roles'])
        
        return user

# Profile update forms
class UserProfileUpdateForm(forms.ModelForm):
    """Form for updating user profile information"""
    
    class Meta:
        model = ExtendedUser
        fields = [
            'first_name', 'last_name', 'email', 'phone_number',
            'date_of_birth', 'gender', 'bio', 'website',
            'address_line_1', 'address_line_2', 'city', 'state',
            'postal_code', 'country', 'job_title', 'company',
            'department', 'language', 'timezone', 'theme_preference'
        ]
        
        widgets = {
            'date_of_birth': forms.DateInput(attrs={'type': 'date'}),
            'bio': forms.Textarea(attrs={'rows': 4}),
            'gender': forms.Select(),
            'language': forms.Select(),
            'theme_preference': forms.Select(),
        }
    
    def clean_email(self):
        """Validate email uniqueness"""
        email = self.cleaned_data['email']
        
        # Exclude current user from uniqueness check
        if (ExtendedUser.objects.filter(email=email)
            .exclude(pk=self.instance.pk).exists()):
            raise forms.ValidationError(_('A user with this email already exists.'))
        
        return email

class NotificationPreferencesForm(forms.ModelForm):
    """Form for updating notification preferences"""
    
    class Meta:
        model = ExtendedUser
        fields = [
            'email_notifications', 'sms_notifications',
            'push_notifications', 'marketing_emails'
        ]
        
        widgets = {
            'email_notifications': forms.CheckboxInput(),
            'sms_notifications': forms.CheckboxInput(),
            'push_notifications': forms.CheckboxInput(),
            'marketing_emails': forms.CheckboxInput(),
        }

Admin Configuration for Custom User Models

# Admin configuration for custom user models
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext_lazy as _

@admin.register(EmailUser)
class EmailUserAdmin(BaseUserAdmin):
    """Admin for EmailUser model"""
    
    form = EmailUserChangeForm
    add_form = EmailUserCreationForm
    
    list_display = ('email', 'first_name', 'last_name', 'is_staff', 'date_joined')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'date_joined')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)
    
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {
            'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
        }),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'first_name', 'last_name', 'password1', 'password2'),
        }),
    )

@admin.register(ExtendedUser)
class ExtendedUserAdmin(BaseUserAdmin):
    """Admin for ExtendedUser model"""
    
    list_display = (
        'username', 'email', 'first_name', 'last_name',
        'is_staff', 'email_verified', 'date_joined'
    )
    
    list_filter = (
        'is_staff', 'is_superuser', 'is_active', 'email_verified',
        'two_factor_enabled', 'gender', 'language', 'date_joined'
    )
    
    search_fields = ('username', 'email', 'first_name', 'last_name', 'phone_number')
    
    fieldsets = BaseUserAdmin.fieldsets + (
        (_('Contact Information'), {
            'fields': ('phone_number', 'website')
        }),
        (_('Personal Information'), {
            'fields': ('date_of_birth', 'gender', 'bio', 'avatar')
        }),
        (_('Address'), {
            'fields': (
                'address_line_1', 'address_line_2', 'city',
                'state', 'postal_code', 'country'
            ),
            'classes': ('collapse',)
        }),
        (_('Professional Information'), {
            'fields': ('job_title', 'company', 'department'),
            'classes': ('collapse',)
        }),
        (_('Preferences'), {
            'fields': ('language', 'timezone', 'theme_preference'),
            'classes': ('collapse',)
        }),
        (_('Notifications'), {
            'fields': (
                'email_notifications', 'sms_notifications',
                'push_notifications', 'marketing_emails'
            ),
            'classes': ('collapse',)
        }),
        (_('Security'), {
            'fields': (
                'email_verified', 'phone_verified', 'two_factor_enabled',
                'account_locked', 'lock_reason'
            ),
            'classes': ('collapse',)
        }),
        (_('Timestamps'), {
            'fields': ('last_password_change', 'last_profile_update'),
            'classes': ('collapse',)
        }),
    )
    
    readonly_fields = ('last_profile_update', 'totp_secret', 'backup_codes')

@admin.register(Role)
class RoleAdmin(admin.ModelAdmin):
    """Admin for Role model"""
    
    list_display = ('name', 'parent_role', 'is_system_role', 'is_active', 'created_at')
    list_filter = ('is_system_role', 'is_active', 'created_at')
    search_fields = ('name', 'description')
    
    fieldsets = (
        (None, {'fields': ('name', 'description', 'parent_role')}),
        (_('Permissions'), {'fields': ('permissions',)}),
        (_('Settings'), {'fields': ('is_system_role', 'is_active')}),
    )
    
    filter_horizontal = ('permissions',)

@admin.register(RoleBasedUser)
class RoleBasedUserAdmin(BaseUserAdmin):
    """Admin for RoleBasedUser model"""
    
    list_display = (
        'username', 'email', 'first_name', 'last_name',
        'employee_id', 'department', 'is_staff', 'is_temporary'
    )
    
    list_filter = (
        'is_staff', 'is_superuser', 'is_active', 'is_temporary',
        'department', 'roles', 'date_joined'
    )
    
    search_fields = ('username', 'email', 'first_name', 'last_name', 'employee_id')
    
    fieldsets = BaseUserAdmin.fieldsets + (
        (_('Employee Information'), {
            'fields': ('employee_id', 'department', 'manager')
        }),
        (_('Roles'), {
            'fields': ('roles',)
        }),
        (_('Account Status'), {
            'fields': ('is_temporary', 'expires_at')
        }),
    )
    
    filter_horizontal = ('roles', 'user_permissions')
    
    def get_queryset(self, request):
        """Optimize queryset with select_related"""
        return super().get_queryset(request).select_related('manager')

Migration Strategies

Migrating to Custom User Models

# Migration strategies for custom user models
class CustomUserMigrationStrategy:
    """Strategies for migrating to custom user models"""
    
    @staticmethod
    def new_project_setup():
        """Setup custom user model for new projects"""
        
        steps = [
            "1. Create custom user model before first migration",
            "2. Set AUTH_USER_MODEL in settings.py",
            "3. Run makemigrations and migrate",
            "4. Create superuser with new model"
        ]
        
        settings_config = """
        # settings.py
        AUTH_USER_MODEL = 'myapp.CustomUser'
        """
        
        return {
            'steps': steps,
            'settings': settings_config,
            'difficulty': 'Easy'
        }
    
    @staticmethod
    def existing_project_migration():
        """Migrate existing project to custom user model"""
        
        # This is complex and often not recommended
        # Better to use UserProfile approach
        
        warnings = [
            "Migrating existing projects to custom user is very complex",
            "Consider using UserProfile model instead",
            "Backup database before attempting migration",
            "Test thoroughly in development environment"
        ]
        
        alternative_approach = """
        # Instead of migrating, use UserProfile:
        
        class UserProfile(models.Model):
            user = models.OneToOneField(User, on_delete=models.CASCADE)
            # Add your custom fields here
            
        # Then access via user.profile.custom_field
        """
        
        return {
            'warnings': warnings,
            'alternative': alternative_approach,
            'difficulty': 'Very Hard'
        }
    
    @staticmethod
    def data_migration_example():
        """Example data migration for custom user fields"""
        
        migration_code = """
        # migrations/0002_migrate_user_data.py
        from django.db import migrations
        
        def migrate_user_data(apps, schema_editor):
            OldUser = apps.get_model('auth', 'User')
            NewUser = apps.get_model('myapp', 'CustomUser')
            
            for old_user in OldUser.objects.all():
                NewUser.objects.create(
                    username=old_user.username,
                    email=old_user.email,
                    first_name=old_user.first_name,
                    last_name=old_user.last_name,
                    is_staff=old_user.is_staff,
                    is_active=old_user.is_active,
                    is_superuser=old_user.is_superuser,
                    date_joined=old_user.date_joined,
                    last_login=old_user.last_login,
                    password=old_user.password,
                )
        
        class Migration(migrations.Migration):
            dependencies = [
                ('myapp', '0001_initial'),
            ]
            
            operations = [
                migrations.RunPython(migrate_user_data),
            ]
        """
        
        return migration_code

Custom user models provide the flexibility to tailor authentication to your application's specific needs. Whether you need email-based authentication, additional required fields, or complex role-based access control, understanding how to implement and manage custom user models enables you to build authentication systems that perfectly fit your requirements while maintaining Django's security best practices.