$ cat /posts/django-user-model-custom-user-and-user-profile.md

Django User Model: Custom User and User Profile

drwxr-xr-x2026-01-235 min0 views
Django User Model: Custom User and User Profile

Django's default User model provides fundamental authentication functionality but often requires customization for real-world applications needing additional user attributes, alternative authentication methods, or specialized user types. Understanding custom user models and profile extensions enables building flexible authentication systems tailored to specific application requirements. This comprehensive guide explores Django's User model architecture including the default User model's fields and methods, creating custom user models extending AbstractUser for simple customizations, implementing completely custom authentication with AbstractBaseUser for maximum flexibility, adding user profiles through OneToOne relationships for supplementary information, managing multiple user types with model inheritance and type discrimination, integrating custom managers for specialized querysets, handling migrations when customizing users, and best practices for user model design ensuring scalability and maintainability. Mastering user model customization early in project development prevents complex migrations later while providing the flexibility needed for authentication requirements across diverse applications from simple blogs to complex multi-tenant SaaS platforms.

Understanding Default User Model

Django's default User model located in django.contrib.auth.models provides fields including username, password, email, first_name, last_name, is_staff, is_active, is_superuser, last_login, and date_joined. It handles authentication, permissions, and groups through built-in methods and relationships. While suitable for simple applications, it has limitations including mandatory username field, non-unique email, and fixed field set requiring customization for most production applications needing additional user attributes or alternative authentication schemes.

pythondefault_user_model.py
# Default User model fields and methods
from django.contrib.auth.models import User

# Creating users
user = User.objects.create_user(
    username='john',
    email='[email protected]',
    password='secure_password',
    first_name='John',
    last_name='Doe'
)

# Accessing user fields
print(user.username)  # 'john'
print(user.email)  # '[email protected]'
print(user.get_full_name())  # 'John Doe'
print(user.is_authenticated)  # True
print(user.is_staff)  # False

# Changing password
user.set_password('new_password')
user.save()

# Checking password
if user.check_password('new_password'):
    print('Password correct')

# User permissions
user.user_permissions.add(permission)
user.groups.add(group)

if user.has_perm('app.change_model'):
    print('User has permission')

# Limitations of default User model:
# 1. Username required (can't use email-only authentication)
# 2. Email not unique by default
# 3. Can't add custom fields without profile
# 4. Fixed authentication method
Always create a custom user model at the start of new projects even if you don't need customization immediately. Changing from default User to custom User after creating migrations is extremely difficult and error-prone.

Custom User with AbstractUser

AbstractUser provides the easiest customization path by extending Django's default User model with additional fields while retaining all built-in authentication functionality. This approach suits applications needing extra user attributes like phone numbers, birthdays, or addresses while keeping standard username and email authentication. AbstractUser includes all default User fields and methods allowing simple field additions without reimplementing authentication logic.

pythonabstract_user_custom.py
# models.py - Custom User extending AbstractUser
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    # Add custom 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)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    website = models.URLField(blank=True)
    location = models.CharField(max_length=100, blank=True)
    
    # Override fields if needed
    email = models.EmailField(unique=True)  # Make email unique
    
    def get_age(self):
        if self.date_of_birth:
            from datetime import date
            today = date.today()
            return today.year - self.date_of_birth.year
        return None
    
    def __str__(self):
        return self.username

# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'

# admin.py - Register custom user
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    model = CustomUser
    list_display = ['username', 'email', 'phone_number', 'is_staff']
    
    # Add custom fields to admin
    fieldsets = UserAdmin.fieldsets + (
        ('Additional Info', {
            'fields': ('phone_number', 'date_of_birth', 'bio', 'avatar', 'website', 'location')
        }),
    )
    
    add_fieldsets = UserAdmin.add_fieldsets + (
        ('Additional Info', {
            'fields': ('phone_number', 'date_of_birth', 'email')
        }),
    )

admin.site.register(CustomUser, CustomUserAdmin)

# forms.py - Custom registration form
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ('username', 'email', 'phone_number', 'password1', 'password2')
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['email'].required = True

# Usage in views
from .forms import CustomUserCreationForm
from django.views.generic import CreateView

class RegisterView(CreateView):
    form_class = CustomUserCreationForm
    template_name = 'register.html'
    success_url = '/dashboard/'

Custom User with AbstractBaseUser

AbstractBaseUser provides maximum flexibility for completely custom authentication systems requiring non-standard fields, alternative authentication methods like email or phone-only login, or specialized user types. This approach requires implementing custom managers, defining USERNAME_FIELD and REQUIRED_FIELDS, and manually adding permission support. Use AbstractBaseUser when you need full control over user model structure and authentication logic beyond what AbstractUser provides.

pythonabstract_base_user_custom.py
# models.py - Email-based authentication with AbstractBaseUser
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Email is required')
        
        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):
        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 CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    full_name = models.CharField(max_length=255)
    phone_number = models.CharField(max_length=15, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(auto_now_add=True)
    
    objects = CustomUserManager()
    
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['full_name']
    
    def __str__(self):
        return self.email
    
    def get_full_name(self):
        return self.full_name
    
    def get_short_name(self):
        return self.email

# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'

# admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import CustomUser

class CustomUserAdmin(BaseUserAdmin):
    list_display = ('email', 'full_name', 'is_staff', 'is_active')
    list_filter = ('is_staff', 'is_active')
    
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal Info', {'fields': ('full_name', 'phone_number')}),
        ('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', 'full_name', 'phone_number', 'password1', 'password2', 'is_staff', 'is_active')
        }),
    )
    
    search_fields = ('email', 'full_name')
    ordering = ('email',)
    filter_horizontal = ('groups', 'user_permissions')

admin.site.register(CustomUser, CustomUserAdmin)

User Profile with OneToOne Relationship

When using Django's default User model or when you can't modify the user model directly, create separate Profile models connected via OneToOne relationships. This approach keeps authentication data separate from additional profile information, supports multiple profile types for different user roles, and allows incremental profile data collection. Profiles are created automatically using signals when users register, accessed through user.profile relationships, and extended easily without modifying core user models.

pythonuser_profile_pattern.py
# models.py - User Profile with OneToOne
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    bio = models.TextField(max_length=500, blank=True)
    avatar = models.ImageField(upload_to='profiles/', blank=True)
    phone_number = models.CharField(max_length=15, blank=True)
    date_of_birth = models.DateField(null=True, blank=True)
    website = models.URLField(blank=True)
    location = models.CharField(max_length=100, blank=True)
    twitter = models.CharField(max_length=50, blank=True)
    github = models.CharField(max_length=50, blank=True)
    
    def __str__(self):
        return f'{self.user.username} Profile'

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

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

# Accessing profile
user = User.objects.get(username='john')
profile = user.profile
print(profile.bio)

# Updating profile
user.profile.bio = 'New bio text'
user.profile.save()

# views.py - Profile update view
from django.views.generic import UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin

class ProfileUpdateView(LoginRequiredMixin, UpdateView):
    model = UserProfile
    fields = ['bio', 'avatar', 'phone_number', 'website', 'location']
    template_name = 'accounts/profile_form.html'
    success_url = '/profile/'
    
    def get_object(self, queryset=None):
        return self.request.user.profile

# forms.py - Combined user and profile form
from django import forms
from django.contrib.auth.models import User
from .models import UserProfile

class UserUpdateForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['username', 'email', 'first_name', 'last_name']

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ['bio', 'avatar', 'phone_number', 'website', 'location']

# Combined update view
class CombinedProfileUpdateView(LoginRequiredMixin, TemplateView):
    template_name = 'accounts/profile_edit.html'
    
    def get(self, request):
        user_form = UserUpdateForm(instance=request.user)
        profile_form = ProfileUpdateForm(instance=request.user.profile)
        return render(request, self.template_name, {
            'user_form': user_form,
            'profile_form': profile_form
        })
    
    def post(self, request):
        user_form = UserUpdateForm(request.POST, instance=request.user)
        profile_form = ProfileUpdateForm(
            request.POST,
            request.FILES,
            instance=request.user.profile
        )
        
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Profile updated successfully!')
            return redirect('profile')
        
        return render(request, self.template_name, {
            'user_form': user_form,
            'profile_form': profile_form
        })

User Model Approaches Comparison

ApproachUse WhenProsCons
Default UserSimple projectsQuick setup, well documentedLimited customization
AbstractUserNeed extra fieldsEasy customization, keeps authStill uses username
AbstractBaseUserFull control neededComplete flexibilityMore complex setup
Profile ModelCan't modify UserSeparate concerns, flexibleExtra queries, complexity

Best Practices

  1. Start with custom user: Always create custom user model at project start even if not customizing immediately
  2. Use AbstractUser for simple cases: Extend AbstractUser when you need additional fields but keep standard authentication
  3. AbstractBaseUser for full control: Use when you need email-only or phone-only authentication or completely custom fields
  4. Signals for profile creation: Use post_save signals to automatically create profiles ensuring every user has one
  5. Related_name for clarity: Always set related_name on OneToOne fields for clear reverse relationships
  6. Test migrations thoroughly: Test custom user migrations extensively before production deployment

Conclusion

Customizing Django's User model is essential for most production applications requiring additional user attributes, alternative authentication methods, or specialized user types. AbstractUser provides the easiest customization path for applications needing extra fields while maintaining standard authentication. AbstractBaseUser offers complete flexibility for custom authentication systems using email, phone numbers, or other identifiers as primary credentials. User Profile models connected via OneToOne relationships provide flexible profile extensions when modifying the user model directly isn't feasible. The key decision is choosing the right approach based on project requirements: default User for simple projects, AbstractUser for additional fields, AbstractBaseUser for complete control, or Profile models for separation of concerns. Always implement custom user models at project start preventing complex migrations later. Understanding user model customization patterns enables building scalable authentication systems tailored to specific application needs supporting diverse use cases from basic blogs to complex multi-tenant platforms throughout Django 6.0 development.

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.