第9章:用户认证系统
9.2 用户注册和登录
注册表单
创建用户注册功能需要自定义表单来处理用户输入和验证:
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("该邮箱已被注册")
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
# 更复杂的注册表单
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)
# 为密码字段添加样式
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("该邮箱已被注册,请使用其他邮箱")
return email
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise ValidationError("该用户名已被使用,请选择其他用户名")
return username登录/登出视图
实现自定义登录和登出功能:
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'账户 {username} 创建成功!现在可以登录了。')
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"欢迎您,{username}!")
# 重定向到用户之前访问的页面或首页
next_page = request.GET.get('next', 'home')
return redirect(next_page)
else:
messages.error(request, "用户名或密码错误")
else:
messages.error(request, "用户名或密码错误")
else:
form = AuthenticationForm()
return render(request, 'registration/login.html', {'form': form})
@login_required
def user_logout(request):
logout(request)
messages.info(request, "您已成功退出登录")
return redirect('home')
# 检查用户是否已登录的装饰器使用
@login_required
def profile(request):
return render(request, 'registration/profile.html')
# 限制只有未登录用户才能访问的页面
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):
# 只有未登录用户才能访问注册页面
pass登录模板示例:
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>用户登录</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">用户名</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">密码</label>
{{ form.password }}
{% if form.password.errors %}
<div class="text-danger">{{ form.password.errors }}</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">登录</button>
<a href="{% url 'password_reset' %}" class="btn btn-link">忘记密码?</a>
</form>
<div class="mt-3">
<p>还没有账户?<a href="{% url 'register' %}">立即注册</a></p>
</div>
</div>
</div>
</div>
</div>
</div>注册模板示例:
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>用户注册</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">必填。150个字符或更少。只能包含字母、数字和@/./+/-/_字符。</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>密码不能与您的其他个人信息太相似。</li>
<li>密码必须包含至少8个字符。</li>
<li>密码不能是常用密码。</li>
<li>密码不能全为数字。</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">为了验证,请输入与上面相同的密码。</div>
</div>
<button type="submit" class="btn btn-primary">注册</button>
</form>
<div class="mt-3">
<p>已有账户?<a href="{% url 'login' %}">立即登录</a></p>
</div>
</div>
</div>
</div>
</div>
</div>密码重置
Django提供了完整的密码重置功能:
python
# 自定义密码重置表单
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):
"""
发送密码重置邮件
"""
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()
# 自定义密码重置视图
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配置
urlpatterns = [
path('password_reset/', CustomPasswordResetView.as_view(), name='password_reset'),
# 其他认证URL
]密码重置模板:
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>重置密码</h3>
</div>
<div class="card-body">
<p>请输入您的邮箱地址,我们将发送密码重置链接到您的邮箱。</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">发送重置链接</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- registration/password_reset_email.html -->
{% autoescape off %}
您好,
您收到这封邮件是因为您请求重置在 {{ site_name }} 网站上的用户账户密码。
请点击下面的链接并选择新密码:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
您的用户名是:{{ user.get_username }}
如果您没有请求密码重置,请忽略这封邮件。
感谢使用我们的网站!
{{ site_name }} 团队
{% endautoescape %}
<!-- registration/password_reset_subject.txt -->
{{ site_name }} 密码重置邮箱验证
实现邮箱验证功能:
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("该邮箱地址未注册或未激活")
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):
"""发送验证邮件"""
# 创建或获取验证记录
verification, created = EmailVerification.objects.get_or_create(user=user)
verification.token = get_random_string(50)
verification.save()
# 构建验证链接
current_site = get_current_site(request)
subject = '请验证您的邮箱地址'
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):
"""验证邮箱"""
try:
verification = EmailVerification.objects.get(token=token)
if not verification.is_verified:
verification.is_verified = True
verification.save()
messages.success(request, '您的邮箱已成功验证!')
else:
messages.info(request, '您的邮箱已经验证过了。')
except EmailVerification.DoesNotExist:
messages.error(request, '无效的验证链接。')
return redirect('home')
# 在用户注册后发送验证邮件
def register(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
# 发送验证邮件
send_verification_email(request, user)
messages.success(request, '注册成功!请检查您的邮箱并点击验证链接。')
return redirect('login')
else:
form = CustomUserCreationForm()
return render(request, 'registration/register.html', {'form': form})邮箱验证模板:
html
<!-- registration/verification_email.html -->
{% autoescape off %}
您好 {{ user.username }},
感谢您注册 {{ site_name }}!
请点击下面的链接验证您的邮箱地址:
http://{{ domain }}{% url 'verify_email' token=token %}
如果您没有注册账户,请忽略这封邮件。
谢谢!
{{ site_name }} 团队
{% endautoescape %}通过这些功能,你可以构建一个完整的用户认证系统,包括注册、登录、密码重置和邮箱验证等功能。
小结
用户注册和登录是Web应用的核心功能:
- ✅ 自定义注册表单处理用户输入和验证
- ✅ 登录/登出视图管理用户会话
- ✅ 密码重置功能保障账户安全
- ✅ 邮箱验证确保账户有效性
- ✅ 模板和消息系统的良好集成
完整的认证流程提升了用户体验和系统安全性。
下一篇
我们将学习权限和装饰器的高级用法。