Skip to main content

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:

  1. One-to-One relationship with a Profile model (we'll focus on this)
  2. Using a custom User model by extending AbstractUser
  3. 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:

python
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:

python
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:

python
@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:

bash
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:

python
# 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:

python
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:

python
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:

html
{% 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:

python
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:

python
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

Then update your project's urls.py to serve media files during development:

python
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:

  1. User registration
  2. Email verification
  3. Profile creation and customization

Step 1: Setup the models

Let's enhance our Profile model with more fields:

python
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

python
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

python
@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:

html
{% 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:

python
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:

python
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:

  1. You added the Profile model to an existing project with users
  2. The signal somehow failed to create a profile

Solution: Create a management command to ensure all users have profiles:

python
# 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:

bash
python manage.py create_missing_profiles

Image Upload Issues

If you're having trouble with image uploads, make sure:

  1. You've installed Pillow: pip install Pillow
  2. Your form has enctype="multipart/form-data"
  3. Your view correctly passes request.FILES to the form

Summary

In this tutorial, we've learned how to:

  1. Create a Profile model that extends the Django User model with a one-to-one relationship
  2. Set up signals to automatically create profiles when users register
  3. Build forms and views for users to update their profile information
  4. Handle profile pictures and other media files
  5. Implement a complete user registration and profile system
  6. 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

Exercises

  1. Basic: Add a field to the Profile model to track the user's favorite programming language.

  2. 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").

  3. Advanced: Create a follow/following system where users can follow each other and see updates from people they follow.

  4. 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! :)