From 5854b4eab4b31c99e76c24e39b570415a657fe86 Mon Sep 17 00:00:00 2001 From: lunhun <2077786863@qq.com> Date: Sun, 12 Oct 2025 23:18:12 +0800 Subject: [PATCH] =?UTF-8?q?account:=20=E8=A1=A5=E5=85=85=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/forms.py | 39 +++++++++--- accounts/views.py | 155 +++++++++++++++++++++++++--------------------- 2 files changed, 114 insertions(+), 80 deletions(-) diff --git a/accounts/forms.py b/accounts/forms.py index fce4137..39c9aa2 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -8,19 +8,26 @@ from . import utils from .models import BlogUser +# ------------------------- 登录表单 ------------------------- class LoginForm(AuthenticationForm): + """登录表单类(继承 Django 内置 AuthenticationForm)""" + def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) + # 自定义输入框样式 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['password'].widget = widgets.PasswordInput( attrs={'placeholder': "password", "class": "form-control"}) +# ------------------------- 注册表单 ------------------------- class RegisterForm(UserCreationForm): + """用户注册表单(继承 Django 自带 UserCreationForm)""" + def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) - + # 为每个字段自定义前端样式与提示文本 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['email'].widget = widgets.EmailInput( @@ -31,17 +38,23 @@ class RegisterForm(UserCreationForm): attrs={'placeholder': "repeat password", "class": "form-control"}) def clean_email(self): + """检查邮箱是否已被注册""" email = self.cleaned_data['email'] if get_user_model().objects.filter(email=email).exists(): - raise ValidationError(_("email already exists")) + raise ValidationError(_("email already exists")) # 抛出重复邮箱异常 return email class Meta: + """指定绑定的模型和表单字段""" model = get_user_model() fields = ("username", "email") +# ------------------------- 忘记密码表单 ------------------------- class ForgetPasswordForm(forms.Form): + """用户通过邮箱重置密码的表单""" + + # 新密码输入 new_password1 = forms.CharField( label=_("New password"), widget=forms.PasswordInput( @@ -52,6 +65,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 新密码确认输入 new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -62,6 +76,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 邮箱输入 email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -72,6 +87,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 验证码输入 code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -82,36 +98,39 @@ class ForgetPasswordForm(forms.Form): ), ) + # ----------- 字段验证逻辑 ----------- def clean_new_password2(self): + """验证两次输入的新密码是否一致,并校验密码强度""" password1 = self.data.get("new_password1") password2 = self.data.get("new_password2") if password1 and password2 and password1 != password2: - raise ValidationError(_("passwords do not match")) - password_validation.validate_password(password2) - + raise ValidationError(_("passwords do not match")) # 两次密码不一致 + password_validation.validate_password(password2) # 使用Django内置规则验证密码强度 return password2 def clean_email(self): + """验证邮箱是否存在于数据库""" user_email = self.cleaned_data.get("email") - if not BlogUser.objects.filter( - email=user_email - ).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 + if not BlogUser.objects.filter(email=user_email).exists(): + # 这里可以根据项目安全策略决定是否提示“邮箱不存在” raise ValidationError(_("email does not exist")) return user_email def clean_code(self): + """验证邮箱验证码是否正确或过期""" code = self.cleaned_data.get("code") error = utils.verify( email=self.cleaned_data.get("email"), code=code, ) - if error: + if error: # utils.verify 返回错误信息时表示验证失败 raise ValidationError(error) return code +# ------------------------- 验证码邮箱发送表单 ------------------------- class ForgetPasswordCodeForm(forms.Form): + """发送邮箱验证码表单,仅校验邮箱格式""" email = forms.EmailField( label=_('Email'), ) diff --git a/accounts/views.py b/accounts/views.py index ae67aec..390e1ee 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -7,11 +7,9 @@ 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 import HttpResponseRedirect, HttpResponseForbidden, HttpResponse 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.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 @@ -26,147 +24,159 @@ from . import utils from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm from .models import BlogUser -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) # 初始化日志记录器 -# Create your views here. - +# ------------------------- 注册视图 ------------------------- class RegisterView(FormView): + """用户注册视图""" form_class = RegisterForm - template_name = 'account/registration_form.html' + template_name = 'account/registration_form.html' # 注册页面模板路径 - @method_decorator(csrf_protect) + @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: + 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) +

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

+ {url} + 再次感谢您! +
+ 如果上面链接无法打开,请将此链接复制至浏览器。 + {url} + """.format(url=url) + + # 发送验证邮件 send_email( - emailto=[ - user.email, - ], + emailto=[user.email], title='验证您的电子邮箱', - content=content) + content=content + ) - url = reverse('accounts:result') + \ - '?type=register&id=' + str(user.id) + # 注册成功后跳转到结果页面 + url = reverse('accounts:result') + '?type=register&id=' + str(user.id) return HttpResponseRedirect(url) else: - return self.render_to_response({ - 'form': form - }) + # 验证失败重新渲染表单 + return self.render_to_response({'form': form}) +# ------------------------- 登出视图 ------------------------- class LogoutView(RedirectView): - url = '/login/' + """用户登出视图""" + url = '/login/' # 登出后跳转页面 - @method_decorator(never_cache) + @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() + 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 = '/' + success_url = '/' # 登录成功默认跳转首页 redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 + login_ttl = 2626560 # “记住我”有效期(30天) - @method_decorator(sensitive_post_parameters('password')) + @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() + delete_sidebar_cache() # 清除缓存 logger.info(self.redirect_field_name) - auth.login(self.request, form.get_user()) - if self.request.POST.get("remember"): + 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) - # return HttpResponseRedirect('/') else: - return self.render_to_response({ - 'form': form - }) + # 登录失败,重新渲染页面 + 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()]): + 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) + user = get_object_or_404(get_user_model(), id=id) # 获取用户对象 logger.info(type) - if user.is_active: + + 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 + return HttpResponseForbidden() # 签名不符禁止访问 + + user.is_active = True # 激活账号 user.save() content = ''' - 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 + 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 ''' title = '验证成功' + + # 渲染结果模板 return render(request, 'account/result.html', { 'title': title, 'content': content @@ -175,30 +185,35 @@ def account_result(request): 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/') + 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"] + return HttpResponse("错误的邮箱") # 邮箱格式错误 - code = generate_code() - utils.send_verify_email(to_email, code) - utils.set_code(to_email, code) - - return HttpResponse("ok") + 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") # 返回成功响应