diff --git a/src/accounts/__init__.py b/src/accounts/__init__.py index 50a55dc..4b3a918 100644 --- a/src/accounts/__init__.py +++ b/src/accounts/__init__.py @@ -1,2 +1 @@ -#zh: -#coding:utf-8 \ No newline at end of file +#hyt: \ No newline at end of file diff --git a/src/accounts/admin.py b/src/accounts/admin.py index 3686f20..1258fb8 100644 --- a/src/accounts/admin.py +++ b/src/accounts/admin.py @@ -1,66 +1,97 @@ -#zh: -#coding:utf-8 +# hyt: + from django import forms from django.contrib.auth.admin import UserAdmin from django.contrib.auth.forms import UserChangeForm from django.contrib.auth.forms import UsernameField from django.utils.translation import gettext_lazy as _ -# 注册模型的地方 +# Register your models here. 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) # 确认密码字段 + """ + 博客用户创建表单 + + 功能:处理新用户注册时的数据验证和保存 + 扩展了Django标准用户创建流程,添加密码确认和来源记录 + """ + password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) + password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: - model = BlogUser # 指定关联的模型 - fields = ('email',) # 表单中包含的字段,只包含email + model = BlogUser + fields = ('email',) # 创建用户时只需要邮箱字段 def clean_password2(self): - """验证两次输入的密码是否一致""" - password1 = self.cleaned_data.get("password1") # 获取第一次输入的密码 - password2 = self.cleaned_data.get("password2") # 获取第二次输入的密码 - if password1 and password2 and password1 != password2: # 如果两次密码不一致 - raise forms.ValidationError(_("passwords do not match")) # 抛出验证错误 - return password2 # 返回确认的密码 + """ + 密码确认验证 + + 功能:验证两次输入的密码是否一致 + 返回:验证通过的密码 + 异常:当密码不匹配时抛出ValidationError + """ + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError(_("passwords do not match")) + return password2 def save(self, commit=True): - """保存用户,对密码进行哈希处理""" - user = super().save(commit=False) # 先不提交到数据库 - user.set_password(self.cleaned_data["password1"]) # 设置哈希后的密码 - if commit: # 如果需要提交 - user.source = 'adminsite' # 设置用户来源为管理员站点 - user.save() # 保存用户到数据库 - return user # 返回用户对象 + """ + 保存用户信息 + + 功能:处理密码哈希化和用户来源记录 + 参数:commit - 是否立即保存到数据库 + 返回:保存后的用户对象 + """ + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) # 密码哈希化 + if commit: + user.source = 'adminsite' # 标记用户来源为管理后台 + user.save() + return user class BlogUserChangeForm(UserChangeForm): - """自定义用户信息修改表单""" + """ + 博客用户信息修改表单 + + 功能:处理用户信息的编辑和更新 + 继承自Django标准UserChangeForm,支持所有字段编辑 + """ + class Meta: - model = BlogUser # 指定关联的模型 + model = BlogUser fields = '__all__' # 包含所有字段 - field_classes = {'username': UsernameField} # 指定用户名字段类型 + field_classes = {'username': UsernameField} # 用户名字段使用特定类型 def __init__(self, *args, **kwargs): - """初始化表单""" - super().__init__(*args, **kwargs) # 调用父类初始化方法 + """初始化表单,设置字段属性""" + super().__init__(*args, **kwargs) class BlogUserAdmin(UserAdmin): - """自定义用户管理后台配置""" - form = BlogUserChangeForm # 使用自定义的用户修改表单 - add_form = BlogUserCreationForm # 使用自定义的用户创建表单 + """ + 博客用户管理后台配置 + + 功能:自定义Django管理后台的用户管理界面 + 扩展了默认的用户管理功能,优化显示字段和排序 + """ + form = BlogUserChangeForm # 使用自定义修改表单 + add_form = BlogUserCreationForm # 使用自定义创建表单 + + # 列表页显示字段 list_display = ( - 'id', # 显示ID - 'nickname', # 显示昵称 - 'username', # 显示用户名 - 'email', # 显示邮箱 - 'last_login', # 显示最后登录时间 - 'date_joined', # 显示注册日期 - 'source' # 显示用户来源 + 'id', + 'nickname', + 'username', + 'email', + 'last_login', + 'date_joined', + 'source' ) - list_display_links = ('id', 'username') # 设置可点击的字段链接 - ordering = ('-id',) # 按ID倒序排列 \ No newline at end of file + + list_display_links = ('id', 'username') # 可点击链接的字段 + ordering = ('-id',) # 按ID降序排列 diff --git a/src/accounts/apps.py b/src/accounts/apps.py index b9670e1..57a0980 100644 --- a/src/accounts/apps.py +++ b/src/accounts/apps.py @@ -1,18 +1,13 @@ -#zh: -#coding:utf-8 -from django.apps import AppConfig # 导入Django应用配置基类 +# hyt: +from django.apps import AppConfig -class AccountsConfig(AppConfig): - """账户应用的配置类""" - # 指定应用的Python路径(Django 3.x及以下版本使用) - # 在Django 4.x中,name字段被替换为使用应用标签 - name = 'accounts' +class AccountsConfig(AppConfig): + """ + 账户应用配置类 - # 在Django 4.x中,可以添加以下字段: - # default_auto_field = 'django.db.models.BigAutoField' # 默认主键类型 - # verbose_name = '用户账户' # 人类可读的应用名称(中文) - # def ready(self): - # # 导入信号处理器等初始化代码 - # import accounts.signals \ No newline at end of file + 功能:定义Django账户应用的配置信息 + 继承自AppConfig,用于应用初始化和元数据配置 + """ + name = 'accounts' # 应用名称,对应INSTALLED_APPS中的配置 diff --git a/src/accounts/forms.py b/src/accounts/forms.py index 1159137..5c510d5 100644 --- a/src/accounts/forms.py +++ b/src/accounts/forms.py @@ -1,5 +1,5 @@ -#zh: -#coding:utf-8 +# hyt: + from django import forms from django.contrib.auth import get_user_model, password_validation from django.contrib.auth.forms import AuthenticationForm, UserCreationForm @@ -11,130 +11,172 @@ from .models import BlogUser class LoginForm(AuthenticationForm): - """用户登录表单""" + """ + 用户登录表单 + + 功能:处理用户登录认证,自定义表单控件样式 + 继承自Django标准AuthenticationForm,添加Bootstrap样式支持 + """ def __init__(self, *args, **kwargs): - """初始化表单,设置字段的widget属性""" + """初始化表单,设置用户名和密码输入框的样式和占位符""" 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): - """初始化表单,设置所有字段的widget属性""" + """初始化表单,设置所有字段的Bootstrap样式和占位符""" 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): - """验证邮箱是否已存在""" + """ + 邮箱唯一性验证 + + 功能:验证邮箱是否已被注册 + 返回:验证通过的邮箱 + 异常:邮箱已存在时抛出ValidationError + """ 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() # 获取当前用户模型 + model = get_user_model() # 使用当前激活的用户模型 fields = ("username", "email") # 表单包含的字段 class ForgetPasswordForm(forms.Form): - """忘记密码重置表单""" + """ + 忘记密码重置表单 + 功能:处理密码重置流程,包含邮箱验证、验证码校验和新密码设置 + 用于用户通过邮箱和验证码找回密码的场景 + """ + + # 新密码字段 new_password1 = forms.CharField( - label=_("New password"), # 新密码标签 - widget=forms.PasswordInput( # 密码输入框 + label=_("New password"), + widget=forms.PasswordInput( attrs={ - "class": "form-control", # CSS类名 - 'placeholder': _("New password") # 占位符文本 + "class": "form-control", + 'placeholder': _("New password") } ), ) + # 确认密码字段 new_password2 = forms.CharField( - label="确认密码", # 确认密码标签 - widget=forms.PasswordInput( # 密码输入框 + label="确认密码", + widget=forms.PasswordInput( attrs={ - "class": "form-control", # CSS类名 - 'placeholder': _("Confirm password") # 占位符文本 + "class": "form-control", + 'placeholder': _("Confirm password") } ), ) + # 邮箱字段 email = forms.EmailField( - label='邮箱', # 邮箱标签 - widget=forms.TextInput( # 文本输入框 + label='邮箱', + widget=forms.TextInput( attrs={ - 'class': 'form-control', # CSS类名 - 'placeholder': _("Email") # 占位符文本 + 'class': 'form-control', + 'placeholder': _("Email") } ), ) + # 验证码字段 code = forms.CharField( - label=_('Code'), # 验证码标签 - widget=forms.TextInput( # 文本输入框 + label=_('Code'), + widget=forms.TextInput( attrs={ - 'class': 'form-control', # CSS类名 - 'placeholder': _("Code") # 占位符文本 + 'class': 'form-control', + 'placeholder': _("Code") } ), ) 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) # 验证密码强度 + """ + 新密码确认验证 + + 功能:验证两次输入的新密码是否一致并符合密码策略 + 返回:验证通过的密码 + 异常:密码不匹配或不符合策略时抛出ValidationError + """ + 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) # Django密码策略验证 - return password2 # 返回确认的密码 + return password2 def clean_email(self): - """验证邮箱是否存在""" - user_email = self.cleaned_data.get("email") # 获取邮箱 - # 检查邮箱是否在系统中注册 + """ + 邮箱存在性验证 + + 功能:验证邮箱是否在系统中注册 + 返回:验证通过的邮箱 + 异常:邮箱未注册时抛出ValidationError + """ + user_email = self.cleaned_data.get("email") if not BlogUser.objects.filter(email=user_email).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 - raise ValidationError(_("email does not exist")) # 邮箱不存在报错 - return user_email # 返回邮箱 + # 安全提示:这里的报错会暴露邮箱是否注册,可根据安全需求调整 + raise ValidationError(_("email does not exist")) + return user_email def clean_code(self): - """验证验证码是否正确""" - code = self.cleaned_data.get("code") # 获取验证码 - # 调用utils模块验证验证码 + """ + 验证码校验 + + 功能:验证邮箱验证码的有效性 + 返回:验证通过的验证码 + 异常:验证码无效时抛出ValidationError + """ + code = self.cleaned_data.get("code") error = utils.verify( - email=self.cleaned_data.get("email"), # 传入邮箱 - code=code, # 传入验证码 + email=self.cleaned_data.get("email"), + code=code, ) - if error: # 如果有错误信息 - raise ValidationError(error) # 抛出验证错误 - return code # 返回验证码 + if error: + raise ValidationError(error) + return code class ForgetPasswordCodeForm(forms.Form): - """获取忘记密码验证码表单""" + """ + 忘记密码验证码请求表单 + + 功能:用于请求发送密码重置验证码,仅包含邮箱字段 + 简化表单,专门用于验证码发送流程 + """ email = forms.EmailField( label=_('Email'), # 邮箱标签 diff --git a/src/accounts/models.py b/src/accounts/models.py index 15fe1fb..e8fe7cd 100644 --- a/src/accounts/models.py +++ b/src/accounts/models.py @@ -1,50 +1,74 @@ -#zh: -#coding:utf-8 -from django.contrib.auth.models import AbstractUser # 导入Django内置的抽象用户基类 -from django.db import models # 导入Django的模型模块 -from django.urls import reverse # 用于生成URL反向解析 -from django.utils.timezone import now # 获取当前时间 -from django.utils.translation import gettext_lazy as _ # 国际化翻译 -from django.utils import get_current_site # 获取当前站点信息 +# hyt: +from django.contrib.auth.models import AbstractUser +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 -# 在此处创建模型 + +# Create your models here. class BlogUser(AbstractUser): - """自定义博客用户模型,继承自Django的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) - # 用户来源字段,记录用户创建来源(如网站注册、管理员创建等) + # 创建来源 - 记录用户注册渠道(如网站、移动端等) source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): - """获取用户的绝对URL,用于生成作者详情页链接""" + """ + 获取用户详情页的绝对URL + + 返回:用户作者详情页的URL路径 + 用于Django admin和模板中的链接生成 + """ return reverse( - 'blog:author_detail', kwargs={ # 反向解析URL - 'author_name': self.username}) # 使用用户名作为URL参数 + 'blog:author_detail', kwargs={ + 'author_name': self.username}) def __str__(self): - """对象的字符串表示,返回邮箱地址""" + """ + 对象字符串表示 + + 返回:用户的邮箱地址 + 用于Django admin和其他显示场景 + """ return self.email def get_full_url(self): - """获取完整的用户URL(包含域名)""" - site = get_current_site().domain # 获取当前站点的域名 - url = "https://{site}{path}".format(site=site, # 格式化完整URL + """ + 获取用户的完整URL(包含域名) + + 返回:包含协议和域名的完整用户URL + 用于生成外部可访问的用户主页链接 + """ + site = get_current_site().domain + url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: - """模型的元数据配置""" + """ + 模型元数据配置 + + 定义模型在数据库和Django admin中的行为 + """ ordering = ['-id'] # 默认按ID降序排列 verbose_name = _('user') # 单数显示名称 verbose_name_plural = verbose_name # 复数显示名称(与单数相同) - get_latest_by = 'id' # 指定获取最新记录的字段 \ No newline at end of file + get_latest_by = 'id' # 获取最新记录的依据字段 \ No newline at end of file diff --git a/src/accounts/tests.py b/src/accounts/tests.py index a7ae4ff..30e64d9 100644 --- a/src/accounts/tests.py +++ b/src/accounts/tests.py @@ -1,5 +1,5 @@ -#zh: -#coding:utf-8 +#hyt: + from django.test import Client, RequestFactory, TestCase from django.urls import reverse from django.utils import timezone @@ -11,41 +11,56 @@ from djangoblog.utils import * from . import utils -# 在此处创建测试 +# Create your tests here. class AccountTest(TestCase): - """账户功能测试类""" + """ + 账户功能测试类 + + 测试用户认证、注册、密码重置等核心账户功能 + 包含完整的用户生命周期测试用例 + """ def setUp(self): - """测试初始化方法,在每个测试方法执行前运行""" - self.client = Client() # 创建测试客户端 - self.factory = RequestFactory() # 创建请求工厂 + """ + 测试初始化方法 + + 在每个测试方法执行前运行,创建测试所需的客户端和用户数据 + """ + self.client = Client() # Django 测试客户端 + self.factory = RequestFactory() # 请求工厂,用于模拟请求 # 创建测试用户 self.blog_user = BlogUser.objects.create_user( username="test", email="admin@admin.com", password="12345678" ) - self.new_test = "xxx123--=" # 新密码用于测试 + self.new_test = "xxx123--=" # 测试用的新密码 def test_validate_account(self): - """测试账户验证功能""" - site = get_current_site().domain # 获取当前站点域名 - # 创建超级用户 + """ + 测试账户验证功能 + + 验证超级用户创建、登录、文章管理权限等核心功能 + 包括管理员权限验证和文章操作测试 + """ + site = get_current_site().domain + # 创建超级用户用于测试管理员功能 user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="qwer!@#$ggg") testuser = BlogUser.objects.get(username='liangliangyy1') - # 测试登录功能 + # 测试用户登录功能 loginresult = self.client.login( username='liangliangyy1', password='qwer!@#$ggg') - self.assertEqual(loginresult, True) # 断言登录成功 - # 测试访问管理员页面 + self.assertEqual(loginresult, True) + + # 测试管理员后台访问权限 response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) # 断言可以访问管理员页面 + self.assertEqual(response.status_code, 200) # 创建测试分类 category = Category() @@ -64,58 +79,63 @@ class AccountTest(TestCase): article.status = 'p' # 发布状态 article.save() - # 测试访问文章管理页面 + # 测试文章管理页面访问 response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) # 断言可以访问文章管理页面 + self.assertEqual(response.status_code, 200) def test_validate_register(self): - """测试用户注册功能""" - # 验证注册前用户不存在 + """ + 测试用户注册流程 + + 验证用户注册、邮箱验证、权限升级等完整注册流程 + 包括注册后的登录状态和功能权限测试 + """ + # 验证注册前邮箱不存在 self.assertEquals( 0, len( BlogUser.objects.filter( email='user123@user.com'))) - # 发送注册请求 + + # 提交注册表单 response = self.client.post(reverse('account:register'), { 'username': 'user1233', 'email': 'user123@user.com', 'password1': 'password123!q@wE#R$T', 'password2': 'password123!q@wE#R$T', }) - # 验证注册后用户存在 + + # 验证注册后用户创建成功 self.assertEquals( 1, len( BlogUser.objects.filter( email='user123@user.com'))) - - # 获取新注册的用户 user = BlogUser.objects.filter(email='user123@user.com')[0] - # 生成验证签名 + + # 生成邮箱验证签名 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) - # 测试验证页面访问 response = self.client.get(url) self.assertEqual(response.status_code, 200) - # 测试用户登录 + # 测试用户登录功能 self.client.login(username='user1233', password='password123!q@wE#R$T') user = BlogUser.objects.filter(email='user123@user.com')[0] - # 提升用户权限 + + # 升级用户权限为管理员 user.is_superuser = True user.is_staff = True user.save() - delete_sidebar_cache() # 清除侧边栏缓存 + delete_sidebar_cache() # 清理侧边栏缓存 - # 创建测试分类 + # 创建测试数据 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() - # 创建测试文章 article = Article() article.category = category article.title = "nicetitle333" @@ -125,64 +145,79 @@ class AccountTest(TestCase): article.status = 'p' article.save() - # 测试文章管理页面访问 + # 测试管理员功能访问 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) - # 测试用户登出 + # 测试退出登录 response = self.client.get(reverse('account:logout')) - self.assertIn(response.status_code, [301, 302, 200]) # 登出后重定向 + self.assertIn(response.status_code, [301, 302, 200]) - # 测试登出后访问文章管理页面(应该被重定向) + # 验证退出后权限失效 response = self.client.get(article.get_admin_url()) self.assertIn(response.status_code, [301, 302, 200]) # 测试错误密码登录 response = self.client.post(reverse('account:login'), { 'username': 'user1233', - 'password': 'password123' # 错误密码 + 'password': 'password123' # 错误的密码 }) self.assertIn(response.status_code, [301, 302, 200]) - # 测试错误登录后访问文章管理页面 + # 验证登录失败后权限仍受限 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) # 发送验证邮件 + + # 设置并发送验证码 + utils.set_code(to_email, code) + utils.send_verify_email(to_email, code) # 测试正确验证码验证 err = utils.verify("admin@admin.com", code) - self.assertEqual(err, None) # 断言验证成功,无错误 + self.assertEqual(err, None) # 测试错误邮箱验证 err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str) # 断言返回错误信息 + 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") ) - self.assertEqual(resp.status_code, 200) # 断言请求成功 - self.assertEqual(resp.content.decode("utf-8"), "ok") # 断言返回成功信息 + self.assertEqual(resp.status_code, 200) + 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() ) self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") - # 测试无效邮箱格式 + # 测试错误邮箱格式 resp = self.client.post( path=reverse("account:forget_password_code"), data=dict(email="admin@com") @@ -190,30 +225,40 @@ 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) # 设置验证码 + """ + 测试密码重置成功场景 + + 验证完整的密码重置流程,包括验证码校验和新密码设置 + """ + code = generate_code() + utils.set_code(self.blog_user.email, code) + + # 准备密码重置数据 data = dict( - new_password1=self.new_test, # 新密码 - new_password2=self.new_test, # 确认密码 - email=self.blog_user.email, # 用户邮箱 - code=code, # 验证码 + new_password1=self.new_test, + new_password2=self.new_test, + email=self.blog_user.email, + code=code, ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 302) # 断言重定向(成功) + self.assertEqual(resp.status_code, 302) # 重定向表示成功 # 验证用户密码是否修改成功 blog_user = BlogUser.objects.filter( email=self.blog_user.email, ).first() # type: BlogUser - self.assertNotEqual(blog_user, None) # 断言用户存在 - self.assertEqual(blog_user.check_password(data["new_password1"]), True) # 断言密码修改成功 + self.assertNotEqual(blog_user, None) + 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, @@ -225,12 +270,18 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) # 断言停留在当前页面(失败) + self.assertEqual(resp.status_code, 200) # 应返回错误页面 def test_forget_password_email_code_error(self): - """测试验证码错误的重置密码""" + """ + 测试验证码错误的密码重置 + + 验证错误验证码情况下的密码重置失败处理 + """ code = generate_code() utils.set_code(self.blog_user.email, code) + + # 使用错误的验证码 data = dict( new_password1=self.new_test, new_password2=self.new_test, @@ -242,4 +293,4 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) # 断言停留在当前页面(失败) \ No newline at end of file + self.assertEqual(resp.status_code, 200) # 应返回错误页面 \ No newline at end of file diff --git a/src/accounts/urls.py b/src/accounts/urls.py index d4ba77f..92f5dbf 100644 --- a/src/accounts/urls.py +++ b/src/accounts/urls.py @@ -1,42 +1,42 @@ -#zh: -#coding:utf-8 -from django.urls import path # 导入路径路由 -from django.urls import re_path # 导入正则表达式路由 +# hyt: -from . import views # 导入当前应用的视图模块 -from .forms import LoginForm # 导入自定义登录表单 +# from django.urls import path +from django.urls import re_path -app_name = "accounts" # 定义应用命名空间,用于URL反向解析 +from . import views +from .forms import LoginForm + +app_name = "accounts" # 应用命名空间,用于URL反向解析 urlpatterns = [ - # 用户登录路由 - re_path(r'^login/$', # 匹配以/login/结尾的URL - views.LoginView.as_view(success_url='/'), # 使用类视图,登录成功后跳转到首页 + # 用户登录URL + re_path(r'^login/$', + views.LoginView.as_view(success_url='/'), # 登录成功后跳转到首页 name='login', # URL名称,用于反向解析 - kwargs={'authentication_form': LoginForm}), # 传递自定义登录表单 + kwargs={'authentication_form': LoginForm}), # 使用自定义登录表单 - # 用户注册路由 - re_path(r'^register/$', # 匹配以/register/结尾的URL - views.RegisterView.as_view(success_url="/"), # 使用类视图,注册成功后跳转到首页 + # 用户注册URL + re_path(r'^register/$', + views.RegisterView.as_view(success_url="/"), # 注册成功后跳转到首页 name='register'), # URL名称,用于反向解析 - # 用户登出路由 - re_path(r'^logout/$', # 匹配以/logout/结尾的URL - views.LogoutView.as_view(), # 使用类视图处理登出 + # 用户注销URL + re_path(r'^logout/$', + views.LogoutView.as_view(), # 注销视图,使用Django内置LogoutView name='logout'), # URL名称,用于反向解析 - # 账户操作结果页面路由 - path(r'account/result.html', # 精确匹配/account/result.html路径 - views.account_result, # 使用函数视图 + # 账户操作结果页面URL + path(r'account/result.html', + views.account_result, # 函数视图,处理账户操作结果展示 name='result'), # URL名称,用于反向解析 - # 忘记密码页面路由(表单提交) - re_path(r'^forget_password/$', # 匹配以/forget_password/结尾的URL - views.ForgetPasswordView.as_view(), # 使用类视图处理忘记密码逻辑 + # 忘记密码页面URL + re_path(r'^forget_password/$', + views.ForgetPasswordView.as_view(), # 密码重置视图 name='forget_password'), # URL名称,用于反向解析 - # 获取忘记密码验证码路由 - re_path(r'^forget_password_code/$', # 匹配以/forget_password_code/结尾的URL - views.ForgetPasswordEmailCode.as_view(), # 使用类视图发送验证码 + # 忘记密码验证码请求URL + re_path(r'^forget_password_code/$', + views.ForgetPasswordEmailCode.as_view(), # 验证码发送视图 name='forget_password_code'), # URL名称,用于反向解析 ] \ No newline at end of file diff --git a/src/accounts/user_login_backend.py b/src/accounts/user_login_backend.py index b0e8ebf..2b67872 100644 --- a/src/accounts/user_login_backend.py +++ b/src/accounts/user_login_backend.py @@ -1,59 +1,70 @@ -#zh: -#coding:utf-8 -from django.contrib.auth import get_user_model # 导入获取用户模型的方法 -from django.contrib.auth.backends import ModelBackend # 导入Django认证后端基类 +# hyt: + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend class EmailOrUsernameModelBackend(ModelBackend): """ - 自定义认证后端:允许使用用户名或邮箱登录 - 扩展了Django的默认认证系统,支持更灵活的登录方式 + 多字段认证后端 + + 功能:扩展Django认证系统,支持使用用户名或邮箱登录 + 继承自ModelBackend,提供灵活的用户身份验证方式 + 应用场景:方便用户使用用户名或邮箱任意一种方式登录系统 """ def authenticate(self, request, username=None, password=None, **kwargs): """ 用户认证方法 - 重写父类方法,支持用户名和邮箱两种登录方式 - Args: + 功能:根据用户名或邮箱验证用户身份 + 参数: request: HTTP请求对象 - username: 用户输入的用户名或邮箱 - password: 用户输入的密码 - **kwargs: 其他参数 + username: 用户名或邮箱地址 + password: 用户密码 + **kwargs: 其他关键字参数 - Returns: - User: 认证成功的用户对象 - None: 认证失败 + 返回: + User对象: 认证成功返回用户实例 + None: 认证失败返回None + + 逻辑: + - 判断输入是否包含'@'符号来区分用户名和邮箱 + - 查询对应用户并验证密码 + - 支持Django认证系统的标准接口 """ - # 判断输入的是邮箱还是用户名 + # 根据输入内容判断是用户名还是邮箱 if '@' in username: - kwargs = {'email': username} # 如果包含@符号,按邮箱处理 + kwargs = {'email': username} # 包含@符号,按邮箱处理 else: - kwargs = {'username': username} # 否则按用户名处理 + 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, user_id): + def get_user(self, username): """ 根据用户ID获取用户对象 - 用于会话管理,保持用户登录状态 - Args: - user_id: 用户ID + 功能:通过用户主键获取用户实例 + 参数: + username: 用户ID(主键) + + 返回: + User对象: 用户存在时返回用户实例 + None: 用户不存在时返回None - Returns: - User: 用户对象 - None: 用户不存在 + 注意:此处的username参数实际上是用户主键ID + 用于session认证中从用户ID获取用户对象 """ try: - return get_user_model().objects.get(pk=user_id) # 根据主键查找用户 + return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: - return None # 用户不存在 \ No newline at end of file + return None \ No newline at end of file diff --git a/src/accounts/utils.py b/src/accounts/utils.py index c180317..bf093df 100644 --- a/src/accounts/utils.py +++ b/src/accounts/utils.py @@ -1,76 +1,81 @@ -#zh: -#coding:utf-8 -import typing # 导入类型提示模块 -from datetime import timedelta # 导入时间间隔模块 +# hyt: -from django.core.cache import cache # 导入Django缓存框架 -from django.utils.translation import gettext # 导入翻译函数 -from django.utils.translation import gettext_lazy as _ # 导入惰性翻译 +import typing +from datetime import timedelta -from djangoblog.utils import send_email # 导入自定义邮件发送工具 +from django.core.cache import cache +from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ -# 定义验证码的有效期:5分钟 +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: 接收邮箱地址 code: 验证码内容 subject: 邮件主题,默认为"Verify Email" + + 注意:邮件内容包含验证码和5分钟有效期的提示 """ - # 构建邮件HTML内容,包含验证码信息 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]: """ - 验证邮箱验证码是否正确 + 验证验证码有效性 - Args: - email: 邮箱地址 + 功能:验证用户输入的验证码与缓存中的验证码是否匹配 + 参数: + email: 用户邮箱地址,作为缓存键 code: 用户输入的验证码 - Returns: - str: 如果验证失败返回错误信息,验证成功返回None + 返回: + str: 验证失败时返回错误信息 + None: 验证成功时返回None - Note: - 这里的错误处理不太合理,应该采用raise抛出异常 - 否则调用方也需要对error进行处理 + 注意:当前错误处理方式不够合理,建议改为异常抛出机制 + 这样调用方可以通过try-except处理错误,避免条件判断 """ - cache_code = get_code(email) # 从缓存中获取该邮箱对应的验证码 - if cache_code != code: # 比较缓存中的验证码和用户输入的验证码 - return gettext("Verification code error") # 验证码错误,返回错误信息 - # 验证成功返回None + cache_code = get_code(email) + if cache_code != code: + return gettext("Verification code error") def set_code(email: str, code: str): """ - 将验证码存储到缓存中 + 设置验证码到缓存 + + 功能:将验证码存储到缓存系统,使用邮箱作为键 + 参数: + email: 邮箱地址,作为缓存键 + code: 验证码内容,作为缓存值 - Args: - email: 邮箱地址(作为缓存的key) - code: 验证码(作为缓存的value) + 缓存配置:验证码有效期为5分钟(_code_ttl) """ - # 使用邮箱作为key,验证码作为value,设置过期时间为5分钟 cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: """ - 从缓存中获取验证码 + 从缓存获取验证码 - Args: - email: 邮箱地址(缓存的key) + 功能:根据邮箱地址从缓存中获取对应的验证码 + 参数: + email: 邮箱地址,作为缓存键 - Returns: - str: 如果存在返回验证码,不存在返回None + 返回: + str: 存在验证码时返回验证码内容 + None: 验证码不存在或已过期时返回None """ - return cache.get(email) # 从缓存中获取指定邮箱的验证码 \ No newline at end of file + return cache.get(email) \ No newline at end of file diff --git a/src/accounts/views.py b/src/accounts/views.py index bad2752..e70429e 100644 --- a/src/accounts/views.py +++ b/src/accounts/views.py @@ -1,5 +1,5 @@ -#zh: -#coding:utf-8 +# hyt: + import logging from django.utils.translation import gettext_lazy as _ from django.conf import settings @@ -28,46 +28,52 @@ from . import utils from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm from .models import BlogUser -# 获取当前模块的日志记录器 logger = logging.getLogger(__name__) -# 在此处创建视图 +# Create your views here. class RegisterView(FormView): - """用户注册视图""" + """ + 用户注册视图 + + 功能:处理新用户注册流程,包括表单验证、用户创建和邮箱验证 + 继承自FormView,使用自定义注册表单 + """ form_class = RegisterForm # 使用自定义注册表单 - template_name = 'account/registration_form.html' # 注册模板路径 + template_name = 'account/registration_form.html' # 注册页面模板 - @method_decorator(csrf_protect) # CSRF保护装饰器 + @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.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' + 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 = """
请点击下面链接验证您的邮箱
@@ -80,122 +86,152 @@ class RegisterView(FormView): """.format(url=url) # 发送验证邮件 send_email( - emailto=[user.email], + emailto=[ + user.email, + ], title='验证您的电子邮箱', 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 + """ + 用户注销视图 + + 功能:处理用户注销流程,清理会话和缓存 + 继承自RedirectView,注销后重定向到登录页面 + """ + 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): - """处理GET请求""" - logout(request) # 执行登出操作 - delete_sidebar_cache() # 删除侧边栏缓存 + """ + 处理GET请求注销 + + 功能:执行用户注销操作,清理侧边栏缓存 + """ + logout(request) # Django内置注销函数 + delete_sidebar_cache() # 清理侧边栏缓存 return super(LogoutView, self).get(request, *args, **kwargs) class LoginView(FormView): - """用户登录视图""" + """ + 用户登录视图 + + 功能:处理用户登录认证,支持记住登录状态 + 继承自FormView,使用自定义登录表单 + """ form_class = LoginForm # 使用自定义登录表单 - template_name = 'account/login.html' # 登录模板路径 - success_url = '/' # 登录成功默认重定向URL + template_name = 'account/login.html' # 登录页面模板 + success_url = '/' # 登录成功默认跳转地址 redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名 - login_ttl = 2626560 # 会话有效期:一个月(以秒为单位) + login_ttl = 2626560 # 记住登录状态的有效期:一个月(秒数) - # 方法装饰器:保护敏感数据、CSRF防护、禁止缓存 - @method_decorator(sensitive_post_parameters('password')) - @method_decorator(csrf_protect) - @method_decorator(never_cache) + @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 = '/' # 默认重定向到首页 + redirect_to = '/' # 默认跳转到首页 kwargs['redirect_to'] = redirect_to return super(LoginView, self).get_context_data(**kwargs) def form_valid(self, form): - """表单验证通过后的处理""" - # 使用Django内置的认证表单 + """ + 表单验证通过处理 + + 功能:处理登录认证,设置会话过期时间 + """ 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()) - - # 处理"记住我"功能 + auth.login(self.request, form.get_user()) # Django内置登录函数 + # 处理"记住我"选项 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}) + # 登录失败,重新渲染登录页面 + 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 + 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') # 操作类型 + """ + 账户操作结果页面视图 + + 功能:显示注册或邮箱验证的结果信息 + 处理注册成功提示和邮箱验证激活 + """ + type = request.GET.get('type') # 操作类型:register或validation id = request.GET.get('id') # 用户ID - # 获取用户对象,不存在则返回404 - 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: 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 = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 @@ -207,43 +243,60 @@ def account_result(request): 'content': content }) else: - # 无效类型,重定向到首页 return HttpResponseRedirect('/') class ForgetPasswordView(FormView): - """忘记密码重置视图""" - form_class = ForgetPasswordForm # 使用忘记密码表单 - template_name = 'account/forget_password.html' # 模板路径 + """ + 忘记密码重置视图 + + 功能:处理用户密码重置流程 + 继承自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/') # 重定向到登录页 + blog_user.save() + return HttpResponseRedirect('/login/') # 重定向到登录页面 else: - # 表单无效,重新渲染表单页 + # 表单验证失败,重新渲染表单 return self.render_to_response({'form': form}) class ForgetPasswordEmailCode(View): - """忘记密码验证码发送视图""" + """ + 忘记密码验证码发送视图 + + 功能:处理密码重置验证码的发送请求 + 继承自View,处理POST请求发送验证码邮件 + """ def post(self, request: HttpRequest): - """处理POST请求,发送验证码""" + """ + 处理POST请求发送验证码 + + 功能:验证邮箱格式,生成并发送验证码 + """ form = ForgetPasswordCodeForm(request.POST) if not form.is_valid(): - return HttpResponse("错误的邮箱") # 表单验证失败 + return HttpResponse("错误的邮箱") # 邮箱格式错误 to_email = form.cleaned_data["email"] # 生成并发送验证码 code = generate_code() - utils.send_verify_email(to_email, code) - utils.set_code(to_email, code) + utils.send_verify_email(to_email, code) # 发送验证邮件 + utils.set_code(to_email, code) # 存储验证码到缓存 - return HttpResponse("ok") # 返回成功响应 \ No newline at end of file + return HttpResponse("ok") # 返回成功响应