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 = """

请点击下面链接验证您的邮箱

{url} 再次感谢您!
如果上面链接无法打开,请将此链接复制至浏览器。 {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") # 返回成功响应