Chapter 9: User Authentication System
9.2 User Registration and Login
Registration Form
Creating user registration functionality requires custom forms to handle user input and validation:
python
# forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
class CustomUserCreationForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email", "password1", "password2")
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise ValidationError("This email has already been 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
# More complex registration form
class ExtendedUserCreationForm(UserCreationForm):
email = forms.EmailField(
required=True,
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
first_name = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
last_name = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email", "password1", "password2")
widgets = {
'username': forms.TextInput(attrs={'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add styles to password fields
self.fields['password1'].widget.attrs.update({'class': 'form-control'})
self.fields['password2'].widget.attrs.update({'class': 'form-control'})
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise ValidationError("This email has already been registered. Please use another email")
return email
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise ValidationError("This username is already taken. Please choose another username")
return usernameLogin/Logout Views
Implement custom login and logout functionality:
python
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import CustomUserCreationForm
def register(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Account {username} created successfully! You can now log in.')
return redirect('login')
else:
form = CustomUserCreationForm()
return render(request, 'registration/register.html', {'form': form})
def user_login(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
messages.info(request, f"Welcome, {username}!")
# Redirect to the page the user was previously visiting or home page
next_page = request.GET.get('next', 'home')
return redirect(next_page)
else:
messages.error(request, "Invalid username or password")
else:
messages.error(request, "Invalid username or password")
else:
form = AuthenticationForm()
return render(request, 'registration/login.html', {'form': form})
@login_required
def user_logout(request):
logout(request)
messages.info(request, "You have successfully logged out")
return redirect('home')
# Using decorator to check if user is logged in
@login_required
def profile(request):
return render(request, 'registration/profile.html')
# Restrict access to pages for unauthenticated users only
from django.contrib.auth.decorators import user_passes_test
def anonymous_required(function=None, redirect_url=None):
if not redirect_url:
redirect_url = 'home'
actual_decorator = user_passes_test(
lambda u: u.is_anonymous,
login_url=redirect_url
)
if function:
return actual_decorator(function)
return actual_decorator
@anonymous_required
def register_view(request):
# Only unauthenticated users can access the registration page
passLogin template example:
html
<!-- registration/login.html -->
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>User Login</h3>
</div>
<div class="card-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.username.id_for_label }}" class="form-label">Username</label>
{{ form.username }}
{% if form.username.errors %}
<div class="text-danger">{{ form.username.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.password.id_for_label }}" class="form-label">Password</label>
{{ form.password }}
{% if form.password.errors %}
<div class="text-danger">{{ form.password.errors }}</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">Login</button>
<a href="{% url 'password_reset' %}" class="btn btn-link">Forgot password?</a>
</form>
<div class="mt-3">
<p>Don't have an account? <a href="{% url 'register' %}">Register now</a></p>
</div>
</div>
</div>
</div>
</div>
</div>Registration template example:
html
<!-- registration/register.html -->
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>User Registration</h3>
</div>
<div class="card-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
<form method="post">
{% csrf_token %}
<div class="mb-3">
{{ form.username.label_tag }}
{{ form.username }}
{% if form.username.errors %}
<div class="text-danger">{{ form.username.errors }}</div>
{% endif %}
<div class="form-text">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.first_name.label_tag }}
{{ form.first_name }}
{% if form.first_name.errors %}
<div class="text-danger">{{ form.first_name.errors }}</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.last_name.label_tag }}
{{ form.last_name }}
{% if form.last_name.errors %}
<div class="text-danger">{{ form.last_name.errors }}</div>
{% endif %}
</div>
</div>
<div class="mb-3">
{{ form.email.label_tag }}
{{ form.email }}
{% if form.email.errors %}
<div class="text-danger">{{ form.email.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.password1.label_tag }}
{{ form.password1 }}
{% if form.password1.errors %}
<div class="text-danger">{{ form.password1.errors }}</div>
{% endif %}
<div class="form-text">
<ul>
<li>Your password can't be too similar to your other personal information.</li>
<li>Your password must contain at least 8 characters.</li>
<li>Your password can't be a commonly used password.</li>
<li>Your password can't be entirely numeric.</li>
</ul>
</div>
</div>
<div class="mb-3">
{{ form.password2.label_tag }}
{{ form.password2 }}
{% if form.password2.errors %}
<div class="text-danger">{{ form.password2.errors }}</div>
{% endif %}
<div class="form-text">For verification, please enter the same password as above.</div>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
<div class="mt-3">
<p>Already have an account? <a href="{% url 'login' %}">Login now</a></p>
</div>
</div>
</div>
</div>
</div>
</div>Password Reset
Django provides complete password reset functionality:
python
# Custom password reset form
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.tokens import default_token_generator
from django.template import loader
from django.core.mail import EmailMultiAlternatives
from django.contrib.sites.shortcuts import get_current_site
class CustomPasswordResetForm(PasswordResetForm):
def send_mail(self, subject_template_name, email_template_name,
context, from_email, to_email, html_email_template_name=None):
"""
Send password reset email
"""
subject = loader.render_to_string(subject_template_name, context)
subject = ''.join(subject.splitlines())
body = loader.render_to_string(email_template_name, context)
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
if html_email_template_name is not None:
html_email = loader.render_to_string(html_email_template_name, context)
email_message.attach_alternative(html_email, 'text/html')
email_message.send()
# Custom password reset view
from django.contrib.auth.views import PasswordResetView
from .forms import CustomPasswordResetForm
class CustomPasswordResetView(PasswordResetView):
form_class = CustomPasswordResetForm
template_name = 'registration/password_reset_form.html'
email_template_name = 'registration/password_reset_email.html'
subject_template_name = 'registration/password_reset_subject.txt'
success_url = '/accounts/password_reset/done/'
# URL configuration
urlpatterns = [
path('password_reset/', CustomPasswordResetView.as_view(), name='password_reset'),
# Other authentication URLs
]Password reset templates:
html
<!-- registration/password_reset_form.html -->
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>Reset Password</h3>
</div>
<div class="card-body">
<p>Please enter your email address. We will send a password reset link to your email.</p>
<form method="post">
{% csrf_token %}
<div class="mb-3">
{{ form.email.label_tag }}
{{ form.email }}
{% if form.email.errors %}
<div class="text-danger">{{ form.email.errors }}</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">Send Reset Link</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- registration/password_reset_email.html -->
{% autoescape off %}
Hello,
You received this email because you requested a password reset for your user account on {{ site_name }}.
Please click the link below and choose a new password:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
Your username is: {{ user.get_username }}
If you didn't request a password reset, please ignore this email.
Thank you for using our website!
The {{ site_name }} Team
{% endautoescape %}
<!-- registration/password_reset_subject.txt -->
{{ site_name }} Password ResetEmail Verification
Implement email verification functionality:
python
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils.crypto import get_random_string
class EmailVerification(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
token = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
is_verified = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if not self.token:
self.token = get_random_string(50)
super().save(*args, **kwargs)
# forms.py
from django import forms
from django.contrib.auth.models import User
class EmailVerificationForm(forms.Form):
email = forms.EmailField()
def clean_email(self):
email = self.cleaned_data['email']
if not User.objects.filter(email=email, is_active=True).exists():
raise forms.ValidationError("This email address is not registered or not activated")
return email
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.contrib.sites.shortcuts import get_current_site
from .models import EmailVerification
def send_verification_email(request, user):
"""Send verification email"""
# Create or get verification record
verification, created = EmailVerification.objects.get_or_create(user=user)
verification.token = get_random_string(50)
verification.save()
# Build verification link
current_site = get_current_site(request)
subject = 'Please verify your email address'
message = render_to_string('registration/verification_email.html', {
'user': user,
'domain': current_site.domain,
'token': verification.token,
})
send_mail(subject, message, 'noreply@yoursite.com', [user.email])
def verify_email(request, token):
"""Verify email"""
try:
verification = EmailVerification.objects.get(token=token)
if not verification.is_verified:
verification.is_verified = True
verification.save()
messages.success(request, 'Your email has been successfully verified!')
else:
messages.info(request, 'Your email has already been verified.')
except EmailVerification.DoesNotExist:
messages.error(request, 'Invalid verification link.')
return redirect('home')
# Send verification email after user registration
def register(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
# Send verification email
send_verification_email(request, user)
messages.success(request, 'Registration successful! Please check your email and click the verification link.')
return redirect('login')
else:
form = CustomUserCreationForm()
return render(request, 'registration/register.html', {'form': form})Email verification template:
html
<!-- registration/verification_email.html -->
{% autoescape off %}
Hello {{ user.username }},
Thank you for registering on {{ site_name }}!
Please click the link below to verify your email address:
http://{{ domain }}{% url 'verify_email' token=token %}
If you didn't register for an account, please ignore this email.
Thank you!
The {{ site_name }} Team
{% endautoescape %}With these features, you can build a complete user authentication system, including registration, login, password reset, and email verification functions.
Summary
User registration and login are core features of web applications:
- ✅ Custom registration forms handle user input and validation
- ✅ Login/logout views manage user sessions
- ✅ Password reset functionality ensures account security
- ✅ Email verification ensures account validity
- ✅ Good integration of templates and messaging systems
A complete authentication process improves user experience and system security.
Next
We will learn about advanced usage of permissions and decorators.