AI Tutor Gemini

Week 4: User Authentication

Page
Style
Download Notes
Month 1: The Foundations (Weeks 1-4)
Module 1

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:

  1. Implement Django's built-in authentication system
  2. Create user registration, login, and logout functionality
  3. Build user profiles with extended information
  4. Implement password reset and management features
  5. Create user-specific blog features and permissions
  6. Practice Pull Request workflow on GitHub
  7. 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_login

Authentication vs Authorization

  1. Authentication: "Who is this user?" (login/logout)
  2. Authorization: "What can this user do?" (permissions)

Key Components

  1. User Model: Stores user information
  2. Authentication Views: Login, logout, password reset
  3. Forms: User creation, login forms
  4. Middleware: Handles sessions and user state
  5. 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:

  1. Django Authentication System Overview (25 min)

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:

  1. Django URL Configuration Best Practices (20 min)

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 user

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

  1. 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:

  1. 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:

  1. 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-authentication

Creating Pull Request

  1. Go to your GitHub repository
  2. Click "Compare & pull request"
  3. 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 feedback

Session 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 registration

Login/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 login

Profile 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 accurate

Simple 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 accounts

Integration Testing with Blog

Test how authentication integrates with your blog:

  1. Comment System Testing:
  2. Comment as authenticated user (should be auto-approved)
  3. Comment as anonymous user (should require approval)
  4. Verify comment attribution shows correctly
  5. Navigation Testing:
  6. Check navigation shows login/register when not authenticated
  7. Check navigation shows profile/logout when authenticated
  8. Verify admin link only shows for staff users
  9. Author Profile Testing:
  10. Create posts with different authors
  11. Visit author profile pages
  12. 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 view

Week 4 Assignment

Requirements

Complete the user authentication system by implementing all features covered this week:

Core Features (Must Complete)

  1. [ ] User registration with custom form
  2. [ ] Login/logout functionality
  3. [ ] User profile page with statistics
  4. [ ] Profile editing capability
  5. [ ] Password change functionality
  6. [ ] Password reset via email
  7. [ ] Integration with blog navigation
  8. [ ] Enhanced comment system for authenticated users

GitHub Workflow (Must Complete)

  1. [ ] Create feature branch: feature/user-authentication
  2. [ ] Commit all changes with descriptive messages
  3. [ ] Push branch to GitHub
  4. [ ] Create Pull Request with detailed description
  5. [ ] Test all functionality before submitting

Testing Requirements

  1. [ ] Manual test all authentication features
  2. [ ] Verify responsive design on mobile
  3. [ ] Test error handling (wrong passwords, duplicate emails)
  4. [ ] Confirm email notifications work (console backend)
  5. [ ] Write at least 3 Django unit tests

Bonus Challenges

  1. [ ] Add "Remember Me" functionality to login
  2. [ ] Implement user avatar uploads
  3. [ ] Create a "My Posts" page for authenticated users
  4. [ ] Add social media links to user profiles
  5. [ ] Implement email confirmation for registration

Submission

  1. GitHub Repository: Ensure your feature branch is pushed
  2. Pull Request: Create detailed PR with testing instructions
  3. Demo: Be prepared to demonstrate all authentication features
  4. 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 test

Week 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

  1. Created accounts app with custom forms and views
  2. Used Django's built-in authentication views for standard functionality
  3. Implemented responsive authentication UI with modern styling
  4. Enhanced comment system to support both authenticated and anonymous users
  5. Added user-specific navigation and features
  6. Configured email backend for password reset functionality
  7. 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:

  1. Property listings and bookings
  2. User profiles for hosts and guests
  3. Advanced model relationships
  4. Search and filtering functionality
  5. 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:

  1. Django Authentication Best Practices (22 min)