|
|
|
|
@ -0,0 +1,330 @@
|
|
|
|
|
# 导入日志模块,用于记录视图操作中的关键信息(如请求类型、用户状态等)
|
|
|
|
|
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_protect(CSRF保护)、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")
|