Django User Profile
Introduction
While Django's built-in authentication system provides a solid User model with essential fields like username, email, and password, most real-world applications need to store additional user information. This is where user profiles come in.
A user profile allows you to extend the default User model to include custom information such as:
- Profile pictures
- Biographical information
- Social media links
- Preferences
- Contact details
- Any other user-specific data
In this tutorial, we'll learn how to implement user profiles in Django applications, creating a one-to-one relationship between Django's User model and our custom Profile model.
Approaches to Extending the User Model
There are several ways to extend the Django User model:
- One-to-One relationship with a Profile model (we'll focus on this)
- Using a custom User model by extending AbstractUser
- Creating a completely custom User model by extending AbstractBaseUser
We'll focus on the profile model approach because it's:
- Beginner-friendly
- Non-intrusive to Django's default authentication
- Flexible for adding fields later
- Easy to implement in existing projects
Creating a User Profile Model
Let's create a basic user profile model that extends the built-in User model:
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 Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
profile_picture = models.ImageField(upload_to='profile_pics', default='profile_pics/default.jpg', blank=True)
def __str__(self):
return f'{self.user.username} Profile'
In this example:
- We create a
Profile
model with a one-to-one relationship to Django's User model on_delete=models.CASCADE
ensures that if a User is deleted, their Profile is also deleted- We've added some common fields like bio, location, birth date, and a profile picture
- The
__str__
method returns a string representation of the profile
Adding the Profile App to Settings
Make sure to add your profile app to the INSTALLED_APPS
in your settings.py:
INSTALLED_APPS = [
# ...
'django.contrib.auth',
# ...
'profiles', # your app name here
]
Creating a Profile Automatically with Signals
To ensure a profile is created whenever a new user registers, we can use Django signals:
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
These signal handlers automatically create and save a profile whenever a User instance is created or saved.
Add these functions to the same file as your Profile model. Don't forget to make and run migrations:
python manage.py makemigrations
python manage.py migrate
Accessing and Updating Profile Information
Accessing the Profile
Once you've set up the one-to-one relationship and signals, you can access a user's profile like this:
# In a view or anywhere you have access to a user object
user = User.objects.get(username='johndoe')
profile = user.profile
# Access profile attributes
bio = profile.bio
location = profile.location
Creating Profile Forms
To allow users to update their profile, let's create forms:
from django import forms
from django.contrib.auth.models import User
from .models import Profile
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email']
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['bio', 'location', 'birth_date', 'profile_picture']
Building Profile Update View
Now let's create a view to handle profile updates:
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserUpdateForm, ProfileUpdateForm
@login_required
def profile(request):
if request.method == 'POST':
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, 'Your account has been updated!')
return redirect('profile')
else:
user_form = UserUpdateForm(instance=request.user)
profile_form = ProfileUpdateForm(instance=request.user.profile)
context = {
'user_form': user_form,
'profile_form': profile_form
}
return render(request, 'profiles/profile.html', context)
Creating the Profile Template
Create a template to display and edit the profile:
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ user.profile.profile_picture.url }}">
<div class="media-body">
<h2 class="account-heading">{{ user.username }}</h2>
<p class="text-secondary">{{ user.email }}</p>
</div>
</div>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Profile Info</legend>
{{ user_form|crispy }}
{{ profile_form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
{% endblock content %}
URL Configuration
Don't forget to configure your URLs:
from django.urls import path
from . import views
urlpatterns = [
path('profile/', views.profile, name='profile'),
# other URLs...
]
Handling Media Files
For profile pictures to work properly, configure your media settings in settings.py:
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
Then update your project's urls.py to serve media files during development:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# Your URL patterns
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Real-World Example: A Complete User Registration and Profile System
Let's put everything together in a practical example. We'll create a complete user registration and profile system that includes:
- User registration
- Email verification
- Profile creation and customization
Step 1: Setup the models
Let's enhance our Profile model with more fields:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
profile_picture = models.ImageField(upload_to='profile_pics', default='profile_pics/default.jpg')
website = models.URLField(max_length=100, blank=True)
github_profile = models.URLField(max_length=100, blank=True)
twitter_handle = models.CharField(max_length=20, blank=True)
is_verified = models.BooleanField(default=False)
def __str__(self):
return f'{self.user.username} Profile'
Step 2: Create a registration view
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login
from django.shortcuts import render, redirect
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, 'Account created successfully!')
return redirect('profile')
else:
form = UserCreationForm()
return render(request, 'profiles/register.html', {'form': form})
Step 3: Profile detail view
@login_required
def profile_detail(request, username):
user = get_object_or_404(User, username=username)
profile = user.profile
context = {
'profile_user': user,
'profile': profile,
'is_own_profile': user == request.user
}
return render(request, 'profiles/profile_detail.html', context)
Step 4: Creating proper templates
Here's a more detailed profile template with Bootstrap styling:
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-4">
<div class="card">
<img src="{{ profile.profile_picture.url }}" class="card-img-top" alt="{{ profile_user.username }}">
<div class="card-body">
<h5 class="card-title">{{ profile_user.username }}</h5>
<p class="card-text">{{ profile.bio }}</p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Location: {{ profile.location }}</li>
{% if profile.website %}
<li class="list-group-item">Website: <a href="{{ profile.website }}">{{ profile.website }}</a></li>
{% endif %}
{% if profile.github_profile %}
<li class="list-group-item">GitHub: <a href="{{ profile.github_profile }}">GitHub Profile</a></li>
{% endif %}
</ul>
{% if is_own_profile %}
<div class="card-body">
<a href="{% url 'profile_update' %}" class="btn btn-primary">Edit Profile</a>
</div>
{% endif %}
</div>
</div>
<div class="col-md-8">
<!-- You could add user's posts, activities, etc. here -->
<h3>Recent Activity</h3>
<div class="alert alert-info">No recent activity</div>
</div>
</div>
</div>
{% endblock %}
Additional Profile Features
Custom User Manager for a Profile-enabled User
If you're creating profiles frequently, you might want a custom manager:
from django.contrib.auth.models import User, UserManager
class ProfileUserManager(UserManager):
def create_user_with_profile(self, username, email=None, password=None, **extra_fields):
user = self.create_user(username, email, password)
profile_fields = {}
# Extract profile fields from extra_fields
for field in ['bio', 'location', 'birth_date', 'website']:
if field in extra_fields:
profile_fields[field] = extra_fields.pop(field)
# Update the profile
for key, value in profile_fields.items():
setattr(user.profile, key, value)
user.profile.save()
return user
Profile Privacy Settings
You might want to add privacy settings to control what information is visible:
class ProfilePrivacySettings(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE, related_name='privacy_settings')
show_email = models.BooleanField(default=False)
show_bio = models.BooleanField(default=True)
show_location = models.BooleanField(default=True)
show_birth_date = models.BooleanField(default=False)
def __str__(self):
return f'Privacy settings for {self.profile.user.username}'
Common Issues and Solutions
Profile Doesn't Exist
Sometimes you might encounter errors where a profile doesn't exist for a user. This can happen if:
- You added the Profile model to an existing project with users
- The signal somehow failed to create a profile
Solution: Create a management command to ensure all users have profiles:
# profiles/management/commands/create_missing_profiles.py
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from profiles.models import Profile
class Command(BaseCommand):
help = 'Creates missing user profiles'
def handle(self, *args, **kwargs):
users_without_profiles = []
for user in User.objects.all():
try:
# Just access the profile to see if it exists
user.profile
except Profile.DoesNotExist:
users_without_profiles.append(user)
Profile.objects.create(user=user)
self.stdout.write(f'Created {len(users_without_profiles)} missing profiles')
Run it with:
python manage.py create_missing_profiles
Image Upload Issues
If you're having trouble with image uploads, make sure:
- You've installed Pillow:
pip install Pillow
- Your form has
enctype="multipart/form-data"
- Your view correctly passes
request.FILES
to the form
Summary
In this tutorial, we've learned how to:
- Create a Profile model that extends the Django User model with a one-to-one relationship
- Set up signals to automatically create profiles when users register
- Build forms and views for users to update their profile information
- Handle profile pictures and other media files
- Implement a complete user registration and profile system
- Handle common issues with user profiles
User profiles are essential for most web applications, providing a way to store additional user information beyond the basic authentication data. By using the one-to-one relationship approach, we can maintain compatibility with Django's authentication system while adding any custom fields we need.
Additional Resources and Exercises
Resources
- Official Django documentation on extending the User model
- Django Authentication System documentation
- Django Signals documentation
Exercises
-
Basic: Add a field to the Profile model to track the user's favorite programming language.
-
Intermediate: Implement a system that shows a badge on a user's profile based on certain achievements (e.g., "Joined 1 year ago", "Active contributor").
-
Advanced: Create a follow/following system where users can follow each other and see updates from people they follow.
-
Challenge: Build a reputation system where users earn points based on their activity, and display a different profile badge based on their reputation level.
By completing these exercises, you'll gain a deeper understanding of how to leverage Django's user profiles to create rich, interactive web applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)