Prerequisites
Before starting, ensure you have the following ready:
-
Basic Knowledge: A fundamental understanding of Python and Django project structure.
-
Environment: A Django project created and running.
-
Dependencies: We need the Pillow library to handle profile image uploads.
pip install Pillow
Phase 1: Setup & Custom User Model
Step 1: Create the App
Run this in your terminal:
python manage.py startapp users
Connect to Main Project
To make all these URLs work, we must connect the app to the main project. Open the urls.py folder in your main project folder (where settings.py is).
# todo_list/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('users.urls')), # Connects your new app
]
# This allows images to be served during development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Step 2: Create the Custom Model
Open users/models.py. We inherit from AbstractUser keeping Django's security features while adding your specific fields.
# users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
('O', 'Other'),
)
date_of_birth = models.DateField(null=True, blank=True)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True)
photo = models.ImageField(upload_to='profile_photos/', blank=True, null=True)
def __str__(self):
return self.username
Step 3: Register in Admin
Open users/admin.py. This ensures you can see these new fields in the Django Admin panel.
# users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
model = CustomUser
# Add the new fields to the admin interface
fieldsets = UserAdmin.fieldsets + (
('Personal Info', {'fields': ('date_of_birth', 'gender', 'photo')}),
)
admin.site.register(CustomUser, CustomUserAdmin)
Step 4: Configure Settings
Open your project's settings.py. Add these lines to tell Django to use your model and where to save images.
# settings.py
# 1. Point to your new model
AUTH_USER_MODEL = 'users.CustomUser'
# 2. Configure media (image) handling
import os
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Step 5: Apply Migrations
Now create the database tables.
python manage.py makemigrations
python manage.py migrate
Phase 2: Base Template (Bootstrap)
We need a main layout file to avoid repeating code. We already created the base.html template in our previous classes, so we are going to use it.
Phase 3: The Signup Feature
We will now build the Signup logic, then the template, then the URL.
Step 1: Create the Form
We need a form that handles the new fields. Create a file users/forms.py.
# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ['username', 'email', 'date_of_birth', 'gender', 'photo']
widgets = {
'date_of_birth': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'gender': forms.Select(attrs={'class': 'form-select'}),
'photo': forms.FileInput(attrs={'class': 'form-control'}),
}
# Loop through fields to add Bootstrap styling
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
if 'class' not in self.fields[field].widget.attrs:
self.fields[field].widget.attrs.update({'class': 'form-control'})
Step 2: The Signup View
Open users/views.py.
# users/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import CustomUserCreationForm
def signup_view(request):
if request.method == 'POST':
# request.FILES is required for image upload
form = CustomUserCreationForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, 'Account created successfully! Please login.')
return redirect('login') # We will create this URL next
else:
form = CustomUserCreationForm()
return render(request, 'users/signup.html', {'form': form})
Step 3: The Signup Template
Create users/templates/users/signup.html.
{% extends 'base.html' %}
{% block content %}
<div class="card mx-auto" style="max-width: 500px;">
<div class="card-header bg-primary text-white">Sign Up</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary w-100">Register</button>
</form>
<p class="mt-2 text-center">Already have an account? <a href="{% url 'login' %}">Login</a></p>
</div>
</div>
{% endblock %}
Step 4: The Signup URL
Create users/urls.py and add the path.
# users/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('signup/', views.signup_view, name='signup'),
]
Phase 4: The Login Feature
Step 1: The Login View
Open users/views.py and add this function.
# users/views.py (Append this)
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import AuthenticationForm
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
messages.info(request, f"Welcome {username}!")
return redirect('task_list') # Redirect to dashboard/home in real app
else:
messages.error(request, "Invalid username or password.")
else:
messages.error(request, "Invalid username or password.")
else:
form = AuthenticationForm()
# Add Bootstrap styling manually for built-in form
for field in form.fields.values():
field.widget.attrs['class'] = 'form-control'
return render(request, 'users/login.html', {'form': form})
Step 2: The Login Template
Create users/templates/users/login.html.
{% extends 'base.html' %}
{% block content %}
<div class="card mx-auto" style="max-width: 400px;">
<div class="card-header bg-success text-white">Login</div>
<div class="card-body">
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-success w-100">Login</button>
</form>
<p class="mt-2 text-center">No account? <a href="{% url 'signup' %}">Sign up</a></p>
</div>
</div>
{% endblock %}
Step 3: Update URLs
Open users/urls.py and add the login path.
# users/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('signup/', views.signup_view, name='signup'),
path('login/', views.login_view, name='login'), # Added this
]
Phase 5: The Logout Feature
Step 1: The Logout View
Open users/views.py and add this function.
# users/views.py (Append this)
from django.contrib.auth import logout
def logout_view(request):
logout(request)
messages.info(request, "You have been logged out.")
return redirect('login')
Step 2: Update URLs
Open users/urls.py and add the logout path.
# users/urls.py
urlpatterns = [
path('signup/', views.signup_view, name='signup'),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'), # Added this
]
Phase 6: The Change Password Feature
Step 1: The Change Password View
Open users/views.py and add this function.
# users/views.py (Append this)
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.decorators import login_required
@login_required
def change_password_view(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
# This is critical: keeps user logged in after password change
update_session_auth_hash(request, user)
messages.success(request, 'Your password was successfully updated!')
return redirect('login') # Or redirect to profile
else:
messages.error(request, 'Please correct the error below.')
else:
form = PasswordChangeForm(request.user)
for field in form.fields.values():
field.widget.attrs['class'] = 'form-control'
return render(request, 'users/change_password.html', {'form': form})
Step 2: The Change Password Template
Create users/templates/users/change_password.html.
{% extends 'base.html' %}
{% block content %}
<div class="card mx-auto" style="max-width: 500px;">
<div class="card-header bg-warning">Change Password</div>
<div class="card-body">
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-warning w-100">Update Password</button>
</form>
</div>
</div>
{% endblock %}
Step 3: Update URLs
Open users/urls.py and add the final path.
# users/urls.py
urlpatterns = [
path('signup/', views.signup_view, name='signup'),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('change-password/', views.change_password_view, name='change_password'), # Added this
]
Run the server test:
python manage.py runserver