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

220 lines
8.6 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, HttpResponse
from django.http.request import HttpRequest
from django.shortcuts import get_object_or_404, 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
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
from .models import BlogUser
logger = logging.getLogger(__name__) # 初始化日志记录器
# ------------------------- 注册视图 -------------------------
class RegisterView(FormView):
"""用户注册视图"""
form_class = RegisterForm
template_name = 'account/registration_form.html' # 注册页面模板路径
@method_decorator(csrf_protect) # 防止CSRF攻击
def dispatch(self, *args, **kwargs):
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
"""当表单验证通过时调用"""
if form.is_valid():
user = form.save(False) # 保存用户但不提交数据库
user.is_active = False # 新注册用户默认未激活
user.source = 'Register' # 标记注册来源
user.save(True) # 保存到数据库
site = get_current_site().domain # 获取当前站点域名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) # 生成验证签名
if settings.DEBUG: # 开发环境下替换站点域名
site = '127.0.0.1:8000'
# 生成邮箱验证链接
path = reverse('account:result')
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 邮件内容
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您!
<br />
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
""".format(url=url)
# 发送验证邮件
send_email(
emailto=[user.email],
title='验证您的电子邮箱',
content=content
)
# 注册成功后跳转到结果页面
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
# 验证失败重新渲染表单
return self.render_to_response({'form': form})
# ------------------------- 登出视图 -------------------------
class LogoutView(RedirectView):
"""用户登出视图"""
url = '/login/' # 登出后跳转页面
@method_decorator(never_cache) # 禁止页面缓存
def dispatch(self, request, *args, **kwargs):
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
logout(request) # 注销当前用户
delete_sidebar_cache() # 清理缓存
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 # “记住我”有效期30天
@method_decorator(sensitive_post_parameters('password')) # 标记敏感字段
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""为模板提供重定向参数"""
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):
"""登录逻辑"""
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()) # 登录用户
if self.request.POST.get("remember"): # 如果勾选“记住我”
self.request.session.set_expiry(self.login_ttl)
return super(LoginView, self).form_valid(form)
else:
# 登录失败,重新渲染页面
return self.render_to_response({'form': form})
def get_success_url(self):
"""登录成功后跳转路径校验"""
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):
"""注册或邮箱验证结果页"""
type = request.GET.get('type')
id = request.GET.get('id')
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':
content = '''
恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。
'''
title = '注册成功'
else:
# 验证签名
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign')
if sign != c_sign:
return HttpResponseForbidden() # 签名不符禁止访问
user.is_active = True # 激活账号
user.save()
content = '''
恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。
'''
title = '验证成功'
# 渲染结果模板
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
return HttpResponseRedirect('/')
# ------------------------- 忘记密码视图 -------------------------
class ForgetPasswordView(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()
# 更新加密后的新密码
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):
"""用于发送找回密码邮箱验证码"""
def post(self, request: HttpRequest):
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱") # 邮箱格式错误
to_email = form.cleaned_data["email"]
code = generate_code() # 生成随机验证码
utils.send_verify_email(to_email, code) # 发送验证码邮件
utils.set_code(to_email, code) # 将验证码写入缓存或数据库
return HttpResponse("ok") # 返回成功响应