From 5a940dd6f1cbbea68d7c2fd6434cd2e290c11769 Mon Sep 17 00:00:00 2001 From: gzs <3428953782@qq.com> Date: Thu, 6 Nov 2025 01:11:23 +0800 Subject: [PATCH] =?UTF-8?q?yjy=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/admin.py | 18 ++++++++ src/accounts/apps.py | 2 + src/accounts/forms.py | 27 +++++++++++ src/accounts/models.py | 13 ++++++ src/accounts/tests.py | 15 ++++++ src/accounts/urls.py | 48 +++++++++++-------- src/accounts/user_login_backend.py | 28 ++++++++++- src/accounts/utils.py | 49 ++++++++++++++------ src/accounts/views.py | 74 +++++++++++++++++++++++++++++- 9 files changed, 238 insertions(+), 36 deletions(-) diff --git a/src/accounts/admin.py b/src/accounts/admin.py index 29d162a..08b3422 100644 --- a/src/accounts/admin.py +++ b/src/accounts/admin.py @@ -9,14 +9,19 @@ from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): + """后台创建用户表单,用于管理员在后台创建新用户""" + # 密码字段 password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) + # 确认密码字段 password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: model = BlogUser + # 创建用户时只需要填写邮箱 fields = ('email',) def clean_password2(self): + """验证两次输入的密码是否一致""" # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") @@ -25,19 +30,25 @@ class BlogUserCreationForm(forms.ModelForm): return password2 def save(self, commit=True): + """保存用户,使用哈希后的密码""" # Save the provided password in hashed format user = super().save(commit=False) + # 使用 Django 的密码哈希方法设置密码 user.set_password(self.cleaned_data["password1"]) if commit: + # 标记用户来源为后台管理 user.source = 'adminsite' user.save() return user class BlogUserChangeForm(UserChangeForm): + """后台修改用户表单,用于管理员编辑用户信息""" class Meta: model = BlogUser + # 包含所有字段 fields = '__all__' + # 用户名字段使用自定义的 UsernameField field_classes = {'username': UsernameField} def __init__(self, *args, **kwargs): @@ -45,8 +56,12 @@ class BlogUserChangeForm(UserChangeForm): class BlogUserAdmin(UserAdmin): + """博客用户后台管理类,自定义了用户列表显示和搜索功能""" + # 编辑用户时使用的表单 form = BlogUserChangeForm + # 创建用户时使用的表单 add_form = BlogUserCreationForm + # 列表页显示的字段 list_display = ( 'id', 'nickname', @@ -55,6 +70,9 @@ class BlogUserAdmin(UserAdmin): 'last_login', 'date_joined', 'source') + # 可点击跳转的字段 list_display_links = ('id', 'username') + # 列表排序方式(按 ID 降序) ordering = ('-id',) + # 可搜索的字段 search_fields = ('username', 'nickname', 'email') diff --git a/src/accounts/apps.py b/src/accounts/apps.py index 9b3fc5a..2025d5b 100644 --- a/src/accounts/apps.py +++ b/src/accounts/apps.py @@ -2,4 +2,6 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): + """账户应用配置类""" + # 应用名称,用于 Django 识别和加载该应用 name = 'accounts' diff --git a/src/accounts/forms.py b/src/accounts/forms.py index fce4137..bd83331 100644 --- a/src/accounts/forms.py +++ b/src/accounts/forms.py @@ -9,39 +9,54 @@ from .models import BlogUser class LoginForm(AuthenticationForm): + """登录表单,继承自 Django 的 AuthenticationForm,自定义了输入框样式""" def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) + # 设置用户名字段的输入框样式(占位符和 CSS 类) self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) + # 设置密码字段的输入框样式(占位符和 CSS 类) 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( attrs={'placeholder': "email", "class": "form-control"}) + # 设置密码字段的输入框样式 self.fields['password1'].widget = widgets.PasswordInput( attrs={'placeholder': "password", "class": "form-control"}) + # 设置确认密码字段的输入框样式 self.fields['password2'].widget = widgets.PasswordInput( 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")) 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 +67,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 确认密码字段 new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -62,6 +78,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 邮箱字段 email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -72,6 +89,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 验证码字段 code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -83,16 +101,21 @@ class ForgetPasswordForm(forms.Form): ) def clean_new_password2(self): + """验证两次输入的密码是否一致,并使用 Django 的密码验证器检查强度""" 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")) + # 使用 Django 的密码验证器验证密码强度 password_validation.validate_password(password2) return password2 def clean_email(self): + """验证邮箱是否已注册""" user_email = self.cleaned_data.get("email") + # 检查邮箱是否存在于数据库中 if not BlogUser.objects.filter( email=user_email ).exists(): @@ -101,7 +124,9 @@ class ForgetPasswordForm(forms.Form): return user_email def clean_code(self): + """验证邮箱验证码是否正确""" code = self.cleaned_data.get("code") + # 调用工具函数验证验证码 error = utils.verify( email=self.cleaned_data.get("email"), code=code, @@ -112,6 +137,8 @@ class ForgetPasswordForm(forms.Form): class ForgetPasswordCodeForm(forms.Form): + """忘记密码验证码申请表单,用于请求发送验证码""" + # 邮箱字段,用于接收验证码 email = forms.EmailField( label=_('Email'), ) diff --git a/src/accounts/models.py b/src/accounts/models.py index 3baddbb..b83bc85 100644 --- a/src/accounts/models.py +++ b/src/accounts/models.py @@ -9,27 +9,40 @@ from djangoblog.utils import get_current_site # Create your models here. class BlogUser(AbstractUser): + """博客用户模型,继承自 Django 的 AbstractUser,扩展了用户信息字段""" + + # 用户昵称,可选字段,最大长度 100 字符 nickname = models.CharField(_('nick name'), max_length=100, blank=True) + # 账户创建时间,默认为当前时间 creation_time = models.DateTimeField(_('creation time'), default=now) + # 最后修改时间,默认为当前时间 last_modify_time = models.DateTimeField(_('last modify time'), default=now) + # 账户来源标识(如:Register、adminsite、OAuth 等),用于追踪用户注册渠道 source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): + """获取用户详情页的相对 URL""" return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) def __str__(self): + """返回用户的邮箱地址作为字符串表示""" return self.email def get_full_url(self): + """获取用户详情页的完整 URL(包含域名)""" site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: + # 按 ID 降序排列(最新的在前) ordering = ['-id'] + # 单数形式的模型名称(用于后台显示) verbose_name = _('user') + # 复数形式的模型名称(与单数相同) verbose_name_plural = verbose_name + # 指定获取最新记录时使用的字段 get_latest_by = 'id' diff --git a/src/accounts/tests.py b/src/accounts/tests.py index 6893411..d15378c 100644 --- a/src/accounts/tests.py +++ b/src/accounts/tests.py @@ -12,17 +12,25 @@ from . import utils # Create your tests here. class AccountTest(TestCase): + """账户功能测试类,测试用户注册、登录、密码重置等功能""" + def setUp(self): + """测试前的初始化设置,创建测试用的客户端和用户""" + # 创建测试客户端 self.client = Client() + # 创建请求工厂(用于创建测试请求) self.factory = RequestFactory() + # 创建测试用户 self.blog_user = BlogUser.objects.create_user( username="test", email="admin@admin.com", password="12345678" ) + # 测试用的新密码 self.new_test = "xxx123--=" def test_validate_account(self): + """测试账户验证功能:创建超级用户、登录、访问后台等""" site = get_current_site().domain user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", @@ -56,6 +64,7 @@ class AccountTest(TestCase): self.assertEqual(response.status_code, 200) def test_validate_register(self): + """测试用户注册功能:注册流程、邮箱验证、登录等完整流程""" self.assertEquals( 0, len( BlogUser.objects.filter( @@ -119,6 +128,7 @@ class AccountTest(TestCase): self.assertIn(response.status_code, [301, 302, 200]) def test_verify_email_code(self): + """测试邮箱验证码功能:生成、发送、验证验证码""" to_email = "admin@admin.com" code = generate_code() utils.set_code(to_email, code) @@ -131,6 +141,7 @@ class AccountTest(TestCase): self.assertEqual(type(err), str) def test_forget_password_email_code_success(self): + """测试忘记密码验证码申请功能(成功场景)""" resp = self.client.post( path=reverse("account:forget_password_code"), data=dict(email="admin@admin.com") @@ -140,6 +151,7 @@ class AccountTest(TestCase): self.assertEqual(resp.content.decode("utf-8"), "ok") def test_forget_password_email_code_fail(self): + """测试忘记密码验证码申请功能(失败场景:邮箱格式错误或为空)""" resp = self.client.post( path=reverse("account:forget_password_code"), data=dict() @@ -153,6 +165,7 @@ class AccountTest(TestCase): self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") def test_forget_password_email_success(self): + """测试忘记密码重置功能(成功场景:验证码正确)""" code = generate_code() utils.set_code(self.blog_user.email, code) data = dict( @@ -175,6 +188,7 @@ class AccountTest(TestCase): self.assertEqual(blog_user.check_password(data["new_password1"]), True) def test_forget_password_email_not_user(self): + """测试忘记密码重置功能(失败场景:邮箱未注册)""" data = dict( new_password1=self.new_test, new_password2=self.new_test, @@ -190,6 +204,7 @@ class AccountTest(TestCase): def test_forget_password_email_code_error(self): + """测试忘记密码重置功能(失败场景:验证码错误)""" code = generate_code() utils.set_code(self.blog_user.email, code) data = dict( diff --git a/src/accounts/urls.py b/src/accounts/urls.py index 107a801..aba1bc3 100644 --- a/src/accounts/urls.py +++ b/src/accounts/urls.py @@ -4,25 +4,33 @@ from django.urls import re_path from . import views from .forms import LoginForm +# 应用命名空间,用于 URL 反向解析 app_name = "accounts" -urlpatterns = [re_path(r'^login/$', - views.LoginView.as_view(success_url='/'), - name='login', - kwargs={'authentication_form': LoginForm}), - re_path(r'^register/$', - views.RegisterView.as_view(success_url="/"), - name='register'), - re_path(r'^logout/$', - views.LogoutView.as_view(), - name='logout'), - path(r'account/result.html', - views.account_result, - name='result'), - re_path(r'^forget_password/$', - views.ForgetPasswordView.as_view(), - name='forget_password'), - re_path(r'^forget_password_code/$', - views.ForgetPasswordEmailCode.as_view(), - name='forget_password_code'), - ] +urlpatterns = [ + # 登录页面路由 + re_path(r'^login/$', + views.LoginView.as_view(success_url='/'), + name='login', + kwargs={'authentication_form': LoginForm}), + # 注册页面路由 + re_path(r'^register/$', + views.RegisterView.as_view(success_url="/"), + name='register'), + # 登出页面路由 + re_path(r'^logout/$', + views.LogoutView.as_view(), + name='logout'), + # 账户操作结果页面路由(如注册成功、邮箱验证等) + path(r'account/result.html', + views.account_result, + name='result'), + # 忘记密码页面路由 + re_path(r'^forget_password/$', + views.ForgetPasswordView.as_view(), + name='forget_password'), + # 忘记密码验证码申请路由 + re_path(r'^forget_password_code/$', + views.ForgetPasswordEmailCode.as_view(), + name='forget_password_code'), +] diff --git a/src/accounts/user_login_backend.py b/src/accounts/user_login_backend.py index 73cdca1..15c97f1 100644 --- a/src/accounts/user_login_backend.py +++ b/src/accounts/user_login_backend.py @@ -4,22 +4,48 @@ from django.contrib.auth.backends import ModelBackend class EmailOrUsernameModelBackend(ModelBackend): """ - 允许使用用户名或邮箱登录 + 自定义认证后端,允许使用用户名或邮箱登录 + 继承自 ModelBackend,扩展了登录方式 """ def authenticate(self, request, username=None, password=None, **kwargs): + """ + 认证用户,支持用户名或邮箱登录 + + Args: + request: HTTP 请求对象 + username: 用户名或邮箱 + password: 密码 + **kwargs: 其他参数 + + Returns: + 认证成功返回用户对象,失败返回 None + """ + # 判断输入的是邮箱还是用户名(通过是否包含 @ 符号) if '@' in username: kwargs = {'email': username} else: kwargs = {'username': username} try: + # 根据用户名或邮箱查找用户 user = get_user_model().objects.get(**kwargs) + # 验证密码是否正确 if user.check_password(password): return user except get_user_model().DoesNotExist: + # 用户不存在时返回 None return None def get_user(self, username): + """ + 根据用户 ID 获取用户对象 + + Args: + username: 用户 ID(注意:这里参数名是 username,但实际是用户 ID) + + Returns: + 用户对象,不存在时返回 None + """ try: return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: diff --git a/src/accounts/utils.py b/src/accounts/utils.py index 4b94bdf..ba46a6d 100644 --- a/src/accounts/utils.py +++ b/src/accounts/utils.py @@ -7,43 +7,66 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import send_email +# 验证码的有效期(5 分钟) _code_ttl = timedelta(minutes=5) def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 + """发送重设密码验证码邮件 + Args: - to_mail: 接受邮箱 - subject: 邮件主题 + to_mail: 接收邮箱地址 + subject: 邮件主题,默认为"验证邮箱" code: 验证码 """ + # 构造邮件内容,包含验证码和有效期提示 html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " "properly") % {'code': code} + # 发送邮件 send_email([to_mail], subject, html_content) def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 + """验证邮箱验证码是否有效 + Args: - email: 请求邮箱 - code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + email: 请求邮箱地址 + code: 用户输入的验证码 + + Returns: + 如果验证码错误返回错误信息字符串,验证成功返回 None + + Note: + 这里的错误处理不太合理,应该采用 raise 抛出异常 + 否则调用方也需要对 error 进行处理 """ + # 从缓存中获取该邮箱对应的验证码 cache_code = get_code(email) + # 比较缓存中的验证码和用户输入的验证码 if cache_code != code: return gettext("Verification code error") + # 验证成功返回 None def set_code(email: str, code: str): - """设置code""" + """将验证码存储到缓存中 + + Args: + email: 邮箱地址(作为缓存键) + code: 验证码(作为缓存值) + """ + # 使用邮箱作为键,验证码作为值,设置过期时间为 _code_ttl 秒 cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: - """获取code""" + """从缓存中获取验证码 + + Args: + email: 邮箱地址(作为缓存键) + + Returns: + 验证码字符串,如果不存在或已过期返回 None + """ return cache.get(email) diff --git a/src/accounts/views.py b/src/accounts/views.py index ae67aec..1dad6be 100644 --- a/src/accounts/views.py +++ b/src/accounts/views.py @@ -32,28 +32,42 @@ logger = logging.getLogger(__name__) # Create your views here. 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(): + # 保存用户但不提交到数据库(commit=False) user = form.save(False) + # 设置用户为非激活状态,需要邮箱验证后才能激活 user.is_active = False + # 标记用户来源为注册 user.source = 'Register' + # 保存用户到数据库 user.save(True) + # 获取当前站点域名 site = get_current_site().domain + # 生成邮箱验证的签名(双重 SHA256 哈希,使用 SECRET_KEY + 用户 ID) 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 = """
请点击下面链接验证您的邮箱
@@ -64,6 +78,7 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + # 发送验证邮件 send_email( emailto=[ user.email, @@ -71,43 +86,60 @@ class RegisterView(FormView): 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 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): + """处理 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' + # 登录成功后的默认重定向 URL success_url = '/' + # 重定向字段名称(用于记录登录前要访问的页面) redirect_field_name = REDIRECT_FIELD_NAME + # 登录会话有效期(一个月的时间,单位:秒) login_ttl = 2626560 # 一个月的时间 @method_decorator(sensitive_post_parameters('password')) @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - + """分发请求,添加安全装饰器:密码敏感参数保护、CSRF 保护、禁用缓存""" return super(LoginView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): + """获取模板上下文数据,包含重定向 URL""" + # 从 GET 参数中获取重定向目标 URL redirect_to = self.request.GET.get(self.redirect_field_name) if redirect_to is None: redirect_to = '/' @@ -116,25 +148,33 @@ class LoginView(FormView): 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) # return HttpResponseRedirect('/') else: + # 表单验证失败,重新渲染登录页面 return self.render_to_response({ 'form': form }) def get_success_url(self): - + """获取登录成功后的重定向 URL,检查安全性""" + # 从 POST 数据中获取重定向目标 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()]): @@ -143,62 +183,92 @@ class LoginView(FormView): def account_result(request): + """账户操作结果页面视图,显示注册成功或邮箱验证结果""" + # 获取操作类型(register 或 validation) type = request.GET.get('type') + # 获取用户 ID 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"] + # 生成 6 位随机验证码 code = generate_code() + # 发送验证码邮件 utils.send_verify_email(to_email, code) + # 将验证码存储到缓存中(有效期 5 分钟) utils.set_code(to_email, code) return HttpResponse("ok")