Week 4: User Authentication
Week 4: User Authentication
Week 4: User Authentication
Professional Django Developer Bootcamp
Learning Objectives
By the end of this week, students will be able to:
- Implement Django's built-in authentication system
- Create user registration, login, and logout functionality
- Build user profiles with extended information
- Implement password reset and management features
- Create user-specific blog features and permissions
- Practice Pull Request workflow on GitHub
- Test authentication features thoroughly
Day 1: Django Authentication Fundamentals
Session 1: Understanding Django's Auth System (2 hours)
What Django Provides Out of the Box
Django includes a complete authentication system:
# Built-in User model fields:
# - username (unique identifier)
# - email
# - first_name, last_name
# - password (automatically hashed)
# - is_active, is_staff, is_superuser
# - date_joined, last_loginAuthentication vs Authorization
- Authentication: "Who is this user?" (login/logout)
- Authorization: "What can this user do?" (permissions)
Key Components
- User Model: Stores user information
- Authentication Views: Login, logout, password reset
- Forms: User creation, login forms
- Middleware: Handles sessions and user state
- Template Context: User object available in templates
Quick Setup Check
# In settings.py, these should already be included:
INSTALLED_APPS = [
'django.contrib.auth', # Authentication framework
'django.contrib.sessions', # Session management
'django.contrib.messages', # Message framework
]
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]Recommended Videos:
Session 2: Setting Up Authentication URLs (2 hours)
Step 1: Create Accounts App
# Create the accounts app
python manage.py startapp accounts
# Add to INSTALLED_APPS in settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
'accounts', # Add this line
]Step 2: Configure Authentication Settings
Add to settings.py:
# Authentication settings
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
# Email settings for password reset (development)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'Step 3: Create Authentication URLs
Create accounts/urls.py:
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
app_name = 'accounts'
urlpatterns = [
# Registration
path('register/', views.register, name='register'),
# Login/Logout using Django's built-in views
path('login/', auth_views.LoginView.as_view(
template_name='accounts/login.html'
), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
# Profile
path('profile/', views.profile, name='profile'),
path('profile/edit/', views.edit_profile, name='edit_profile'),
# Password management
path('password-change/', auth_views.PasswordChangeView.as_view(
template_name='accounts/password_change.html',
success_url='/accounts/profile/'
), name='password_change'),
path('password-reset/', auth_views.PasswordResetView.as_view(
template_name='accounts/password_reset.html',
success_url='/accounts/password-reset/done/'
), name='password_reset'),
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(
template_name='accounts/password_reset_done.html'
), name='password_reset_done'),
]Step 4: Update Main URLs
In blog_project/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('accounts.urls')),
path('', include('blog.urls')),
]Recommended Videos:
Day 2: User Registration and Login
Session 3: User Registration (2 hours)
Step 1: Create Registration Form
Create accounts/forms.py:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class SignUpForm(UserCreationForm):
first_name = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'First name'
})
)
last_name = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Last name'
})
)
email = forms.EmailField(
required=True,
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Email address'
})
)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add CSS classes to default fields
self.fields['username'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Username'
})
self.fields['password1'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Password'
})
self.fields['password2'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Confirm password'
})
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError("This email is already registered.")
return email
def save(self, commit=True):
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']
if commit:
user.save()
return userStep 2: Create Registration View
Create accounts/views.py:
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import SignUpForm
def register(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save()
# Automatically log in the user after registration
login(request, user)
messages.success(request, f'Welcome {user.first_name}! Your account has been created.')
return redirect('blog:post_list')
else:
messages.error(request, 'Please correct the errors below.')
else:
form = SignUpForm()
return render(request, 'accounts/register.html', {
'form': form,
'title': 'Sign Up'
})
@login_required
def profile(request):
return render(request, 'accounts/profile.html', {
'title': 'My Profile'
})
@login_required
def edit_profile(request):
if request.method == 'POST':
user = request.user
user.first_name = request.POST.get('first_name', '')
user.last_name = request.POST.get('last_name', '')
user.email = request.POST.get('email', '')
user.save()
messages.success(request, 'Profile updated successfully!')
return redirect('accounts:profile')
return render(request, 'accounts/edit_profile.html', {
'title': 'Edit Profile'
})Session 4: Authentication Templates (2 hours)
Create Base Authentication Template
Create accounts/templates/accounts/base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Django Blog{% endblock %}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.auth-container {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
width: 100%;
max-width: 400px;
}
.auth-header {
text-align: center;
margin-bottom: 2rem;
}
.auth-header h1 {
color: #333;
margin-bottom: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-weight: 600;
}
.form-control {
width: 100%;
padding: 0.75rem;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 1rem;
transition: border-color 0.3s;
}
.form-control:focus {
outline: none;
border-color: #667eea;
}
.btn {
width: 100%;
padding: 0.75rem;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
transition: background 0.3s;
}
.btn:hover {
background: #5a67d8;
}
.auth-links {
text-align: center;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.auth-links a {
color: #667eea;
text-decoration: none;
}
.auth-links a:hover {
text-decoration: underline;
}
.alert {
padding: 0.75rem;
margin-bottom: 1rem;
border-radius: 4px;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.errorlist {
color: #dc3545;
font-size: 0.875rem;
list-style: none;
margin-top: 0.25rem;
}
.back-link {
position: absolute;
top: 1rem;
left: 1rem;
color: white;
text-decoration: none;
background: rgba(255,255,255,0.2);
padding: 0.5rem 1rem;
border-radius: 5px;
}
.back-link:hover {
background: rgba(255,255,255,0.3);
}
</style>
</head>
<body>
<a href="{% url 'blog:post_list' %}" class="back-link">← Back to Blog</a>
<div class="auth-container">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
<div class="auth-header">
<h1>{% block header %}Authentication{% endblock %}</h1>
</div>
{% block content %}
{% endblock %}
</div>
</body>
</html>Registration Template
Create accounts/templates/accounts/register.html:
{% extends 'accounts/base.html' %}
{% block title %}Sign Up - My Django Blog{% endblock %}
{% block header %}Create Account{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.username.id_for_label }}">Username</label>
{{ form.username }}
{% for error in form.username.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.first_name.id_for_label }}">First Name</label>
{{ form.first_name }}
{% for error in form.first_name.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.last_name.id_for_label }}">Last Name</label>
{{ form.last_name }}
{% for error in form.last_name.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.email.id_for_label }}">Email</label>
{{ form.email }}
{% for error in form.email.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.password1.id_for_label }}">Password</label>
{{ form.password1 }}
{% for error in form.password1.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.password2.id_for_label }}">Confirm Password</label>
{{ form.password2 }}
{% for error in form.password2.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<button type="submit" class="btn">Create Account</button>
</form>
<div class="auth-links">
<p>Already have an account? <a href="{% url 'accounts:login' %}">Sign in</a></p>
</div>
{% endblock %}Login Template
Create accounts/templates/accounts/login.html:
{% extends 'accounts/base.html' %}
{% block title %}Sign In - My Django Blog{% endblock %}
{% block header %}Welcome Back{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.username.id_for_label }}">Username</label>
{{ form.username }}
{% for error in form.username.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.password.id_for_label }}">Password</label>
{{ form.password }}
{% for error in form.password.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
{% endif %}
<button type="submit" class="btn">Sign In</button>
</form>
<div class="auth-links">
<p><a href="{% url 'accounts:password_reset' %}">Forgot password?</a></p>
<p>Don't have an account? <a href="{% url 'accounts:register' %}">Sign up</a></p>
</div>
{% endblock %}Recommended Videos:
- Django User Registration Tutorial (30 min)
Day 3: User Profiles and Blog Integration
Session 5: User Profile System (2 hours)
Profile Template
Create accounts/templates/accounts/profile.html:
{% extends 'blog/base.html' %}
{% block title %}{{ title }} - My Django Blog{% endblock %}
{% block content %}
<div class="profile-container">
<div class="profile-header">
<div class="profile-info">
<div class="profile-avatar">
{{ user.first_name|first }}{{ user.last_name|first }}
</div>
<div class="profile-details">
<h1>{{ user.get_full_name }}</h1>
<p class="username">@{{ user.username }}</p>
<p class="email">{{ user.email }}</p>
<p class="join-date">Member since {{ user.date_joined|date:"F Y" }}</p>
</div>
</div>
<div class="profile-actions">
<a href="{% url 'accounts:edit_profile' %}" class="btn btn-primary">Edit Profile</a>
<a href="{% url 'accounts:password_change' %}" class="btn btn-outline">Change Password</a>
</div>
</div>
<div class="profile-stats">
<div class="stat-card">
<h3>{{ user.posts.count }}</h3>
<p>Blog Posts</p>
</div>
<div class="stat-card">
<h3>{{ user.posts.filter:status='published'.count }}</h3>
<p>Published</p>
</div>
<div class="stat-card">
<h3>{{ user.posts.filter:status='draft'.count }}</h3>
<p>Drafts</p>
</div>
</div>
<div class="recent-activity">
<h2>Recent Posts</h2>
{% for post in user.posts.all|slice:":5" %}
<div class="activity-item">
<h4><a href="{% url 'blog:post_detail' slug=post.slug %}">{{ post.title }}</a></h4>
<p>{{ post.status|title }} • {{ post.created_at|date:"M d, Y" }}</p>
</div>
{% empty %}
<p>No posts yet. <a href="{% url 'admin:index' %}">Create your first post!</a></p>
{% endfor %}
</div>
</div>
<style>
.profile-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.profile-header {
display: flex;
justify-content: space-between;
align-items: center;
background: #f8f9fa;
padding: 2rem;
border-radius: 10px;
margin-bottom: 2rem;
}
.profile-info {
display: flex;
align-items: center;
gap: 1.5rem;
}
.profile-avatar {
width: 80px;
height: 80px;
background: #667eea;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: bold;
}
.profile-details h1 {
margin: 0 0 0.5rem 0;
}
.username {
color: #667eea;
font-weight: 600;
margin: 0;
}
.email, .join-date {
color: #666;
margin: 0.25rem 0;
}
.profile-actions {
display: flex;
gap: 1rem;
}
.profile-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-card h3 {
font-size: 2rem;
color: #667eea;
margin-bottom: 0.5rem;
}
.recent-activity {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.activity-item {
padding: 1rem 0;
border-bottom: 1px solid #eee;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-item h4 {
margin: 0 0 0.5rem 0;
}
.activity-item a {
color: #333;
text-decoration: none;
}
.activity-item a:hover {
color: #667eea;
}
@media (max-width: 768px) {
.profile-header {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.profile-actions {
width: 100%;
flex-direction: column;
}
}
</style>
{% endblock %}Edit Profile Template
Create accounts/templates/accounts/edit_profile.html:
{% extends 'accounts/base.html' %}
{% block title %}Edit Profile - My Django Blog{% endblock %}
{% block header %}Edit Profile{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="first_name">First Name</label>
<input type="text" id="first_name" name="first_name"
value="{{ user.first_name }}" class="form-control" required>
</div>
<div class="form-group">
<label for="last_name">Last Name</label>
<input type="text" id="last_name" name="last_name"
value="{{ user.last_name }}" class="form-control" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email"
value="{{ user.email }}" class="form-control" required>
</div>
<button type="submit" class="btn">Save Changes</button>
</form>
<div class="auth-links">
<p><a href="{% url 'accounts:profile' %}">Cancel</a></p>
</div>
{% endblock %}Session 6: Integrating Authentication with Blog (2 hours)
Update Blog Navigation
Update blog/templates/blog/base.html to include authentication:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Django Blog{% endblock %}</title>
<style>
/* ... existing styles ... */
.user-info {
display: flex;
align-items: center;
gap: 1rem;
}
.user-avatar {
width: 32px;
height: 32px;
background: #667eea;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.9rem;
}
.auth-nav {
display: flex;
align-items: center;
gap: 1rem;
}
</style>
</head>
<body>
<div class="container">
<header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<h1>My Django Blog</h1>
<div class="auth-nav">
{% if user.is_authenticated %}
<div class="user-info">
<span class="user-avatar">{{ user.first_name|first }}{{ user.last_name|first }}</span>
<span>Hello, {{ user.first_name }}!</span>
</div>
{% endif %}
</div>
</div>
<nav>
<a href="{% url 'blog:post_list' %}">Home</a>
<a href="{% url 'blog:about' %}">About</a>
{% if user.is_authenticated %}
<a href="{% url 'accounts:profile' %}">Profile</a>
{% if user.is_staff %}
<a href="{% url 'admin:index' %}">Admin</a>
{% endif %}
<a href="{% url 'accounts:logout' %}">Logout</a>
{% else %}
<a href="{% url 'accounts:login' %}">Login</a>
<a href="{% url 'accounts:register' %}">Sign Up</a>
{% endif %}
</nav>
</header>
<main>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" style="padding: 1rem; margin: 1rem 0; border-radius: 4px;
{% if message.tags == 'success' %}background: #d4edda; color: #155724; border: 1px solid #c3e6cb;
{% elif message.tags == 'error' %}background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb;
{% endif %}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</main>
</div>
</body>
</html>Update Blog Models for User Integration
Update blog/models.py to show relationship with users:
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
content = models.TextField()
excerpt = models.TextField(max_length=300, blank=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=100)
email = models.EmailField()
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
is_approved = models.BooleanField(default=False)
class Meta:
ordering = ['created_at']
def __str__(self):
return f'Comment by {self.name} on {self.post.title}'Recommended Videos:
- Django User Profiles Tutorial (25 min)
Day 4: Password Management & User Features
Session 7: Password Reset System (2 hours)
Password Reset Templates
Create accounts/templates/accounts/password_reset.html:
{% extends 'accounts/base.html' %}
{% block title %}Password Reset - My Django Blog{% endblock %}
{% block header %}Reset Password{% endblock %}
{% block content %}
<p style="text-align: center; color: #666; margin-bottom: 1.5rem;">
Enter your email address and we'll send you a reset link
</p>
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.email.id_for_label }}">Email Address</label>
{{ form.email }}
{% for error in form.email.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<button type="submit" class="btn">Send Reset Link</button>
</form>
<div class="auth-links">
<p>Remember your password? <a href="{% url 'accounts:login' %}">Sign in</a></p>
</div>
{% endblock %}Create accounts/templates/accounts/password_reset_done.html:
{% extends 'accounts/base.html' %}
{% block title %}Password Reset Sent - My Django Blog{% endblock %}
{% block header %}Check Your Email{% endblock %}
{% block content %}
<div style="text-align: center;">
<div style="background: #d4edda; color: #155724; padding: 1rem; border-radius: 5px; margin-bottom: 1rem;">
<p><strong>Password reset email sent!</strong></p>
<p>We've sent you instructions for resetting your password. Check your email and follow the link provided.</p>
</div>
<p style="color: #666; font-size: 0.9rem;">
If you don't receive an email within a few minutes, please check your spam folder.
</p>
</div>
<div class="auth-links">
<p><a href="{% url 'accounts:login' %}">Return to sign in</a></p>
</div>
{% endblock %}Password Change Template
Create accounts/templates/accounts/password_change.html:
{% extends 'accounts/base.html' %}
{% block title %}Change Password - My Django Blog{% endblock %}
{% block header %}Change Password{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.old_password.id_for_label }}">Current Password</label>
{{ form.old_password }}
{% for error in form.old_password.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.new_password1.id_for_label }}">New Password</label>
{{ form.new_password1 }}
{% for error in form.new_password1.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.new_password2.id_for_label }}">Confirm New Password</label>
{{ form.new_password2 }}
{% for error in form.new_password2.errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
</div>
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="errorlist">{{ error }}</div>
{% endfor %}
{% endif %}
<button type="submit" class="btn">Change Password</button>
</form>
<div class="auth-links">
<p><a href="{% url 'accounts:profile' %}">Cancel</a></p>
</div>
{% endblock %}Session 8: User-Specific Blog Features (2 hours)
Enhanced Comment System with Authentication
Update blog/forms.py to handle authenticated comments:
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
"""Form for anonymous users to comment"""
class Meta:
model = Comment
fields = ['name', 'email', 'content']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Your name'
}),
'email': forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'your.email@example.com'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Share your thoughts...'
}),
}
class AuthenticatedCommentForm(forms.ModelForm):
"""Form for authenticated users to comment"""
class Meta:
model = Comment
fields = ['content']
widgets = {
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Share your thoughts...'
}),
}Update Blog Views for Authentication
Update blog/views.py to handle authenticated users:
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from .models import Post, Comment
from .forms import CommentForm, AuthenticatedCommentForm
def post_detail(request, slug):
"""Enhanced post detail with user-aware comments"""
post = get_object_or_404(Post, slug=slug, status='published')
comments = post.comments.filter(is_approved=True).order_by('created_at')
# Use different forms for authenticated vs anonymous users
if request.user.is_authenticated:
comment_form = AuthenticatedCommentForm()
else:
comment_form = CommentForm()
context = {
'post': post,
'comments': comments,
'comment_form': comment_form,
'title': post.title,
}
return render(request, 'blog/post_detail.html', context)
@require_POST
def add_comment(request, slug):
"""Handle comment submission for both authenticated and anonymous users"""
post = get_object_or_404(Post, slug=slug, status='published')
if request.user.is_authenticated:
form = AuthenticatedCommentForm(request.POST)
else:
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
if request.user.is_authenticated:
comment.author = request.user
comment.name = request.user.get_full_name() or request.user.username
comment.email = request.user.email
comment.is_approved = True # Auto-approve for authenticated users
comment.save()
if request.user.is_authenticated:
messages.success(request, 'Your comment has been added!')
else:
messages.success(request, 'Your comment has been submitted and is awaiting approval.')
return redirect('blog:post_detail', slug=slug)
messages.error(request, 'Please correct the errors in your comment.')
return redirect('blog:post_detail', slug=slug)
def author_profile(request, username):
"""Public author profile page"""
author = get_object_or_404(User, username=username)
posts = Post.objects.filter(
author=author,
status='published'
).order_by('-published_at')[:10]
context = {
'author': author,
'posts': posts,
'title': f'{author.get_full_name() or author.username} - Author Profile'
}
return render(request, 'blog/author_profile.html', context)Enhanced Post Detail Template
Update blog/templates/blog/post_detail.html:
{% extends 'blog/base.html' %}
{% block title %}{{ post.title }} - My Django Blog{% endblock %}
{% block content %}
<article class="post-detail">
<header class="post-header">
<h1>{{ post.title }}</h1>
<div class="post-meta">
<span>By
<a href="{% url 'blog:author_profile' username=post.author.username %}">
{{ post.author.get_full_name|default:post.author.username }}
</a>
</span>
<span>{{ post.published_at|date:"F d, Y" }}</span>
</div>
</header>
<div class="post-content">
{{ post.content|linebreaks }}
</div>
</article>
<!-- Comments Section -->
<section class="comments-section">
<h3>Comments ({{ comments.count }})</h3>
<!-- Comment Form -->
<div class="comment-form-wrapper">
{% if user.is_authenticated %}
<div class="authenticated-user-info">
<div class="user-avatar">{{ user.first_name|first }}{{ user.last_name|first }}</div>
<span>Commenting as <strong>{{ user.get_full_name|default:user.username }}</strong></span>
</div>
<form method="post" action="{% url 'blog:add_comment' slug=post.slug %}">
{% csrf_token %}
<div class="form-group">
{{ comment_form.content }}
</div>
<button type="submit" class="btn btn-primary">Post Comment</button>
</form>
{% else %}
<div class="guest-prompt">
<p><a href="{% url 'accounts:login' %}?next={{ request.path }}">Sign in</a>
or <a href="{% url 'accounts:register' %}">create an account</a> to comment instantly.</p>
<details class="guest-form-toggle">
<summary>Or comment as a guest</summary>
<form method="post" action="{% url 'blog:add_comment' slug=post.slug %}">
{% csrf_token %}
<div class="form-group">
{{ comment_form.name.label_tag }}
{{ comment_form.name }}
</div>
<div class="form-group">
{{ comment_form.email.label_tag }}
{{ comment_form.email }}
</div>
<div class="form-group">
{{ comment_form.content.label_tag }}
{{ comment_form.content }}
</div>
<button type="submit" class="btn btn-primary">Submit Comment</button>
<small>Guest comments require approval.</small>
</form>
</details>
</div>
{% endif %}
</div>
<!-- Comments List -->
<div class="comments-list">
{% for comment in comments %}
<div class="comment">
<div class="comment-header">
<div class="comment-author">
<div class="user-avatar">{{ comment.name|first|upper }}</div>
<strong>{{ comment.name }}</strong>
{% if comment.author %}
<span class="verified">✓ Verified</span>
{% endif %}
</div>
<small>{{ comment.created_at|date:"M d, Y \a\t g:i A" }}</small>
</div>
<div class="comment-content">
{{ comment.content|linebreaks }}
</div>
</div>
{% empty %}
<p class="no-comments">No comments yet. Be the first to comment!</p>
{% endfor %}
</div>
</section>
<style>
.post-detail {
max-width: 800px;
margin: 0 auto 3rem auto;
}
.post-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.post-header h1 {
margin-bottom: 1rem;
color: #333;
}
.post-meta {
color: #666;
font-size: 0.9rem;
}
.post-meta span {
margin-right: 1rem;
}
.post-meta a {
color: #667eea;
text-decoration: none;
}
.post-meta a:hover {
text-decoration: underline;
}
.post-content {
line-height: 1.8;
margin-bottom: 3rem;
}
.comments-section {
max-width: 800px;
margin: 0 auto;
}
.comment-form-wrapper {
background: #f8f9fa;
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.authenticated-user-info {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
padding: 1rem;
background: white;
border-radius: 5px;
}
.user-avatar {
width: 32px;
height: 32px;
background: #667eea;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.9rem;
}
.guest-prompt {
text-align: center;
margin-bottom: 1rem;
}
.guest-prompt a {
color: #667eea;
text-decoration: none;
font-weight: 600;
}
.guest-prompt a:hover {
text-decoration: underline;
}
.guest-form-toggle {
margin-top: 1rem;
}
.guest-form-toggle summary {
cursor: pointer;
padding: 0.5rem;
background: white;
border-radius: 3px;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}
.form-control {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.btn {
background: #667eea;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.btn:hover {
background: #5a67d8;
}
.comment {
padding: 1.5rem 0;
border-bottom: 1px solid #eee;
}
.comment:last-child {
border-bottom: none;
}
.comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.comment-author {
display: flex;
align-items: center;
gap: 0.5rem;
}
.verified {
color: #28a745;
font-size: 0.8rem;
}
.comment-content {
color: #555;
line-height: 1.6;
}
.no-comments {
text-align: center;
color: #666;
font-style: italic;
}
</style>
{% endblock %}Author Profile Template
Create blog/templates/blog/author_profile.html:
{% extends 'blog/base.html' %}
{% block title %}{{ title }} - My Django Blog{% endblock %}
{% block content %}
<div class="author-profile">
<div class="author-header">
<div class="author-info">
<div class="author-avatar">
{{ author.first_name|first }}{{ author.last_name|first }}
</div>
<div class="author-details">
<h1>{{ author.get_full_name|default:author.username }}</h1>
<p class="author-username">@{{ author.username }}</p>
<p class="author-stats">
{{ posts.count }} published post{{ posts.count|pluralize }}
• Member since {{ author.date_joined|date:"F Y" }}
</p>
</div>
</div>
</div>
<div class="author-posts">
<h2>Recent Posts</h2>
{% for post in posts %}
<article class="post-preview">
<h3>
<a href="{% url 'blog:post_detail' slug=post.slug %}">
{{ post.title }}
</a>
</h3>
<div class="post-meta">
{{ post.published_at|date:"F d, Y" }}
• {{ post.comments.count }} comment{{ post.comments.count|pluralize }}
</div>
<p class="post-excerpt">
{{ post.excerpt|default:post.content|truncatewords:25 }}
</p>
</article>
{% empty %}
<p>This author hasn't published any posts yet.</p>
{% endfor %}
</div>
</div>
<style>
.author-profile {
max-width: 800px;
margin: 0 auto;
}
.author-header {
background: #f8f9fa;
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.author-info {
display: flex;
align-items: center;
gap: 1.5rem;
}
.author-avatar {
width: 80px;
height: 80px;
background: #667eea;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: bold;
}
.author-details h1 {
margin: 0 0 0.5rem 0;
color: #333;
}
.author-username {
color: #667eea;
font-weight: 600;
margin: 0 0 0.5rem 0;
}
.author-stats {
color: #666;
margin: 0;
}
.author-posts h2 {
margin-bottom: 1.5rem;
color: #333;
}
.post-preview {
padding: 1.5rem 0;
border-bottom: 1px solid #eee;
}
.post-preview:last-child {
border-bottom: none;
}
.post-preview h3 {
margin: 0 0 0.5rem 0;
}
.post-preview h3 a {
color: #333;
text-decoration: none;
}
.post-preview h3 a:hover {
color: #667eea;
}
.post-preview .post-meta {
color: #666;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.post-excerpt {
color: #555;
line-height: 1.6;
}
@media (max-width: 768px) {
.author-info {
flex-direction: column;
text-align: center;
}
}
</style>
{% endblock %}Recommended Videos:
- Django Password Reset Tutorial (20 min)
Day 5: Pull Request Workflow & Testing
Session 9: GitHub Pull Request Workflow (2 hours)
Create Feature Branch
# Make sure you're on main and it's up to date
git checkout main
git pull origin main
# Create and switch to feature branch
git checkout -b feature/user-authentication
# Work on your authentication features
# (Add all the files we created above)
# Stage all changes
git add .
# Make a comprehensive commit
git commit -m "Implement complete user authentication system
Features added:
- User registration with custom form validation
- Login/logout functionality with Django's built-in views
- User profile system with dashboard and editing
- Password reset via email (console backend for development)
- Password change functionality for authenticated users
- Integration of authentication with existing blog
- Enhanced comment system with user-specific features
- Author profile pages
- Responsive authentication UI with modern styling
Technical changes:
- Created accounts app with custom forms and views
- Added authentication templates with consistent styling
- Updated blog templates to show authentication status
- Enhanced comment model to support both authenticated and anonymous users
- Added user-specific features and permissions
- Configured email backend for password reset functionality"
# Push to GitHub
git push -u origin feature/user-authenticationCreating Pull Request
- Go to your GitHub repository
- Click "Compare & pull request"
- Fill out the PR description:
## 🔐 User Authentication System
### Overview
This PR implements a complete user authentication system for the Django blog application, including user registration, login/logout, profiles, and password management.
### ✨ Features Added
- [x] User registration with form validation
- [x] Login/logout with proper redirects
- [x] User profile dashboard with statistics
- [x] Profile editing functionality
- [x] Password reset via email
- [x] Password change for authenticated users
- [x] Enhanced comment system with user attribution
- [x] Author profile pages
- [x] Responsive authentication UI
### 🧪 How to Test
1. **Registration**: Go to `/accounts/register/` and create a new account
2. **Login**: Test login at `/accounts/login/`
3. **Profile**: Visit `/accounts/profile/` when logged in
4. **Password Reset**: Try password reset at `/accounts/password-reset/`
5. **Comments**: Test commenting both as authenticated and anonymous user
6. **Navigation**: Verify navigation changes based on auth status
### 📝 Testing Checklist
- [ ] User can register with valid data
- [ ] Email validation prevents duplicates
- [ ] Login/logout works correctly
- [ ] Profile displays user information
- [ ] Profile can be edited
- [ ] Password reset sends email to console
- [ ] Authenticated users can comment instantly
- [ ] Anonymous comments require approval
- [ ] Navigation updates based on login status
### 📸 Screenshots
<!-- Add screenshots of key pages -->
### 🔧 Technical Notes
- Uses Django's built-in authentication views where possible
- Custom forms for enhanced user experience
- Email backend configured for console output (development)
- Responsive CSS for mobile compatibility
- Proper error handling and user feedbackSession 10: Testing Your Authentication System (2 hours)
Manual Testing Checklist
Registration Tests:
# Test these scenarios:
1. Register with valid information
2. Try registering with existing email
3. Test password confirmation mismatch
4. Try registering with weak password
5. Verify user is automatically logged in after registrationLogin/Logout Tests:
# Test these scenarios:
1. Login with correct credentials
2. Login with incorrect credentials
3. Logout and verify user is logged out
4. Test login redirect to intended page
5. Access protected pages without loginProfile Tests:
# Test these scenarios:
1. View profile when logged in
2. Edit profile information
3. Change password with correct old password
4. Try changing password with wrong old password
5. Verify profile statistics are accurateSimple Django Tests
Create accounts/tests.py:
from django.test import TestCase, Client
from django.contrib.auth.models import User
from django.urls import reverse
class AuthenticationTests(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123',
first_name='Test',
last_name='User'
)
def test_user_registration(self):
"""Test user can register"""
response = self.client.post(reverse('accounts:register'), {
'username': 'newuser',
'first_name': 'New',
'last_name': 'User',
'email': 'newuser@example.com',
'password1': 'newpass123',
'password2': 'newpass123',
})
# Check user was created
self.assertTrue(User.objects.filter(username='newuser').exists())
# Check redirect after registration
self.assertEqual(response.status_code, 302)
def test_user_login(self):
"""Test user can login"""
response = self.client.post(reverse('accounts:login'), {
'username': 'testuser',
'password': 'testpass123'
})
# Check redirect after login
self.assertEqual(response.status_code, 302)
def test_profile_requires_login(self):
"""Test profile page requires authentication"""
response = self.client.get(reverse('accounts:profile'))
# Should redirect to login
self.assertEqual(response.status_code, 302)
def test_profile_view_authenticated(self):
"""Test profile view for logged in user"""
self.client.login(username='testuser', password='testpass123')
response = self.client.get(reverse('accounts:profile'))
# Should show profile page
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test User')Run tests:
python manage.py test accountsIntegration Testing with Blog
Test how authentication integrates with your blog:
- Comment System Testing:
- Comment as authenticated user (should be auto-approved)
- Comment as anonymous user (should require approval)
- Verify comment attribution shows correctly
- Navigation Testing:
- Check navigation shows login/register when not authenticated
- Check navigation shows profile/logout when authenticated
- Verify admin link only shows for staff users
- Author Profile Testing:
- Create posts with different authors
- Visit author profile pages
- Verify post counts and information are correct
Debug Common Issues
# Common authentication issues and solutions:
# 1. Login not working - check form errors
def debug_login(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if not form.is_valid():
print("Login form errors:", form.errors)
# ... rest of view
# 2. User not staying logged in - check session middleware
# Ensure SessionMiddleware is before AuthenticationMiddleware in MIDDLEWARE
# 3. Password reset not sending emails - check email backend
# For development: EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# 4. Profile view not showing user data - check login_required decorator
@login_required
def profile(request):
print(f"Current user: {request.user}")
print(f"Is authenticated: {request.user.is_authenticated}")
# ... rest of viewWeek 4 Assignment
Requirements
Complete the user authentication system by implementing all features covered this week:
Core Features (Must Complete)
- [ ] User registration with custom form
- [ ] Login/logout functionality
- [ ] User profile page with statistics
- [ ] Profile editing capability
- [ ] Password change functionality
- [ ] Password reset via email
- [ ] Integration with blog navigation
- [ ] Enhanced comment system for authenticated users
GitHub Workflow (Must Complete)
- [ ] Create feature branch:
feature/user-authentication - [ ] Commit all changes with descriptive messages
- [ ] Push branch to GitHub
- [ ] Create Pull Request with detailed description
- [ ] Test all functionality before submitting
Testing Requirements
- [ ] Manual test all authentication features
- [ ] Verify responsive design on mobile
- [ ] Test error handling (wrong passwords, duplicate emails)
- [ ] Confirm email notifications work (console backend)
- [ ] Write at least 3 Django unit tests
Bonus Challenges
- [ ] Add "Remember Me" functionality to login
- [ ] Implement user avatar uploads
- [ ] Create a "My Posts" page for authenticated users
- [ ] Add social media links to user profiles
- [ ] Implement email confirmation for registration
Submission
- GitHub Repository: Ensure your feature branch is pushed
- Pull Request: Create detailed PR with testing instructions
- Demo: Be prepared to demonstrate all authentication features
- Documentation: Update your README with authentication setup instructions
Testing Script
Use this to verify your implementation:
# 1. Start fresh
python manage.py migrate
python manage.py createsuperuser
# 2. Test registration
# Visit: http://localhost:8000/accounts/register/
# 3. Test login/logout
# Visit: http://localhost:8000/accounts/login/
# 4. Test profile
# Visit: http://localhost:8000/accounts/profile/
# 5. Test password reset
# Visit: http://localhost:8000/accounts/password-reset/
# 6. Test comments
# Visit any blog post and try commenting
# 7. Run tests
python manage.py testWeek 4 Summary
Congratulations! You've successfully implemented a complete user authentication system in Django:
🎯 Skills Mastered
✅ Django Authentication: Built-in User model and authentication views
✅ User Registration: Custom forms with validation and error handling
✅ Login/Logout: Session management and user state
✅ User Profiles: Dashboard with statistics and editing capabilities
✅ Password Management: Reset and change functionality with email
✅ Blog Integration: User-specific features and enhanced commenting
✅ GitHub Workflow: Feature branches and Pull Request process
✅ Testing: Manual testing and basic unit tests
🔧 Technical Implementation
- Created
accountsapp with custom forms and views - Used Django's built-in authentication views for standard functionality
- Implemented responsive authentication UI with modern styling
- Enhanced comment system to support both authenticated and anonymous users
- Added user-specific navigation and features
- Configured email backend for password reset functionality
- Integrated authentication seamlessly with existing blog application
🚀 What's Next?
In Week 5, you'll start building the Airbnb Clone, where you'll apply your authentication knowledge to create:
- Property listings and bookings
- User profiles for hosts and guests
- Advanced model relationships
- Search and filtering functionality
- Image uploads and media handling
Your authentication system provides the foundation for user-specific features in all future projects. You now understand how to create secure, user-friendly authentication flows that are essential for modern web applications!
Recommended Videos:
- Django Authentication Best Practices (22 min)