diff --git a/view.py b/view.py index e69de29..9933d75 100644 --- a/view.py +++ b/view.py @@ -0,0 +1,283 @@ +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 +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from django.shortcuts import get_object_or_404 +from django.shortcuts import 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) + def dispatch(self, *args, **kwargs): + """确保视图受到CSRF保护""" + 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' + + # 构建验证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) + + # 构建邮件内容 + 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/' # 登出后重定向的URL + + @method_decorator(never_cache) + def dispatch(self, request, *args, **kwargs): + """确保登出页面不被缓存""" + return super(LogoutView, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + """处理GET请求的登出操作""" + 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 = '/' # 登录成功后默认重定向的URL + redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名 + login_ttl = 2626560 # 一个月的时间(秒),用于"记住我"功能 + + @method_decorator(sensitive_post_parameters('password')) # 保护密码参数 + @method_decorator(csrf_protect) # CSRF保护 + @method_decorator(never_cache) # 禁止缓存 + def dispatch(self, request, *args, **kwargs): + """应用装饰器到视图分发方法""" + return super(LoginView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + """向模板上下文添加重定向URL""" + 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): + """处理有效的登录表单""" + # 使用Django的AuthenticationForm进行认证 + 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): + """获取登录成功后重定向的URL""" + redirect_to = self.request.POST.get(self.redirect_field_name) + + # 验证重定向URL的安全性 + if not url_has_allowed_host_and_scheme( + url=redirect_to, allowed_hosts=[ + self.request.get_host()]): + redirect_to = self.success_url # 不安全的URL使用默认URL + + return redirect_to + + +def account_result(request): + """ + 账户操作结果页面 + 处理注册结果和邮箱验证 + """ + type = request.GET.get('type') # 操作类型:register或validation + id = request.GET.get('id') # 用户ID + + # 获取用户对象,如果不存在返回404 + 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() + # 使用Django的密码哈希器设置新密码 + 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): + """处理POST请求,发送密码重置验证码""" + 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") # 返回成功响应 \ No newline at end of file