|
|
|
|
@ -1,3 +1,14 @@
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""
|
|
|
|
|
# 模块级注释(app: accounts)
|
|
|
|
|
作者:djq
|
|
|
|
|
功能:用户账户相关视图逻辑,包含用户注册、登录、登出、密码找回及邮箱验证等功能
|
|
|
|
|
关联:
|
|
|
|
|
- 表单:RegisterForm(注册表单)、LoginForm(登录表单)等
|
|
|
|
|
- 模型:BlogUser(自定义用户模型)
|
|
|
|
|
- 模板:account/registration_form.html(注册页)、account/login.html(登录页)等
|
|
|
|
|
- 工具函数:djangoblog.utils中的邮件发送、加密等工具
|
|
|
|
|
"""
|
|
|
|
|
import logging
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
@ -32,28 +43,55 @@ logger = logging.getLogger(__name__)
|
|
|
|
|
# Create your views here.
|
|
|
|
|
|
|
|
|
|
class RegisterView(FormView):
|
|
|
|
|
form_class = RegisterForm
|
|
|
|
|
template_name = 'account/registration_form.html'
|
|
|
|
|
"""
|
|
|
|
|
功能:处理用户注册逻辑,包括表单验证、创建未激活用户、发送邮箱验证链接
|
|
|
|
|
继承:FormView(Django表单处理基类)
|
|
|
|
|
核心流程:
|
|
|
|
|
1. 验证注册表单数据
|
|
|
|
|
2. 创建用户并设置为未激活状态
|
|
|
|
|
3. 生成邮箱验证链接(包含用户ID和加密签名)
|
|
|
|
|
4. 发送验证邮件到用户邮箱
|
|
|
|
|
5. 重定向到注册结果页
|
|
|
|
|
"""
|
|
|
|
|
form_class = RegisterForm # 关联注册表单类
|
|
|
|
|
template_name = 'account/registration_form.html' # 注册页面模板
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_protect)
|
|
|
|
|
def dispatch(self, *args, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
功能:重写dispatch方法,添加CSRF保护装饰器(防止跨站请求伪造)
|
|
|
|
|
参数:*args, **kwargs:视图函数的位置参数和关键字参数
|
|
|
|
|
返回:父类dispatch方法的处理结果
|
|
|
|
|
"""
|
|
|
|
|
return super(RegisterView, self).dispatch(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
"""
|
|
|
|
|
功能:当表单验证通过后执行的逻辑(核心注册流程)
|
|
|
|
|
参数:form:验证通过的注册表单实例
|
|
|
|
|
返回:重定向到注册结果页的响应
|
|
|
|
|
"""
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
# djq: 创建用户但不立即保存(先设置额外属性)
|
|
|
|
|
user = form.save(False)
|
|
|
|
|
user.is_active = False
|
|
|
|
|
user.source = 'Register'
|
|
|
|
|
user.save(True)
|
|
|
|
|
user.is_active = False # djq: 新用户默认未激活(需邮箱验证)
|
|
|
|
|
user.source = 'Register' # djq: 标记用户来源为自主注册
|
|
|
|
|
user.save(True) # djq: 保存用户到数据库
|
|
|
|
|
|
|
|
|
|
# djq: 获取当前站点域名(用于构建验证链接)
|
|
|
|
|
site = get_current_site().domain
|
|
|
|
|
# djq: 生成加密签名(结合SECRET_KEY和用户ID,防止链接被篡改)
|
|
|
|
|
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
|
|
|
|
|
|
|
|
|
|
# djq: 开发环境下使用本地域名
|
|
|
|
|
if settings.DEBUG:
|
|
|
|
|
site = '127.0.0.1:8000'
|
|
|
|
|
path = reverse('account:result')
|
|
|
|
|
path = reverse('account:result') # djq: 验证结果页的路由
|
|
|
|
|
# djq: 拼接邮箱验证链接(包含用户ID和签名)
|
|
|
|
|
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
|
|
|
|
|
site=site, path=path, id=user.id, sign=sign)
|
|
|
|
|
|
|
|
|
|
# djq: 验证邮件内容(包含验证链接)
|
|
|
|
|
content = """
|
|
|
|
|
<p>请点击下面链接验证您的邮箱</p>
|
|
|
|
|
|
|
|
|
|
@ -64,6 +102,7 @@ class RegisterView(FormView):
|
|
|
|
|
如果上面链接无法打开,请将此链接复制至浏览器。
|
|
|
|
|
{url}
|
|
|
|
|
""".format(url=url)
|
|
|
|
|
# djq: 发送验证邮件到用户注册邮箱
|
|
|
|
|
send_email(
|
|
|
|
|
emailto=[
|
|
|
|
|
user.email,
|
|
|
|
|
@ -71,134 +110,234 @@ class RegisterView(FormView):
|
|
|
|
|
title='验证您的电子邮箱',
|
|
|
|
|
content=content)
|
|
|
|
|
|
|
|
|
|
# djq: 重定向到注册结果页(提示用户查收验证邮件)
|
|
|
|
|
url = reverse('accounts:result') + \
|
|
|
|
|
'?type=register&id=' + str(user.id)
|
|
|
|
|
return HttpResponseRedirect(url)
|
|
|
|
|
else:
|
|
|
|
|
# djq: 表单验证失败时,返回原页面并显示错误
|
|
|
|
|
return self.render_to_response({
|
|
|
|
|
'form': form
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LogoutView(RedirectView):
|
|
|
|
|
url = '/login/'
|
|
|
|
|
"""
|
|
|
|
|
功能:处理用户登出逻辑,清除会话并跳转至登录页
|
|
|
|
|
继承:RedirectView(Django重定向基类)
|
|
|
|
|
"""
|
|
|
|
|
url = '/login/' # 登出后跳转的目标URL(登录页)
|
|
|
|
|
|
|
|
|
|
@method_decorator(never_cache)
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
功能:重写dispatch方法,添加禁止缓存装饰器(确保每次登出请求都是最新的)
|
|
|
|
|
参数:*args, **kwargs:视图函数的参数
|
|
|
|
|
返回:父类dispatch方法的处理结果
|
|
|
|
|
"""
|
|
|
|
|
return super(LogoutView, self).dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
|
logout(request)
|
|
|
|
|
delete_sidebar_cache()
|
|
|
|
|
"""
|
|
|
|
|
功能:处理GET请求,执行登出操作
|
|
|
|
|
参数:request:HTTP请求对象
|
|
|
|
|
返回:重定向到登录页的响应
|
|
|
|
|
"""
|
|
|
|
|
logout(request) # djq: 清除用户会话,完成登出
|
|
|
|
|
delete_sidebar_cache() # djq: 清除侧边栏缓存(可能包含用户相关信息)
|
|
|
|
|
return super(LogoutView, self).get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginView(FormView):
|
|
|
|
|
form_class = LoginForm
|
|
|
|
|
template_name = 'account/login.html'
|
|
|
|
|
success_url = '/'
|
|
|
|
|
redirect_field_name = REDIRECT_FIELD_NAME
|
|
|
|
|
login_ttl = 2626560 # 一个月的时间
|
|
|
|
|
"""
|
|
|
|
|
功能:处理用户登录逻辑,包括表单验证、用户认证、会话管理
|
|
|
|
|
继承:FormView(Django表单处理基类)
|
|
|
|
|
核心流程:
|
|
|
|
|
1. 验证登录表单(用户名/邮箱+密码)
|
|
|
|
|
2. 认证用户身份
|
|
|
|
|
3. 根据"记住我"选项设置会话过期时间
|
|
|
|
|
4. 重定向到登录前的页面或首页
|
|
|
|
|
"""
|
|
|
|
|
form_class = LoginForm # 关联登录表单类
|
|
|
|
|
template_name = 'account/login.html' # 登录页面模板
|
|
|
|
|
success_url = '/' # 登录成功默认跳转页(首页)
|
|
|
|
|
redirect_field_name = REDIRECT_FIELD_NAME # 存储登录前URL的参数名
|
|
|
|
|
login_ttl = 2626560 # djq: 会话过期时间(单位:秒),此处为一个月
|
|
|
|
|
|
|
|
|
|
@method_decorator(sensitive_post_parameters('password'))
|
|
|
|
|
@method_decorator(csrf_protect)
|
|
|
|
|
@method_decorator(never_cache)
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
功能:重写dispatch方法,添加多重保护装饰器
|
|
|
|
|
- sensitive_post_parameters:标记密码字段为敏感信息(日志中隐藏)
|
|
|
|
|
- csrf_protect:CSRF保护
|
|
|
|
|
- never_cache:禁止缓存
|
|
|
|
|
参数:*args, **kwargs:视图函数的参数
|
|
|
|
|
返回:父类dispatch方法的处理结果
|
|
|
|
|
"""
|
|
|
|
|
return super(LoginView, self).dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
def get_context_data(self,** kwargs):
|
|
|
|
|
"""
|
|
|
|
|
功能:向模板传递额外上下文数据(登录前的跳转URL)
|
|
|
|
|
参数:**kwargs:上下文关键字参数
|
|
|
|
|
返回:包含跳转URL的上下文字典
|
|
|
|
|
"""
|
|
|
|
|
# djq: 获取登录前的页面URL(从请求参数中提取)
|
|
|
|
|
redirect_to = self.request.GET.get(self.redirect_field_name)
|
|
|
|
|
if redirect_to is None:
|
|
|
|
|
redirect_to = '/'
|
|
|
|
|
kwargs['redirect_to'] = redirect_to
|
|
|
|
|
redirect_to = '/' # djq: 默认跳转至首页
|
|
|
|
|
kwargs['redirect_to'] = redirect_to # djq: 将跳转URL添加到上下文
|
|
|
|
|
|
|
|
|
|
return super(LoginView, self).get_context_data(**kwargs)
|
|
|
|
|
return super(LoginView, self).get_context_data(** kwargs)
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
"""
|
|
|
|
|
功能:表单验证通过后执行的登录逻辑
|
|
|
|
|
参数:form:验证通过的登录表单实例
|
|
|
|
|
返回:重定向到目标页面的响应
|
|
|
|
|
"""
|
|
|
|
|
# djq: 使用Django内置认证表单再次验证(兼容用户名/邮箱登录)
|
|
|
|
|
form = AuthenticationForm(data=self.request.POST, request=self.request)
|
|
|
|
|
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
delete_sidebar_cache()
|
|
|
|
|
logger.info(self.redirect_field_name)
|
|
|
|
|
delete_sidebar_cache() # djq: 清除侧边栏缓存(更新用户登录状态)
|
|
|
|
|
logger.info(self.redirect_field_name) # djq: 记录跳转参数名到日志
|
|
|
|
|
|
|
|
|
|
# djq: 执行登录(将用户信息存入会话)
|
|
|
|
|
auth.login(self.request, form.get_user())
|
|
|
|
|
# djq: 如果勾选"记住我",设置会话过期时间为一个月
|
|
|
|
|
if self.request.POST.get("remember"):
|
|
|
|
|
self.request.session.set_expiry(self.login_ttl)
|
|
|
|
|
return super(LoginView, self).form_valid(form)
|
|
|
|
|
# return HttpResponseRedirect('/')
|
|
|
|
|
else:
|
|
|
|
|
# djq: 表单验证失败(如密码错误),返回原页面显示错误
|
|
|
|
|
return self.render_to_response({
|
|
|
|
|
'form': form
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
功能:确定登录成功后的跳转URL(优先跳转到登录前的页面)
|
|
|
|
|
返回:安全的跳转URL
|
|
|
|
|
"""
|
|
|
|
|
# djq: 从POST参数中获取登录前的URL
|
|
|
|
|
redirect_to = self.request.POST.get(self.redirect_field_name)
|
|
|
|
|
# djq: 验证跳转URL是否安全(防止跳转到外部恶意网站)
|
|
|
|
|
if not url_has_allowed_host_and_scheme(
|
|
|
|
|
url=redirect_to, allowed_hosts=[
|
|
|
|
|
self.request.get_host()]):
|
|
|
|
|
redirect_to = self.success_url
|
|
|
|
|
redirect_to = self.success_url # djq: 不安全则使用默认首页
|
|
|
|
|
return redirect_to
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def account_result(request):
|
|
|
|
|
type = request.GET.get('type')
|
|
|
|
|
id = request.GET.get('id')
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
功能:处理注册结果和邮箱验证结果的展示
|
|
|
|
|
参数:request:HTTP请求对象
|
|
|
|
|
返回:渲染结果页面的响应
|
|
|
|
|
逻辑:
|
|
|
|
|
1. 区分"注册成功"和"邮箱验证成功"两种场景
|
|
|
|
|
2. 验证场景合法性(如验证链接的签名是否有效)
|
|
|
|
|
3. 展示对应结果信息
|
|
|
|
|
"""
|
|
|
|
|
type = request.GET.get('type') # djq: 获取场景类型(register/validation)
|
|
|
|
|
id = request.GET.get('id') # djq: 获取用户ID
|
|
|
|
|
|
|
|
|
|
# djq: 获取对应的用户(不存在则返回404)
|
|
|
|
|
user = get_object_or_404(get_user_model(), id=id)
|
|
|
|
|
logger.info(type)
|
|
|
|
|
logger.info(type) # djq: 记录场景类型到日志
|
|
|
|
|
|
|
|
|
|
# djq: 如果用户已激活,直接跳转至首页(避免重复验证)
|
|
|
|
|
if user.is_active:
|
|
|
|
|
return HttpResponseRedirect('/')
|
|
|
|
|
|
|
|
|
|
if type and type in ['register', 'validation']:
|
|
|
|
|
if type == 'register':
|
|
|
|
|
# djq: 注册成功场景:提示用户查收验证邮件
|
|
|
|
|
content = '''
|
|
|
|
|
恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。
|
|
|
|
|
'''
|
|
|
|
|
title = '注册成功'
|
|
|
|
|
else:
|
|
|
|
|
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
|
|
|
|
|
sign = request.GET.get('sign')
|
|
|
|
|
# djq: 邮箱验证场景:验证签名合法性
|
|
|
|
|
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) # 计算正确签名
|
|
|
|
|
sign = request.GET.get('sign') # 获取请求中的签名
|
|
|
|
|
if sign != c_sign:
|
|
|
|
|
return HttpResponseForbidden()
|
|
|
|
|
return HttpResponseForbidden() # djq: 签名不匹配,返回403禁止访问
|
|
|
|
|
|
|
|
|
|
# djq: 签名验证通过,激活用户
|
|
|
|
|
user.is_active = True
|
|
|
|
|
user.save()
|
|
|
|
|
# djq: 提示用户验证成功
|
|
|
|
|
content = '''
|
|
|
|
|
恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。
|
|
|
|
|
'''
|
|
|
|
|
title = '验证成功'
|
|
|
|
|
# djq: 渲染结果页面
|
|
|
|
|
return render(request, 'account/result.html', {
|
|
|
|
|
'title': title,
|
|
|
|
|
'content': content
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
# djq: 场景类型不合法,跳转至首页
|
|
|
|
|
return HttpResponseRedirect('/')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ForgetPasswordView(FormView):
|
|
|
|
|
form_class = ForgetPasswordForm
|
|
|
|
|
template_name = 'account/forget_password.html'
|
|
|
|
|
"""
|
|
|
|
|
功能:处理密码找回逻辑(通过邮箱验证后重置密码)
|
|
|
|
|
继承:FormView(Django表单处理基类)
|
|
|
|
|
"""
|
|
|
|
|
form_class = ForgetPasswordForm # 关联密码找回表单
|
|
|
|
|
template_name = 'account/forget_password.html' # 密码找回页面模板
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
"""
|
|
|
|
|
功能:表单验证通过后执行的密码重置逻辑
|
|
|
|
|
参数:form:验证通过的密码找回表单实例
|
|
|
|
|
返回:重定向到登录页的响应
|
|
|
|
|
"""
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
# djq: 根据邮箱获取用户(假设表单已验证邮箱存在)
|
|
|
|
|
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
|
|
|
|
|
# djq: 加密新密码并保存(make_password自动处理哈希)
|
|
|
|
|
blog_user.password = make_password(form.cleaned_data["new_password2"])
|
|
|
|
|
blog_user.save()
|
|
|
|
|
# djq: 密码重置成功,跳转至登录页
|
|
|
|
|
return HttpResponseRedirect('/login/')
|
|
|
|
|
else:
|
|
|
|
|
# djq: 表单验证失败(如密码不一致),返回原页面
|
|
|
|
|
return self.render_to_response({'form': form})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ForgetPasswordEmailCode(View):
|
|
|
|
|
"""
|
|
|
|
|
功能:处理密码找回时的邮箱验证码发送逻辑
|
|
|
|
|
继承:View(Django基础视图类)
|
|
|
|
|
核心流程:
|
|
|
|
|
1. 验证邮箱格式
|
|
|
|
|
2. 生成随机验证码
|
|
|
|
|
3. 发送验证码到目标邮箱
|
|
|
|
|
4. 存储验证码(用于后续验证)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def post(self, request: HttpRequest):
|
|
|
|
|
"""
|
|
|
|
|
功能:处理POST请求,发送密码找回验证码
|
|
|
|
|
参数:request:HTTP请求对象(包含邮箱参数)
|
|
|
|
|
返回:"ok"字符串(成功)或错误提示
|
|
|
|
|
"""
|
|
|
|
|
# djq: 验证请求中的邮箱格式
|
|
|
|
|
form = ForgetPasswordCodeForm(request.POST)
|
|
|
|
|
if not form.is_valid():
|
|
|
|
|
return HttpResponse("错误的邮箱")
|
|
|
|
|
to_email = form.cleaned_data["email"]
|
|
|
|
|
return HttpResponse("错误的邮箱") # djq: 邮箱格式错误,返回提示
|
|
|
|
|
|
|
|
|
|
to_email = form.cleaned_data["email"] # djq: 获取验证通过的邮箱
|
|
|
|
|
|
|
|
|
|
code = generate_code()
|
|
|
|
|
utils.send_verify_email(to_email, code)
|
|
|
|
|
utils.set_code(to_email, code)
|
|
|
|
|
code = generate_code() # djq: 生成随机验证码
|
|
|
|
|
utils.send_verify_email(to_email, code) # djq: 发送验证码到邮箱
|
|
|
|
|
utils.set_code(to_email, code) # djq: 存储验证码(如存入缓存,用于后续校验)
|
|
|
|
|
|
|
|
|
|
return HttpResponse("ok")
|
|
|
|
|
return HttpResponse("ok") # djq: 发送成功,返回标识
|