You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DjangoBlog/accounts/views.py

306 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth import get_user_model
from django.contrib.auth import logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
# 导入项目公共工具函数:邮件发送、加密、站点信息、验证码生成、缓存删除
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
from . import utils # 导入accounts应用自定义工具验证码发送/存储)
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm # 导入用户交互表单
from .models import BlogUser # 导入自定义用户模型
logger = logging.getLogger(__name__) # 初始化日志对象,用于记录视图层操作日志
# 模块级注释——accounts应用的核心视图文件
# 包含用户注册、登录、退出、邮箱验证、密码重置等核心业务的视图实现,
# 基于Django类视图FormView/RedirectView/View和函数视图
# 整合表单验证、邮件发送、缓存操作、权限控制等逻辑,处理用户交互的全流程
# Create your views here.
class RegisterView(FormView):
"""
用户注册视图继承Django FormView专门处理表单提交的类视图
核心功能:接收注册表单数据、验证合法性、创建未激活用户、发送邮箱验证链接、重定向到结果页
"""
form_class = RegisterForm # 关联注册表单类(处理用户名、邮箱、密码验证)
template_name = 'account/registration_form.html' # 注册页面模板路径
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
"""
重写分发方法添加CSRF防护装饰器
防止跨站请求伪造攻击,确保注册请求来自本网站合法表单
"""
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
"""
表单验证通过后的核心处理逻辑FormView的核心方法
流程:创建未激活用户 → 生成邮箱验证链接 → 发送验证邮件 → 重定向到注册结果页
"""
if form.is_valid():
# 1. 表单验证通过先不提交到数据库commit=False后续补充字段
user = form.save(False)
user.is_active = False # 默认设置用户为未激活状态(需邮箱验证后激活)
user.source = 'Register' # 标记用户创建来源:前台注册(区别于后台创建/第三方登录)
user.save(True) # 最终保存用户数据到数据库
# 2. 生成邮箱验证链接包含站点域名、用户ID、加密签名确保链接安全性
site = get_current_site().domain # 获取当前站点域名(如"example.com"
# 双重SHA256加密结合SECRET_KEY和用户ID防止链接被篡改
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境适配DEBUG模式下使用本地测试域名127.0.0.1:8000
if settings.DEBUG:
site = '127.0.0.1:8000'
path = reverse('account:result') # 验证结果页路由
# 拼接完整的验证链接HTTP协议开发环境可用生产环境建议改为HTTPS
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 3. 构建验证邮件内容HTML格式包含验证链接
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您!
<br />
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
""".format(url=url)
# 发送验证邮件到用户注册邮箱
send_email(
emailto=[user.email], # 收件人(注册时填写的邮箱)
title='验证您的电子邮箱', # 邮件标题
content=content) # 邮件HTML内容
# 4. 重定向到注册结果页(告知用户验证邮件已发送)
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
# 表单验证失败(如用户名已存在、密码不匹配),重新渲染注册页面并显示错误
return self.render_to_response({'form': form})
class LogoutView(RedirectView):
"""
用户退出登录视图继承Django RedirectView专门处理重定向的类视图
核心功能:执行退出登录逻辑、清理缓存、重定向到登录页
"""
url = '/login/' # 退出后重定向的目标URL登录页
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写分发方法添加never_cache装饰器
禁止浏览器缓存退出页,避免用户后退到已登录状态
"""
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
"""
处理GET请求退出登录请求
流程:执行退出登录 → 清理侧边栏缓存 → 重定向到登录页
"""
logout(request) # Django内置logout函数清除用户会话销毁登录状态
delete_sidebar_cache() # 清除侧边栏缓存(避免缓存中保留用户相关信息)
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginView(FormView):
"""
用户登录视图继承Django FormView
核心功能:接收登录表单数据、验证合法性、执行登录逻辑、处理"记住我"、重定向到目标页面
"""
form_class = LoginForm # 关联自定义登录表单(适配前端样式)
template_name = 'account/login.html' # 登录页面模板路径
success_url = '/' # 登录成功后的默认重定向URL网站根目录
redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名(默认'redirect_to',用于跳转前的目标页面)
login_ttl = 2626560 # "记住我"的会话有效期2626560秒 ≈ 1个月
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写分发方法添加3个核心装饰器
1. sensitive_post_parameters('password'):保护密码字段,避免在错误报告中泄露
2. csrf_protect防跨站请求伪造攻击
3. never_cache禁止浏览器缓存登录页确保每次请求都是最新状态
"""
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
补充模板上下文数据(传递重定向地址给模板)
用于登录成功后跳转到登录前的目标页面(如访问需要登录的页面时,先跳转登录,登录后返回原页面)
"""
# 从GET请求中获取重定向地址如?redirect_to=/article/1/
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
redirect_to = '/' # 无重定向地址时,默认跳转到根目录
kwargs['redirect_to'] = redirect_to # 将重定向地址添加到上下文
return super(LoginView, self).get_context_data(** kwargs)
def form_valid(self, form):
"""
表单验证通过后的核心登录逻辑
流程:验证表单 → 清理缓存 → 执行登录 → 处理"记住我" → 重定向
"""
# 重新初始化Django内置AuthenticationForm确保使用正确的认证逻辑
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
delete_sidebar_cache() # 清理侧边栏缓存(更新用户相关的缓存数据)
logger.info(self.redirect_field_name) # 记录重定向字段名到日志
# 执行登录:将用户信息存入会话,创建登录状态
auth.login(self.request, form.get_user())
# 处理"记住我"选项若勾选则设置会话有效期为1个月
if self.request.POST.get("remember"):
self.request.session.set_expiry(self.login_ttl)
# 调用父类form_valid方法执行重定向
return super(LoginView, self).form_valid(form)
else:
# 表单验证失败(如用户名/密码错误),重新渲染登录页并显示错误
return self.render_to_response({'form': form})
def get_success_url(self):
"""
自定义登录成功后的重定向URL
核心:校验重定向地址的合法性,避免恶意重定向攻击
"""
# 从POST请求中获取目标重定向地址登录表单中隐藏字段传递
redirect_to = self.request.POST.get(self.redirect_field_name)
# 校验重定向地址是否合法(仅允许本网站域名下的地址)
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[self.request.get_host()]):
redirect_to = self.success_url # 非法地址则使用默认重定向地址(根目录)
return redirect_to
def account_result(request):
"""
账号操作结果展示函数视图
处理两种场景1. 注册成功后的提示 2. 邮箱验证后的激活与提示
核心根据URL参数区分场景验证链接合法性激活用户渲染结果页面
"""
# 从GET请求中获取操作类型register/validation和用户ID
type = request.GET.get('type')
id = request.GET.get('id')
# 根据用户ID查询用户不存在则返回404页面
user = get_object_or_404(get_user_model(), id=id)
logger.info(type) # 记录操作类型到日志
# 若用户已激活,直接重定向到首页(避免重复验证)
if user.is_active:
return HttpResponseRedirect('/')
# 仅处理注册和验证两种合法操作类型
if type and type in ['register', 'validation']:
if type == 'register':
# 场景1注册成功提示告知用户验证邮件已发送
content = '''
恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。
'''
title = '注册成功'
else:
# 场景2邮箱验证校验链接签名合法性激活用户
# 重新计算签名(与注册时的加密规则一致)
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign') # 从URL中获取传递的签名
# 签名不匹配则返回403禁止访问防止恶意篡改链接
if sign != c_sign:
return HttpResponseForbidden()
# 签名匹配激活用户设置is_active为True
user.is_active = True
user.save()
# 验证成功提示
content = '''
恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。
'''
title = '验证成功'
# 渲染结果页面,传递标题和内容
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
# 非法操作类型,重定向到首页
return HttpResponseRedirect('/')
class ForgetPasswordView(FormView):
"""
忘记密码重置视图继承Django FormView
核心功能:接收密码重置表单(新密码、验证码、邮箱)、验证合法性、更新用户密码、重定向到登录页
"""
form_class = ForgetPasswordForm # 关联忘记密码表单(处理密码强度、验证码、邮箱验证)
template_name = 'account/forget_password.html' # 密码重置页面模板路径
def form_valid(self, form):
"""
表单验证通过后的密码重置逻辑
流程:查询用户 → 哈希新密码 → 保存更新 → 重定向到登录页
"""
if form.is_valid():
# 根据表单验证后的邮箱查询用户
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
# 哈希处理新密码make_password自动使用Django配置的哈希算法避免明文存储
blog_user.password = make_password(form.cleaned_data["new_password2"])
blog_user.save() # 保存更新后的密码
return HttpResponseRedirect('/login/') # 密码重置成功,重定向到登录页
else:
# 表单验证失败(如验证码错误、密码不匹配),重新渲染页面并显示错误
return self.render_to_response({'form': form})
class ForgetPasswordEmailCode(View):
"""
忘记密码验证码发送视图继承Django View
核心功能:接收邮箱 → 验证邮箱合法性 → 生成验证码 → 发送验证邮件 → 存储验证码到缓存
"""
def post(self, request: HttpRequest):
"""
处理POST请求接收邮箱发送验证码
"""
# 初始化忘记密码验证码表单,验证请求数据
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱") # 表单验证失败(如邮箱格式错误),返回错误提示
# 表单验证通过,获取目标邮箱
to_email = form.cleaned_data["email"]
# 生成随机验证码(项目工具函数)
code = generate_code()
# 发送验证码邮件调用accounts应用自定义工具函数
utils.send_verify_email(to_email, code)
# 存储验证码到缓存key=邮箱value=验证码有效期5分钟由utils模块的_code_ttl控制
utils.set_code(to_email, code)
return HttpResponse("ok") # 验证码发送成功,返回"ok"提示