From 3d17196f45792a799d0ed68aabe19077cfe741e9 Mon Sep 17 00:00:00 2001 From: jyn <3080239289@qq.com> Date: Sun, 26 Oct 2025 00:13:45 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/第六周鞠泆男用例说明.docx | Bin 0 -> 16266 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/第六周鞠泆男用例说明.docx diff --git a/doc/第六周鞠泆男用例说明.docx b/doc/第六周鞠泆男用例说明.docx new file mode 100644 index 0000000000000000000000000000000000000000..ffac333411f6964e71dfe7d11598d2c77e22767c GIT binary patch literal 16266 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 Date: Fri, 7 Nov 2025 16:51:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/django-master/accounts/admin.py | 34 ++-- src/django-master/accounts/apps.py | 4 +- src/django-master/accounts/forms.py | 42 ++-- src/django-master/accounts/models.py | 34 ++-- src/django-master/accounts/tests.py | 136 ++++++------- src/django-master/accounts/urls.py | 48 ++--- .../accounts/user_login_backend.py | 22 +-- src/django-master/accounts/utils.py | 24 +-- src/django-master/accounts/views.py | 182 +++++++++--------- 9 files changed, 263 insertions(+), 263 deletions(-) diff --git a/src/django-master/accounts/admin.py b/src/django-master/accounts/admin.py index bc63321..bd5ed47 100644 --- a/src/django-master/accounts/admin.py +++ b/src/django-master/accounts/admin.py @@ -4,7 +4,7 @@ from django.contrib.auth.forms import UserChangeForm from django.contrib.auth.forms import UsernameField from django.utils.translation import gettext_lazy as _ -# 导入自定义用户模型 +# jyn:导入自定义用户模型 from .models import BlogUser @@ -13,15 +13,15 @@ class BlogUserCreationForm(forms.ModelForm): 自定义用户创建表单,用于在管理员界面添加新用户 继承自ModelForm,提供密码验证功能 """ - # 密码字段,使用PasswordInput小部件确保输入不可见 + # jyn:密码字段,使用PasswordInput小部件确保输入不可见 password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) - # 确认密码字段,用于验证两次输入的密码是否一致 + # jyn:确认密码字段,用于验证两次输入的密码是否一致 password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: - # 指定关联的模型 + # jyn:指定关联的模型 model = BlogUser - # 表单中包含的字段,这里只显示邮箱 + # jyn:表单中包含的字段,这里只显示邮箱 fields = ('email',) def clean_password2(self): @@ -31,7 +31,7 @@ class BlogUserCreationForm(forms.ModelForm): """ password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") - # 检查密码是否存在且不一致 + # jyn:检查密码是否存在且不一致 if password1 and password2 and password1 != password2: raise forms.ValidationError(_("passwords do not match")) return password2 @@ -41,12 +41,12 @@ class BlogUserCreationForm(forms.ModelForm): 重写保存方法,确保密码以哈希形式存储 而不是明文存储 """ - # 先调用父类方法获取用户对象,但不立即保存到数据库 + # jyn:先调用父类方法获取用户对象,但不立即保存到数据库 user = super().save(commit=False) - # 使用set_password方法对密码进行哈希处理 + # jyn:使用set_password方法对密码进行哈希处理 user.set_password(self.cleaned_data["password1"]) if commit: - # 标记用户来源为管理员站点 + # jyn:标记用户来源为管理员站点 user.source = 'adminsite' user.save() return user @@ -58,11 +58,11 @@ class BlogUserChangeForm(UserChangeForm): 继承自Django内置的UserChangeForm """ class Meta: - # 指定关联的模型 + # jyn:指定关联的模型 model = BlogUser - # 显示所有字段 + # jyn:显示所有字段 fields = '__all__' - # 指定用户名字段的处理类 + # jyn:指定用户名字段的处理类 field_classes = {'username': UsernameField} def __init__(self, *args, **kwargs): @@ -75,11 +75,11 @@ class BlogUserAdmin(UserAdmin): 自定义用户管理员类,用于在Django管理后台配置用户模型的显示和操作 继承自Django内置的UserAdmin """ - # 指定编辑用户时使用的表单 + # jyn:指定编辑用户时使用的表单 form = BlogUserChangeForm - # 指定添加用户时使用的表单 + # jyn:指定添加用户时使用的表单 add_form = BlogUserCreationForm - # 列表页面显示的字段 + # jyn:列表页面显示的字段 list_display = ( 'id', 'nickname', @@ -89,7 +89,7 @@ class BlogUserAdmin(UserAdmin): 'date_joined', 'source' ) - # 列表页面中可点击跳转的字段 + # jyn:列表页面中可点击跳转的字段 list_display_links = ('id', 'username') - # 排序方式,按id降序排列(最新的用户在前) + # jyn:排序方式,按id降序排列(最新的用户在前) ordering = ('-id',) \ No newline at end of file diff --git a/src/django-master/accounts/apps.py b/src/django-master/accounts/apps.py index 34121a3..2a51746 100644 --- a/src/django-master/accounts/apps.py +++ b/src/django-master/accounts/apps.py @@ -8,6 +8,6 @@ class AccountsConfig(AppConfig): 每个Django应用都需要一个配置类,用于设置应用的各种属性和行为 通常放在应用目录下的apps.py文件中 """ - # 应用的名称,必须与应用目录名一致 - # 这个名称会被Django用来识别和管理应用 + # jyn:应用的名称,必须与应用目录名一致 + # jyn:这个名称会被Django用来识别和管理应用 name = 'accounts' \ No newline at end of file diff --git a/src/django-master/accounts/forms.py b/src/django-master/accounts/forms.py index 713edf5..0373b3c 100644 --- a/src/django-master/accounts/forms.py +++ b/src/django-master/accounts/forms.py @@ -4,8 +4,8 @@ from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.core.exceptions import ValidationError from django.forms import widgets from django.utils.translation import gettext_lazy as _ -from . import utils # 导入自定义工具模块,可能用于验证码验证等功能 -from .models import BlogUser # 导入自定义用户模型 +from . import utils # jyn:导入自定义工具模块,可能用于验证码验证等功能 +from .models import BlogUser # jyn:导入自定义用户模型 class LoginForm(AuthenticationForm): @@ -14,14 +14,14 @@ class LoginForm(AuthenticationForm): 用于处理用户登录验证,主要扩展了表单字段的样式 """ def __init__(self, *args, **kwargs): - # 调用父类构造方法初始化表单 + # jyn:调用父类构造方法初始化表单 super(LoginForm, self).__init__(*args, **kwargs) - # 自定义用户名输入框:添加占位符和CSS类 + # jyn:自定义用户名输入框:添加占位符和CSS类 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) - # 自定义密码输入框:添加占位符和CSS类 + # jyn:自定义密码输入框:添加占位符和CSS类 self.fields['password'].widget = widgets.PasswordInput( attrs={'placeholder': "password", "class": "form-control"}) @@ -32,10 +32,10 @@ class RegisterForm(UserCreationForm): 用于处理用户注册逻辑,包含用户名、邮箱和密码验证 """ def __init__(self, *args, **kwargs): - # 调用父类构造方法初始化表单 + # jyn:调用父类构造方法初始化表单 super(RegisterForm, self).__init__(*args, **kwargs) - # 自定义各字段的输入控件,添加样式和占位符 + # jyn:自定义各字段的输入控件,添加样式和占位符 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['email'].widget = widgets.EmailInput( @@ -51,14 +51,14 @@ class RegisterForm(UserCreationForm): 表单验证机制中,以clean_为前缀的方法会自动被调用 """ email = self.cleaned_data['email'] - # 检查该邮箱是否已存在于数据库中 + # jyn:检查该邮箱是否已存在于数据库中 if get_user_model().objects.filter(email=email).exists(): - raise ValidationError(_("email already exists")) # 抛出验证错误 + raise ValidationError(_("email already exists")) #jyn: 抛出验证错误 return email class Meta: - model = get_user_model() # 使用项目配置的用户模型(可能是自定义的BlogUser) - fields = ("username", "email") # 注册表单中显示的字段 + model = get_user_model() # jyn:使用项目配置的用户模型(可能是自定义的BlogUser) + fields = ("username", "email") # jyn:注册表单中显示的字段 class ForgetPasswordForm(forms.Form): @@ -66,7 +66,7 @@ class ForgetPasswordForm(forms.Form): 忘记密码表单,用于用户重置密码的流程 包含新密码、确认密码、邮箱和验证码字段 """ - # 新密码字段 + # jyn:新密码字段 new_password1 = forms.CharField( label=_("New password"), widget=forms.PasswordInput( @@ -77,7 +77,7 @@ class ForgetPasswordForm(forms.Form): ), ) - # 确认新密码字段 + # jyn:确认新密码字段 new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -88,7 +88,7 @@ class ForgetPasswordForm(forms.Form): ), ) - # 邮箱字段(用于验证用户身份) + # jyn:邮箱字段(用于验证用户身份) email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -99,7 +99,7 @@ class ForgetPasswordForm(forms.Form): ), ) - # 验证码字段(用于身份验证) + # jyn:验证码字段(用于身份验证) code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -115,11 +115,11 @@ class ForgetPasswordForm(forms.Form): password1 = self.data.get("new_password1") password2 = self.data.get("new_password2") - # 检查两次密码是否一致 + # jyn:检查两次密码是否一致 if password1 and password2 and password1 != password2: raise ValidationError(_("passwords do not match")) - # 使用Django内置的密码验证器验证密码强度 + # jyn:使用Django内置的密码验证器验证密码强度 password_validation.validate_password(password2) return password2 @@ -127,22 +127,22 @@ class ForgetPasswordForm(forms.Form): def clean_email(self): """验证邮箱是否已注册""" user_email = self.cleaned_data.get("email") - # 检查该邮箱是否存在于系统中 + # jyn:检查该邮箱是否存在于系统中 if not BlogUser.objects.filter(email=user_email).exists(): - # 提示邮箱不存在(实际应用中可能需要模糊提示以避免信息泄露) + # jyn:提示邮箱不存在(实际应用中可能需要模糊提示以避免信息泄露) raise ValidationError(_("email does not exist")) return user_email def clean_code(self): """验证验证码是否有效""" code = self.cleaned_data.get("code") - # 调用工具函数验证邮箱和验证码是否匹配 + # jyn:调用工具函数验证邮箱和验证码是否匹配 error = utils.verify( email=self.cleaned_data.get("email"), code=code, ) if error: - raise ValidationError(error) # 验证码无效时抛出错误 + raise ValidationError(error) # jyn:验证码无效时抛出错误 return code diff --git a/src/django-master/accounts/models.py b/src/django-master/accounts/models.py index c1279eb..4e35e93 100644 --- a/src/django-master/accounts/models.py +++ b/src/django-master/accounts/models.py @@ -3,28 +3,28 @@ from django.db import models from django.urls import reverse from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ -from djangoblog.utils import get_current_site # 导入获取当前站点信息的工具函数 +from djangoblog.utils import get_current_site # jyn:导入获取当前站点信息的工具函数 # Create your models here. class BlogUser(AbstractUser): """ - 自定义用户模型,继承自Django内置的AbstractUser + jyn:自定义用户模型,继承自Django内置的AbstractUser 扩展了默认用户模型的字段,以满足博客系统的特定需求 """ - # 昵称字段,允许为空 + # jyn:昵称字段,允许为空 nickname = models.CharField(_('nick name'), max_length=100, blank=True) - # 账号创建时间,默认为当前时间 + # jyn:账号创建时间,默认为当前时间 creation_time = models.DateTimeField(_('creation time'), default=now) - # 最后修改时间,默认为当前时间(通常需要在保存时更新) + # jyn:最后修改时间,默认为当前时间(通常需要在保存时更新) last_modify_time = models.DateTimeField(_('last modify time'), default=now) - # 账号创建来源(如管理员添加、前台注册等),用于追踪用户注册渠道 + # jyn:账号创建来源(如管理员添加、前台注册等),用于追踪用户注册渠道 source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): """ - 返回用户详情页的URL + jyn:返回用户详情页的URL Django推荐为模型定义此方法,用于获取对象的标准URL """ return reverse( @@ -33,23 +33,23 @@ class BlogUser(AbstractUser): ) def __str__(self): - """模型的字符串表示,这里返回用户的邮箱""" + """jyn:模型的字符串表示,这里返回用户的邮箱""" return self.email def get_full_url(self): - """获取用户详情页的完整URL(包含域名)""" - # 获取当前站点的域名 + """jyn:获取用户详情页的完整URL(包含域名)""" + # jyn:获取当前站点的域名 site = get_current_site().domain - # 拼接完整URL(协议+域名+路径) + # jyn:拼接完整URL(协议+域名+路径) url = "https://{site}{path}".format( site=site, - path=self.get_absolute_url() # 调用get_absolute_url获取相对路径 + path=self.get_absolute_url() # jyn:调用get_absolute_url获取相对路径 ) return url class Meta: - """模型的元数据配置""" - ordering = ['-id'] # 默认排序方式:按id降序(最新创建的用户在前) - verbose_name = _('user') # 模型的单数显示名称(支持国际化) - verbose_name_plural = verbose_name # 模型的复数显示名称(与单数相同) - get_latest_by = 'id' # 指定使用id字段获取最新对象(用于Model.objects.latest()) \ No newline at end of file + """jyn:模型的元数据配置""" + ordering = ['-id'] # jyn:默认排序方式:按id降序(最新创建的用户在前) + verbose_name = _('user') # jyn:模型的单数显示名称(支持国际化) + verbose_name_plural = verbose_name # jyn:模型的复数显示名称(与单数相同) + get_latest_by = 'id' # jyn:指定使用id字段获取最新对象(用于Model.objects.latest()) \ No newline at end of file diff --git a/src/django-master/accounts/tests.py b/src/django-master/accounts/tests.py index 70b2776..ac8263b 100644 --- a/src/django-master/accounts/tests.py +++ b/src/django-master/accounts/tests.py @@ -3,10 +3,10 @@ from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from accounts.models import BlogUser # 导入自定义用户模型 -from blog.models import Article, Category # 导入博客相关模型 -from djangoblog.utils import * # 导入项目工具函数 -from . import utils # 导入当前应用的工具函数 +from accounts.models import BlogUser # jyn:导入自定义用户模型 +from blog.models import Article, Category # jyn:导入博客相关模型 +from djangoblog.utils import * # jyn:导入项目工具函数 +from . import utils # jyn:导入当前应用的工具函数 # Create your tests here. @@ -21,68 +21,68 @@ class AccountTest(TestCase): 测试前的初始化方法,会在每个测试方法执行前运行 用于创建测试所需的基础数据 """ - self.client = Client() # 创建测试客户端,用于模拟用户请求 - self.factory = RequestFactory() # 创建请求工厂,用于构建请求对象 - # 创建一个测试用户 + self.client = Client() # jyn:创建测试客户端,用于模拟用户请求 + self.factory = RequestFactory() # jyn:创建请求工厂,用于构建请求对象 + # jyn:创建一个测试用户 self.blog_user = BlogUser.objects.create_user( username="test", email="admin@admin.com", password="12345678" ) - self.new_test = "xxx123--=" # 测试用的新密码 + self.new_test = "xxx123--=" #jyn: 测试用的新密码 def test_validate_account(self): """测试用户账号验证相关功能,包括登录和管理员权限""" - site = get_current_site().domain # 获取当前站点域名 + site = get_current_site().domain # jyn:获取当前站点域名 - # 创建一个超级用户 + # jyn:创建一个超级用户 user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="qwer!@#$ggg") - # 获取创建的用户 + # jyn:获取创建的用户 testuser = BlogUser.objects.get(username='liangliangyy1') - # 测试登录功能 + # jyn:测试登录功能 loginresult = self.client.login( username='liangliangyy1', password='qwer!@#$ggg') - self.assertEqual(loginresult, True) # 断言登录成功 + self.assertEqual(loginresult, True) #jyn: 断言登录成功 - # 测试访问管理员页面 + # jyn:测试访问管理员页面 response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) # 断言页面访问成功 + self.assertEqual(response.status_code, 200) # jyn:断言页面访问成功 - # 创建测试分类 + # jyn:创建测试分类 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() - # 创建测试文章 + # jyn:创建测试文章 article = Article() article.title = "nicetitleaaa" article.body = "nicecontentaaa" article.author = user article.category = category - article.type = 'a' # 假设'a'表示文章类型 - article.status = 'p' # 假设'p'表示已发布 + article.type = 'a' # jyn:假设'a'表示文章类型 + article.status = 'p' # jyn:假设'p'表示已发布 article.save() - # 测试访问文章管理页面 + # jyn:测试访问文章管理页面 response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) # 断言页面访问成功 + self.assertEqual(response.status_code, 200) # jyn:断言页面访问成功 def test_validate_register(self): """测试用户注册功能,包括注册流程、邮箱验证和权限控制""" - # 初始状态下,该邮箱应不存在 + # jyn:初始状态下,该邮箱应不存在 self.assertEquals( 0, len( BlogUser.objects.filter( email='user123@user.com'))) - # 模拟用户注册提交 + # jyn:模拟用户注册提交 response = self.client.post(reverse('account:register'), { 'username': 'user1233', 'email': 'user123@user.com', @@ -90,43 +90,43 @@ class AccountTest(TestCase): 'password2': 'password123!q@wE#R$T', }) - # 注册后,该邮箱应存在 + #jyn: 注册后,该邮箱应存在 self.assertEquals( 1, len( BlogUser.objects.filter( email='user123@user.com'))) - # 获取刚注册的用户 + #jyn: 获取刚注册的用户 user = BlogUser.objects.filter(email='user123@user.com')[0] - # 生成验证链接(模拟邮箱验证流程) + # jyn:生成验证链接(模拟邮箱验证流程) sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) path = reverse('accounts:result') url = '{path}?type=validation&id={id}&sign={sign}'.format( path=path, id=user.id, sign=sign) - # 访问验证链接 + # jyn:访问验证链接 response = self.client.get(url) - self.assertEqual(response.status_code, 200) # 断言验证页面访问成功 + self.assertEqual(response.status_code, 200) # jyn:断言验证页面访问成功 - # 登录新注册用户 + # jyn:登录新注册用户 self.client.login(username='user1233', password='password123!q@wE#R$T') - # 提升用户权限 + # jyn:提升用户权限 user = BlogUser.objects.filter(email='user123@user.com')[0] user.is_superuser = True user.is_staff = True user.save() - # 清除缓存 + # jyn:清除缓存 delete_sidebar_cache() - # 创建测试分类 + # jyn:创建测试分类 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() - # 创建测试文章 + # jyn:创建测试文章 article = Article() article.category = category article.title = "nicetitle333" @@ -136,94 +136,94 @@ class AccountTest(TestCase): article.status = 'p' article.save() - # 测试访问文章管理页面 + # jyn:测试访问文章管理页面 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) - # 测试登出功能 + # jyn:测试登出功能 response = self.client.get(reverse('account:logout')) self.assertIn(response.status_code, [301, 302, 200]) # 登出通常是重定向 - # 登出后访问管理页面(应被拒绝或重定向) + # jyn:登出后访问管理页面(应被拒绝或重定向) response = self.client.get(article.get_admin_url()) self.assertIn(response.status_code, [301, 302, 200]) - # 使用错误密码登录 + # jyn:使用错误密码登录 response = self.client.post(reverse('account:login'), { 'username': 'user1233', 'password': 'password123' }) self.assertIn(response.status_code, [301, 302, 200]) - # 错误登录后访问管理页面 + # jyn:错误登录后访问管理页面 response = self.client.get(article.get_admin_url()) 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) # 存储验证码 - utils.send_verify_email(to_email, code) # 发送验证邮件 + code = generate_code() # jyn:生成验证码 + utils.set_code(to_email, code) # jyn:存储验证码 + utils.send_verify_email(to_email, code) # jyn:发送验证邮件 - # 验证正确的邮箱和验证码 + # jyn:验证正确的邮箱和验证码 err = utils.verify("admin@admin.com", code) - self.assertEqual(err, None) # 应无错误 + self.assertEqual(err, None) # jyn:应无错误 - # 验证错误的邮箱和正确的验证码 + # jyn:验证错误的邮箱和正确的验证码 err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str) # 应返回错误信息 + self.assertEqual(type(err), str) # jyn:应返回错误信息 def test_forget_password_email_code_success(self): """测试发送密码重置验证码成功的情况""" resp = self.client.post( path=reverse("account:forget_password_code"), - data=dict(email="admin@admin.com") # 使用已存在的邮箱 + data=dict(email="admin@admin.com") # jyn:使用已存在的邮箱 ) - self.assertEqual(resp.status_code, 200) # 断言请求成功 - self.assertEqual(resp.content.decode("utf-8"), "ok") # 断言返回成功信息 + self.assertEqual(resp.status_code, 200) # jyn:断言请求成功 + self.assertEqual(resp.content.decode("utf-8"), "ok") # jyn:断言返回成功信息 def test_forget_password_email_code_fail(self): """测试发送密码重置验证码失败的情况""" - # 不提供邮箱 + # jyn:不提供邮箱 resp = self.client.post( path=reverse("account:forget_password_code"), data=dict() ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") # 断言返回错误信息 + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") # jyn:断言返回错误信息 - # 提供无效格式的邮箱 + # jyn:提供无效格式的邮箱 resp = self.client.post( path=reverse("account:forget_password_code"), data=dict(email="admin@com") ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") # 断言返回错误信息 + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #jyn: 断言返回错误信息 def test_forget_password_email_success(self): """测试密码重置成功的情况""" - code = generate_code() # 生成验证码 - utils.set_code(self.blog_user.email, code) # 存储验证码 - # 准备重置密码的数据 + code = generate_code() # jyn:生成验证码 + utils.set_code(self.blog_user.email, code) #jyn: 存储验证码 + # jyn:准备重置密码的数据 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, code=code, ) - # 提交密码重置请求 + # jyn:提交密码重置请求 resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 302) # 成功重置后通常重定向 + self.assertEqual(resp.status_code, 302) # jyn:成功重置后通常重定向 - # 验证密码是否已更新 + # jyn:验证密码是否已更新 blog_user = BlogUser.objects.filter( email=self.blog_user.email, - ).first() # type: BlogUser - self.assertNotEqual(blog_user, None) # 断言用户存在 - # 断言密码修改成功 + ).first() # jyn:type: BlogUser + self.assertNotEqual(blog_user, None) # jyn:断言用户存在 + # jyn:断言密码修改成功 self.assertEqual(blog_user.check_password(data["new_password1"]), True) def test_forget_password_email_not_user(self): @@ -231,7 +231,7 @@ class AccountTest(TestCase): data = dict( new_password1=self.new_test, new_password2=self.new_test, - email="123@123.com", # 不存在的邮箱 + email="123@123.com", # jyn:不存在的邮箱 code="123456", ) resp = self.client.post( @@ -239,22 +239,22 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) # 应返回页面但不重置密码 + self.assertEqual(resp.status_code, 200) # jyn:应返回页面但不重置密码 def test_forget_password_email_code_error(self): """测试使用错误的验证码重置密码的情况""" - code = generate_code() # 生成正确验证码 - utils.set_code(self.blog_user.email, code) # 存储验证码 - # 使用错误的验证码 + code = generate_code() # jyn:生成正确验证码 + utils.set_code(self.blog_user.email, code) #jyn: 存储验证码 + # jyn:使用错误的验证码 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", # 错误的验证码 + code="111111", # jyn:错误的验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) # 应返回页面但不重置密码 \ No newline at end of file + self.assertEqual(resp.status_code, 200) # jyn:应返回页面但不重置密码 \ No newline at end of file diff --git a/src/django-master/accounts/urls.py b/src/django-master/accounts/urls.py index dcde6e9..36b9b7d 100644 --- a/src/django-master/accounts/urls.py +++ b/src/django-master/accounts/urls.py @@ -1,49 +1,49 @@ from django.urls import path -from django.urls import re_path # 用于正则表达式匹配URL +from django.urls import re_path # jyn:用于正则表达式匹配URL -from . import views # 导入当前应用的视图函数/类 -from .forms import LoginForm # 导入自定义的登录表单 +from . import views # jyn:导入当前应用的视图函数/类 +from .forms import LoginForm # jyn:导入自定义的登录表单 -# 定义应用命名空间,避免URL名称冲突 +# jyn:定义应用命名空间,避免URL名称冲突 app_name = "accounts" -# URL路由配置列表,映射URL路径到对应的视图 +# jyn:URL路由配置列表,映射URL路径到对应的视图 urlpatterns = [ - # 登录页面路由:使用正则匹配以login/结尾的路径 + # jyn:登录页面路由:使用正则匹配以login/结尾的路径 re_path(r'^login/$', - # 调用LoginView类视图,指定登录成功后重定向到首页(/) + # jyn:调用LoginView类视图,指定登录成功后重定向到首页(/) views.LoginView.as_view(success_url='/'), - name='login', # URL的名称,用于反向解析 - # 向视图传递额外参数:指定登录表单为自定义的LoginForm + name='login', # jyn:URL的名称,用于反向解析 + # jyn:向视图传递额外参数:指定登录表单为自定义的LoginForm kwargs={'authentication_form': LoginForm}), - # 注册页面路由:匹配以register/结尾的路径 + # jyn:注册页面路由:匹配以register/结尾的路径 re_path(r'^register/$', - # 调用RegisterView类视图,注册成功后重定向到首页 + # jyn:调用RegisterView类视图,注册成功后重定向到首页 views.RegisterView.as_view(success_url="/"), - name='register'), # URL名称,用于反向解析 + name='register'), # jyn:URL名称,用于反向解析 - # 登出功能路由:匹配以logout/结尾的路径 + # jyn:登出功能路由:匹配以logout/结尾的路径 re_path(r'^logout/$', - # 调用LogoutView类视图(Django内置或自定义) + # jyn:调用LogoutView类视图(Django内置或自定义) views.LogoutView.as_view(), - name='logout'), # URL名称 + name='logout'), #jyn: URL名称 - # 账号操作结果页面路由:精确匹配account/result.html路径 + # jyn:账号操作结果页面路由:精确匹配account/result.html路径 path(r'account/result.html', - # 调用函数视图account_result + # jyn:调用函数视图account_result views.account_result, - name='result'), # URL名称,用于展示注册/验证等结果 + name='result'), #jyn: URL名称,用于展示注册/验证等结果 - # 忘记密码页面路由:匹配以forget_password/结尾的路径 + # jyn:忘记密码页面路由:匹配以forget_password/结尾的路径 re_path(r'^forget_password/$', - # 调用ForgetPasswordView类视图 + # jyn:调用ForgetPasswordView类视图 views.ForgetPasswordView.as_view(), - name='forget_password'), # URL名称 + name='forget_password'), #jyn: URL名称 - # 发送密码重置验证码页面路由:匹配以forget_password_code/结尾的路径 + # jyn:发送密码重置验证码页面路由:匹配以forget_password_code/结尾的路径 re_path(r'^forget_password_code/$', - # 调用ForgetPasswordEmailCode类视图(处理发送验证码逻辑) + # jyn:调用ForgetPasswordEmailCode类视图(处理发送验证码逻辑) views.ForgetPasswordEmailCode.as_view(), - name='forget_password_code'), # URL名称 + name='forget_password_code'), # jyn:URL名称 ] \ No newline at end of file diff --git a/src/django-master/accounts/user_login_backend.py b/src/django-master/accounts/user_login_backend.py index 1b2211c..47c2316 100644 --- a/src/django-master/accounts/user_login_backend.py +++ b/src/django-master/accounts/user_login_backend.py @@ -1,5 +1,5 @@ -from django.contrib.auth import get_user_model # 获取项目配置的用户模型(支持自定义模型) -from django.contrib.auth.backends import ModelBackend # 导入Django内置的模型认证后端 +from django.contrib.auth import get_user_model # jyn:获取项目配置的用户模型(支持自定义模型) +from django.contrib.auth.backends import ModelBackend # jyn:导入Django内置的模型认证后端 class EmailOrUsernameModelBackend(ModelBackend): @@ -17,22 +17,22 @@ class EmailOrUsernameModelBackend(ModelBackend): - password:前端传入的密码(明文) 返回:验证成功返回用户对象,失败返回None """ - # 判断输入的「username」是否包含@符号,以此区分邮箱和用户名 + # jyn:判断输入的「username」是否包含@符号,以此区分邮箱和用户名 if '@' in username: - # 若包含@,则按邮箱字段查询用户 + # jyn:若包含@,则按邮箱字段查询用户 kwargs = {'email': username} else: - # 若不包含@,则按用户名字段查询用户 + # jyn:若不包含@,则按用户名字段查询用户 kwargs = {'username': username} try: - # 根据上述条件从数据库查询唯一用户 + # jyn:根据上述条件从数据库查询唯一用户 user = get_user_model().objects.get(**kwargs) - # 验证密码:check_password会自动将明文密码与数据库中存储的哈希密码比对 + # jyn:验证密码:check_password会自动将明文密码与数据库中存储的哈希密码比对 if user.check_password(password): - return user # 密码正确,返回用户对象(认证成功) + return user # jyn:密码正确,返回用户对象(认证成功) except get_user_model().DoesNotExist: - # 若查询不到用户(用户名/邮箱不存在),返回None(认证失败) + # jyn:若查询不到用户(用户名/邮箱不存在),返回None(认证失败) return None def get_user(self, username): @@ -43,8 +43,8 @@ class EmailOrUsernameModelBackend(ModelBackend): 返回:存在则返回用户对象,不存在返回None """ try: - # 通过主键(ID)查询用户 + # jyn:通过主键(ID)查询用户 return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: - # 若用户不存在,返回None + # jyn:若用户不存在,返回None return None \ No newline at end of file diff --git a/src/django-master/accounts/utils.py b/src/django-master/accounts/utils.py index 858b241..95eb6c3 100644 --- a/src/django-master/accounts/utils.py +++ b/src/django-master/accounts/utils.py @@ -1,13 +1,13 @@ import typing from datetime import timedelta -from django.core.cache import cache # 导入Django缓存模块,用于存储验证码 -from django.utils.translation import gettext # 用于获取即时翻译文本 -from django.utils.translation import gettext_lazy as _ # 用于延迟翻译文本(支持国际化) +from django.core.cache import cache # jyn:导入Django缓存模块,用于存储验证码 +from django.utils.translation import gettext # jyn:用于获取即时翻译文本 +from django.utils.translation import gettext_lazy as _ # jyn:用于延迟翻译文本(支持国际化) -from djangoblog.utils import send_email # 导入项目自定义的发送邮件工具函数 +from djangoblog.utils import send_email #jyn: 导入项目自定义的发送邮件工具函数 -# 验证码有效期:5分钟(全局变量,统一控制时效) +# jyn:验证码有效期:5分钟(全局变量,统一控制时效) _code_ttl = timedelta(minutes=5) @@ -19,11 +19,11 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")) code: 生成的随机验证码 subject: 邮件主题,默认值为“Verify Email”(支持国际化) """ - # 构造邮件HTML内容,包含验证码和有效期提示,使用%(code)s占位符注入验证码 + # jyn:构造邮件HTML内容,包含验证码和有效期提示,使用%(code)s占位符注入验证码 html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " "properly") % {'code': code} - # 调用发送邮件函数,参数依次为:收件人列表、邮件主题、邮件内容 + # jyn:调用发送邮件函数,参数依次为:收件人列表、邮件主题、邮件内容 send_email([to_mail], subject, html_content) @@ -39,11 +39,11 @@ def verify(email: str, code: str) -> typing.Optional[str]: 代码注释中指出当前错误处理逻辑不合理:应使用raise抛出异常,而非返回错误字符串 若返回错误字符串,调用方需额外判断返回值是否为错误,增加了代码耦合度 """ - # 从缓存中获取该邮箱对应的验证码 + # jyn:从缓存中获取该邮箱对应的验证码 cache_code = get_code(email) - # 对比用户输入的验证码与缓存中的验证码 + # jyn:对比用户输入的验证码与缓存中的验证码 if cache_code != code: - # 验证码不匹配时,返回国际化的错误提示 + #jyn: 验证码不匹配时,返回国际化的错误提示 return gettext("Verification code error") @@ -54,7 +54,7 @@ def set_code(email: str, code: str): email: 作为缓存key的邮箱地址(确保一个邮箱对应一个验证码) code: 需要存入缓存的验证码 """ - # 调用Django缓存的set方法:key=邮箱,value=验证码,timeout=有效期(秒) + # jyn:调用Django缓存的set方法:key=邮箱,value=验证码,timeout=有效期(秒) cache.set(email, code, _code_ttl.seconds) @@ -66,5 +66,5 @@ def get_code(email: str) -> typing.Optional[str]: Return: 缓存中存在该邮箱对应的验证码时返回字符串,不存在时返回None """ - # 调用Django缓存的get方法,根据邮箱key获取验证码 + # jyn:调用Django缓存的get方法,根据邮箱key获取验证码 return cache.get(email) \ No newline at end of file diff --git a/src/django-master/accounts/views.py b/src/django-master/accounts/views.py index 810e352..4a2d837 100644 --- a/src/django-master/accounts/views.py +++ b/src/django-master/accounts/views.py @@ -2,45 +2,45 @@ 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 # Django内置登录表单 -from django.contrib.auth.hashers import make_password # 密码哈希处理函数 -from django.http import HttpResponseRedirect, HttpResponseForbidden # HTTP响应类 +from django.contrib.auth import REDIRECT_FIELD_NAME # jyn:登录后重定向字段名常量 +from django.contrib.auth import get_user_model # jyn:获取项目配置的用户模型 +from django.contrib.auth import logout # jyn:登出功能函数 +from django.contrib.auth.forms import AuthenticationForm # jyn:Django内置登录表单 +from django.contrib.auth.hashers import make_password # jyn:密码哈希处理函数 +from django.http import HttpResponseRedirect, HttpResponseForbidden # jyn:HTTP响应类 from django.http.request import HttpRequest from django.http.response import HttpResponse -from django.shortcuts import get_object_or_404, render # 快捷函数 -from django.urls import reverse # URL反向解析 -from django.utils.decorators import method_decorator # 类视图装饰器工具 -from django.utils.http import url_has_allowed_host_and_scheme # 验证重定向URL安全性 -from django.views import View # 基础视图类 -from django.views.decorators.cache import never_cache # 禁止缓存装饰器 -from django.views.decorators.csrf import csrf_protect # CSRF保护装饰器 -from django.views.decorators.debug import sensitive_post_parameters # 敏感参数保护装饰器 -from django.views.generic import FormView, RedirectView # 通用类视图 +from django.shortcuts import get_object_or_404, render # jyn:快捷函数 +from django.urls import reverse # jyn:URL反向解析 +from django.utils.decorators import method_decorator # jyn:类视图装饰器工具 +from django.utils.http import url_has_allowed_host_and_scheme # jyn:验证重定向URL安全性 +from django.views import View # jyn:基础视图类 +from django.views.decorators.cache import never_cache # jyn:禁止缓存装饰器 +from django.views.decorators.csrf import csrf_protect #jyn: CSRF保护装饰器 +from django.views.decorators.debug import sensitive_post_parameters # jyn:敏感参数保护装饰器 +from django.views.generic import FormView, RedirectView # jyn:通用类视图 from djangoblog.utils import (send_email, get_sha256, get_current_site, - generate_code, delete_sidebar_cache) # 项目工具函数 -from . import utils # 当前应用工具函数(验证码相关) + generate_code, delete_sidebar_cache) #jyn: 项目工具函数 +from . import utils # jyn:当前应用工具函数(验证码相关) from .forms import (RegisterForm, LoginForm, ForgetPasswordForm, - ForgetPasswordCodeForm) # 当前应用表单类 -from .models import BlogUser # 自定义用户模型 + ForgetPasswordCodeForm) # jyn:当前应用表单类 +from .models import BlogUser # jyn:自定义用户模型 -logger = logging.getLogger(__name__) # 初始化日志记录器 +logger = logging.getLogger(__name__) # jyn:初始化日志记录器 -# Create your views here. +# jyn:Create your views here. class RegisterView(FormView): """ 用户注册类视图,继承自FormView(处理表单提交的通用视图) 负责用户注册表单展示、数据验证、发送验证邮件及注册结果跳转 """ - form_class = RegisterForm # 指定使用的注册表单 - template_name = 'account/registration_form.html' # 注册页面模板路径 + form_class = RegisterForm # jyn:指定使用的注册表单 + template_name = 'account/registration_form.html' # jyn:注册页面模板路径 - @method_decorator(csrf_protect) # 为视图添加CSRF保护 + @method_decorator(csrf_protect) # jyn:为视图添加CSRF保护 def dispatch(self, *args, **kwargs): """重写分发方法,添加装饰器后调用父类逻辑""" return super(RegisterView, self).dispatch(*args, **kwargs) @@ -48,26 +48,26 @@ class RegisterView(FormView): def form_valid(self, form): """表单验证通过后执行的逻辑(注册核心流程)""" if form.is_valid(): - # 1. 暂存用户数据,不立即保存(is_active设为False,需邮箱验证后激活) + # jyn:1. 暂存用户数据,不立即保存(is_active设为False,需邮箱验证后激活) user = form.save(False) - user.is_active = False # 初始状态:未激活(需邮箱验证) - user.source = 'Register' # 标记注册来源为“前台注册” - user.save(True) # 保存用户到数据库 + user.is_active = False # jyn:初始状态:未激活(需邮箱验证) + user.source = 'Register' # jyn:标记注册来源为“前台注册” + user.save(True) # jyn:保存用户到数据库 - # 2. 生成邮箱验证链接(包含签名,防止篡改) - site = get_current_site().domain # 获取当前站点域名 - # 双重SHA256加密:用SECRET_KEY+用户ID生成签名,确保链接安全性 + # jyn:2. 生成邮箱验证链接(包含签名,防止篡改) + site = get_current_site().domain # jyn:获取当前站点域名 + # jyn:双重SHA256加密:用SECRET_KEY+用户ID生成签名,确保链接安全性 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - # 开发环境下替换域名(适配本地测试) + # jyn:开发环境下替换域名(适配本地测试) if settings.DEBUG: site = '127.0.0.1:8000' - # 反向解析结果页URL,拼接完整验证链接 + # jyn:反向解析结果页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) - # 3. 构造验证邮件内容并发送 + # jyn:3. 构造验证邮件内容并发送 content = """

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

@@ -79,16 +79,16 @@ class RegisterView(FormView): {url} """.format(url=url) send_email( - emailto=[user.email], # 收件人邮箱(新注册用户的邮箱) - title='验证您的电子邮箱', # 邮件标题 - content=content # 邮件HTML内容 + emailto=[user.email], # jyn:收件人邮箱(新注册用户的邮箱) + title='验证您的电子邮箱', #jyn: 邮件标题 + content=content #jyn: 邮件HTML内容 ) - # 4. 跳转到注册结果页(提示用户查收验证邮件) + # jyn:4. 跳转到注册结果页(提示用户查收验证邮件) url = reverse('accounts:result') + f'?type=register&id={str(user.id)}' return HttpResponseRedirect(url) else: - # 表单验证失败,重新渲染表单并显示错误 + # jyn:表单验证失败,重新渲染表单并显示错误 return self.render_to_response({'form': form}) @@ -97,18 +97,18 @@ class LogoutView(RedirectView): 用户登出类视图,继承自RedirectView(处理重定向的通用视图) 负责清除用户会话、缓存,并重定向到登录页 """ - url = '/login/' # 登出后默认重定向地址(登录页) + url = '/login/' # jyn:登出后默认重定向地址(登录页) - @method_decorator(never_cache) # 禁止缓存登出页面,避免浏览器缓存导致的问题 + @method_decorator(never_cache) # jyn:禁止缓存登出页面,避免浏览器缓存导致的问题 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) # 执行重定向 + logout(request) # jyn:清除用户会话,实现登出 + delete_sidebar_cache() # jyn:删除侧边栏缓存(可能存储了用户相关信息) + return super(LogoutView, self).get(request, *args, **kwargs) # jyn:执行重定向 class LoginView(FormView): @@ -116,14 +116,14 @@ class LoginView(FormView): 用户登录类视图,继承自FormView 负责登录表单展示、数据验证、用户认证、记住登录状态及重定向 """ - form_class = LoginForm # 指定使用的自定义登录表单 - template_name = 'account/login.html' # 登录页面模板路径 - success_url = '/' # 登录成功默认重定向地址(首页) - redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名(默认next) - login_ttl = 2626560 # “记住登录”状态的有效期(秒),约等于1个月 - - # 为视图添加多重装饰器:敏感参数保护、CSRF保护、禁止缓存 - @method_decorator(sensitive_post_parameters('password')) # 保护密码参数,避免日志泄露 + form_class = LoginForm #jyn: 指定使用的自定义登录表单 + template_name = 'account/login.html' # jyn:登录页面模板路径 + success_url = '/' # jyn:登录成功默认重定向地址(首页) + redirect_field_name = REDIRECT_FIELD_NAME # jyn:重定向字段名(默认next) + login_ttl = 2626560 #jyn: “记住登录”状态的有效期(秒),约等于1个月 + + # jyn:为视图添加多重装饰器:敏感参数保护、CSRF保护、禁止缓存 + @method_decorator(sensitive_post_parameters('password')) # jyn:保护密码参数,避免日志泄露 @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): @@ -131,41 +131,41 @@ class LoginView(FormView): def get_context_data(self, **kwargs): """添加额外上下文数据(重定向地址)到模板""" - # 获取URL中的重定向参数(如登录前访问的受保护页面) + # jyn:获取URL中的重定向参数(如登录前访问的受保护页面) redirect_to = self.request.GET.get(self.redirect_field_name) if redirect_to is None: - redirect_to = '/' # 默认重定向到首页 - kwargs['redirect_to'] = redirect_to # 将重定向地址传入模板上下文 + redirect_to = '/' # jyn:默认重定向到首页 + kwargs['redirect_to'] = redirect_to # jyn:将重定向地址传入模板上下文 return super(LoginView, self).get_context_data(** kwargs) def form_valid(self, form): """表单验证通过后执行的逻辑(登录核心流程)""" - # 用Django内置AuthenticationForm重新验证(确保认证逻辑符合默认规范) + # jyn:用Django内置AuthenticationForm重新验证(确保认证逻辑符合默认规范) form = AuthenticationForm(data=self.request.POST, request=self.request) if form.is_valid(): - delete_sidebar_cache() # 删除侧边栏缓存(更新用户登录状态) - logger.info(self.redirect_field_name) # 日志记录重定向字段名 + delete_sidebar_cache() # jyn:删除侧边栏缓存(更新用户登录状态) + logger.info(self.redirect_field_name) # jyn:日志记录重定向字段名 - # 执行登录:将用户信息存入会话 + # jyn:执行登录:将用户信息存入会话 auth.login(self.request, form.get_user()) - # 处理“记住我”功能:若勾选,设置会话有效期为1个月 + # jyn:处理“记住我”功能:若勾选,设置会话有效期为1个月 if self.request.POST.get("remember"): self.request.session.set_expiry(self.login_ttl) - # 调用父类form_valid,执行重定向 + # jyn:调用父类form_valid,执行重定向 return super(LoginView, self).form_valid(form) else: - # 表单验证失败(如密码错误),重新渲染表单并显示错误 + # jyn:表单验证失败(如密码错误),重新渲染表单并显示错误 return self.render_to_response({'form': form}) def get_success_url(self): """自定义登录成功后的重定向地址(优先使用URL中的next参数)""" - # 获取POST请求中的重定向地址(用户登录前尝试访问的页面) + # jyn:获取POST请求中的重定向地址(用户登录前尝试访问的页面) redirect_to = self.request.POST.get(self.redirect_field_name) - # 验证重定向地址的安全性:避免跳转到外部恶意网站 + # jyn:验证重定向地址的安全性:避免跳转到外部恶意网站 if not url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts=[self.request.get_host()]): - redirect_to = self.success_url # 不安全则使用默认重定向地址 + redirect_to = self.success_url # jyn:不安全则使用默认重定向地址 return redirect_to @@ -174,48 +174,48 @@ def account_result(request): 账号操作结果视图函数(函数视图) 处理注册成功提示、邮箱验证逻辑,并展示结果页面 """ - # 从URL参数中获取操作类型(register/validation)和用户ID + # jyn:从URL参数中获取操作类型(register/validation)和用户ID type = request.GET.get('type') id = request.GET.get('id') - # 获取对应的用户,若不存在则返回404 + # jyn:获取对应的用户,若不存在则返回404 user = get_object_or_404(get_user_model(), id=id) logger.info(type) # 日志记录操作类型 - # 若用户已激活,直接重定向到首页(避免重复验证) + # jyn:若用户已激活,直接重定向到首页(避免重复验证) if user.is_active: return HttpResponseRedirect('/') - # 处理合法的操作类型(注册成功提示/邮箱验证) + # jyn:处理合法的操作类型(注册成功提示/邮箱验证) if type and type in ['register', 'validation']: if type == 'register': - # 注册成功:提示用户查收验证邮件 + # jyn:注册成功:提示用户查收验证邮件 content = ''' 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 ''' title = '注册成功' else: - # 邮箱验证:验证签名是否正确,正确则激活用户 - # 重新计算签名,与URL中的签名对比(防止链接篡改) + # jyn:邮箱验证:验证签名是否正确,正确则激活用户 + # jyn:重新计算签名,与URL中的签名对比(防止链接篡改) c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) sign = request.GET.get('sign') if sign != c_sign: - return HttpResponseForbidden() # 签名不匹配,返回403禁止访问 - # 激活用户:将is_active设为True + return HttpResponseForbidden() # jyn:签名不匹配,返回403禁止访问 + # jyn:激活用户:将is_active设为True user.is_active = True user.save() - # 验证成功:提示用户可登录 + # jyn:验证成功:提示用户可登录 content = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 ''' title = '验证成功' - # 渲染结果页面,传递标题和内容 + # jyn:渲染结果页面,传递标题和内容 return render(request, 'account/result.html', { 'title': title, 'content': content }) else: - # 操作类型不合法,重定向到首页 + # jyn:操作类型不合法,重定向到首页 return HttpResponseRedirect('/') @@ -224,23 +224,23 @@ class ForgetPasswordView(FormView): 忘记密码类视图,继承自FormView 负责密码重置表单展示、数据验证,及更新用户密码 """ - form_class = ForgetPasswordForm # 指定使用的密码重置表单 - template_name = 'account/forget_password.html' # 密码重置页面模板路径 + form_class = ForgetPasswordForm # jyn:指定使用的密码重置表单 + template_name = 'account/forget_password.html' # jyn:密码重置页面模板路径 def form_valid(self, form): """表单验证通过后执行的逻辑(密码重置核心流程)""" if form.is_valid(): - # 1. 获取表单中的邮箱,查询对应的用户 + # jyn:1. 获取表单中的邮箱,查询对应的用户 blog_user = BlogUser.objects.filter( email=form.cleaned_data.get("email") ).get() - # 2. 对新密码进行哈希处理,并更新用户密码 + # jyn:2. 对新密码进行哈希处理,并更新用户密码 blog_user.password = make_password(form.cleaned_data["new_password2"]) blog_user.save() - # 3. 密码重置成功,重定向到登录页 + # jyn:3. 密码重置成功,重定向到登录页 return HttpResponseRedirect('/login/') else: - # 表单验证失败(如验证码错误、密码不一致),重新渲染表单 + # jyn:表单验证失败(如验证码错误、密码不一致),重新渲染表单 return self.render_to_response({'form': form}) @@ -251,16 +251,16 @@ class ForgetPasswordEmailCode(View): """ def post(self, request: HttpRequest): """处理POST请求(发送验证码核心逻辑)""" - # 1. 验证邮箱表单数据 + #jyn: 1. 验证邮箱表单数据 form = ForgetPasswordCodeForm(request.POST) if not form.is_valid(): - return HttpResponse("错误的邮箱") # 邮箱格式不合法,返回错误提示 + return HttpResponse("错误的邮箱") # jyn:邮箱格式不合法,返回错误提示 - # 2. 生成验证码并发送邮件 - to_email = form.cleaned_data["email"] # 获取合法的邮箱地址 - code = generate_code() # 生成随机验证码 - utils.send_verify_email(to_email, code) # 发送验证码邮件 - utils.set_code(to_email, code) # 将验证码存入缓存(设置有效期) + # jyn:2. 生成验证码并发送邮件 + to_email = form.cleaned_data["email"] # jyn:获取合法的邮箱地址 + code = generate_code() # jyn:生成随机验证码 + utils.send_verify_email(to_email, code) # jyn:发送验证码邮件 + utils.set_code(to_email, code) # jyn:将验证码存入缓存(设置有效期) - # 3. 操作成功,返回“ok”提示 + # jyn:3. 操作成功,返回“ok”提示 return HttpResponse("ok") \ No newline at end of file