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.
Django/doc/accounts/views.py

331 lines
16 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
# 导入Django国际化翻译工具用于视图中文本的多语言支持
from django.utils.translation import gettext_lazy as _
# 导入Django项目配置用于获取SECRET_KEY等全局设置
from django.conf import settings
# 导入Django认证相关模块auth处理登录逻辑REDIRECT_FIELD_NAME定义重定向参数名get_user_model获取自定义用户模型
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 # 登出功能函数
# 导入Django内置认证表单用于登录表单的基础验证
from django.contrib.auth.forms import AuthenticationForm
# 导入密码哈希工具,用于生成加密后的密码(忘记密码功能中更新密码时使用)
from django.contrib.auth.hashers import make_password
# 导入DjangoHTTP响应类重定向、403禁止访问、基础响应
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
# 导入Django快捷函数get_object_or_404获取对象不存在时返回404、render渲染模板
from django.shortcuts import get_object_or_404
from django.shortcuts import render
# 导入reverse函数通过URL名称生成路径避免硬编码路径
from django.urls import reverse
# 导入Django视图装饰器method_decorator为类视图方法添加装饰器、never_cache禁止缓存、csrf_protectCSRF保护、sensitive_post_parameters保护敏感POST参数
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme # 验证重定向地址是否安全
# 导入Django类视图基类View基础视图类、FormView处理表单的视图类、RedirectView处理重定向的视图类
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
# 导入项目自定义工具函数发送邮件、SHA256加密、获取当前站点、生成验证码、删除侧边栏缓存
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
# 导入当前应用accounts的工具函数验证码相关操作
from . import utils
# 导入当前应用的自定义表单:注册、登录、忘记密码、获取忘记密码验证码的表单
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
# 导入当前应用的自定义用户模型
from .models import BlogUser
# 创建日志记录器,用于记录当前视图模块的日志
logger = logging.getLogger(__name__)
# Create your views here.
class RegisterView(FormView):
"""
处理用户注册的类视图继承自FormView专门处理表单提交的视图基类
功能:展示注册表单、验证表单数据、创建未激活用户、发送邮箱验证链接、跳转注册结果页
"""
form_class = RegisterForm # 关联的表单类自定义的RegisterForm
template_name = 'account/registration_form.html' # 渲染的模板文件路径
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
"""
重写dispatch方法为视图添加CSRF保护装饰器
dispatch是类视图的入口方法所有请求GET/POST都会先经过此方法
"""
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
"""
表单数据验证通过后执行的逻辑(用户提交的注册信息合法时)
"""
if form.is_valid():
# 1. 创建用户但不立即提交到数据库commit=False后续手动设置额外字段
user = form.save(False)
# 2. 设置用户初始状态:未激活(需邮箱验证后激活)、注册来源为"Register"
user.is_active = False
user.source = 'Register'
# 3. 提交用户数据到数据库
user.save(True)
# 4. 生成邮箱验证链接包含当前站点域名、验证路径、用户ID、加密签名
site = get_current_site().domain # 获取当前站点域名如www.example.com
# 双重SHA256加密使用SECRET_KEY+用户ID生成签名防止链接被篡改
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境DEBUG=True站点域名替换为本地地址127.0.0.1:8000
if settings.DEBUG:
site = '127.0.0.1:8000'
# 获取验证结果页的路径通过URL名称反向生成
path = reverse('account:result')
# 拼接完整的邮箱验证链接
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 5. 构造验证邮件内容(包含验证链接)
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您!
<br />
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
""".format(url=url)
# 6. 发送验证邮件到用户注册邮箱
send_email(
emailto=[user.email], # 收件人列表
title='验证您的电子邮箱', # 邮件标题
content=content # 邮件HTML内容
)
# 7. 重定向到注册结果页携带注册成功的类型和用户ID参数
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
# 表单验证失败(如用户名已存在、邮箱格式错误),重新渲染表单并显示错误信息
return self.render_to_response({'form': form})
class LogoutView(RedirectView):
"""
处理用户登出的类视图继承自RedirectView专门处理重定向的视图基类
功能:执行登出逻辑、删除侧边栏缓存、重定向到登录页
"""
url = '/login/' # 登出后默认重定向的目标路径(登录页)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写dispatch方法添加禁止缓存装饰器
避免浏览器缓存登出页面,防止用户后退到已登出的页面
"""
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
"""
处理GET请求用户访问登出URL时
"""
# 1. 执行Django内置的登出函数清除用户的session信息
logout(request)
# 2. 删除侧边栏缓存(可能存储了用户相关信息,登出后需更新)
delete_sidebar_cache()
# 3. 调用父类的get方法执行重定向到登录页
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginView(FormView):
"""
处理用户登录的类视图继承自FormView
功能:展示登录表单、验证登录信息、处理"记住我"功能、重定向到目标页面
"""
form_class = LoginForm # 关联的表单类自定义的LoginForm
template_name = 'account/login.html' # 渲染的登录模板路径
success_url = '/' # 登录成功后的默认重定向路径(网站根目录)
redirect_field_name = REDIRECT_FIELD_NAME # 重定向参数名(默认是"next"
login_ttl = 2626560 # 记住登录状态的有效期(秒),约等于一个月
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写dispatch方法添加三个装饰器
1. sensitive_post_parameters('password'):保护密码参数,不在错误日志中显示
2. csrf_protect开启CSRF保护防止跨站请求伪造
3. never_cache禁止缓存登录页面确保每次访问都是最新状态
"""
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
扩展上下文数据将重定向地址next参数传递到模板中
模板中可根据该参数决定登录成功后跳转到哪里
"""
# 从GET请求中获取重定向地址如访问需要登录的页面时会携带next参数
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):
"""
表单数据验证通过后执行的逻辑(用户提交的登录信息合法时)
注意此处重新初始化了AuthenticationForm用于Django内置的登录验证
"""
# 用请求的POST数据和request对象初始化Django内置的认证表单
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
# 1. 登录成功,删除侧边栏缓存(可能存储了未登录状态的内容)
delete_sidebar_cache()
# 2. 记录重定向参数名到日志(用于调试)
logger.info(self.redirect_field_name)
# 3. 执行Django内置的登录函数将用户信息存入session
auth.login(self.request, form.get_user())
# 4. 处理"记住我"功能:如果用户勾选了"remember"设置session有效期
if self.request.POST.get("remember"):
self.request.session.set_expiry(self.login_ttl)
# 5. 调用父类的form_valid方法执行重定向到成功页面
return super(LoginView, self).form_valid(form)
else:
# 表单验证失败(如用户名不存在、密码错误),重新渲染表单并显示错误
return self.render_to_response({'form': form})
def get_success_url(self):
"""
自定义登录成功后的重定向地址
优先使用POST请求中的"next"参数如果合法否则使用默认的success_url
"""
# 从POST请求中获取重定向地址用户登录时提交的next参数
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):
"""
处理账户操作结果的函数视图(注册成功提示、邮箱验证结果)
功能根据URL参数type和id展示不同的结果信息激活用户邮箱
"""
# 从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)
# 如果用户已激活is_active=True直接重定向到根目录无需再展示结果
if user.is_active:
return HttpResponseRedirect('/')
# 如果操作类型合法属于register或validation处理对应的逻辑
if type and type in ['register', 'validation']:
if type == 'register':
# 1. 注册成功场景:展示注册成功提示,告知用户验证邮件已发送
content = '''
恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。
'''
title = '注册成功'
else:
# 2. 邮箱验证场景:验证签名是否合法,合法则激活用户
# 重新生成签名用于与请求中的sign参数对比
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 从GET请求中获取签名参数
sign = request.GET.get('sign')
# 如果签名不匹配链接被篡改返回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):
"""
处理忘记密码的类视图继承自FormView
功能:展示忘记密码表单、验证表单数据(验证码、邮箱、密码)、更新用户密码
"""
form_class = ForgetPasswordForm # 关联的表单类自定义的ForgetPasswordForm
template_name = 'account/forget_password.html' # 渲染的忘记密码模板路径
def form_valid(self, form):
"""
表单数据验证通过后执行的逻辑(验证码、邮箱、密码均合法时)
"""
if form.is_valid():
# 1. 根据表单中的邮箱查询对应的用户
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
# 2. 用新密码(加密处理)更新用户密码
blog_user.password = make_password(form.cleaned_data["new_password2"])
# 3. 保存密码更新结果到数据库
blog_user.save()
# 4. 重定向到登录页(密码重置成功后需重新登录)
return HttpResponseRedirect('/login/')
else:
# 表单验证失败(如验证码错误、密码不一致),重新渲染表单并显示错误
return self.render_to_response({'form': form})
class ForgetPasswordEmailCode(View):
"""
处理发送忘记密码验证码的类视图继承自基础View类
功能:接收邮箱、验证邮箱格式、生成验证码、发送验证邮件、存储验证码到缓存
"""
def post(self, request: HttpRequest):
"""
处理POST请求用户提交邮箱以获取验证码时
"""
# 1. 用请求的POST数据初始化验证码表单
form = ForgetPasswordCodeForm(request.POST)
# 2. 验证表单(主要验证邮箱格式是否合法)
if not form.is_valid():
# 表单验证失败(邮箱格式错误),返回"错误的邮箱"响应
return HttpResponse("错误的邮箱")
# 3. 表单验证通过,获取清洗后的邮箱地址
to_email = form.cleaned_data["email"]
# 4. 生成随机验证码
code = generate_code()
# 5. 发送验证码邮件到用户邮箱调用utils中的send_verify_email函数
utils.send_verify_email(to_email, code)
# 6. 将验证码存储到缓存(关联邮箱,设置有效期)
utils.set_code(to_email, code)
# 7. 发送成功,返回"ok"响应
return HttpResponse("ok")