王芸代码注释

master
zyl 3 months ago
parent eccf142687
commit 76d99c3469

@ -38,12 +38,13 @@
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "zyl__branch",
"git-widget-placeholder": "master",
"ignore.virus.scanning.warn.message": "true",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
@ -90,6 +91,7 @@
<updated>1760256904468</updated>
<workItem from="1760256905584" duration="153000" />
<workItem from="1760257111491" duration="1866000" />
<workItem from="1763889357146" duration="268000" />
</task>
<servers />
</component>

@ -0,0 +1,101 @@
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 _
# 模块级注释——accounts应用的Admin后台表单配置文件
# 自定义BlogUser自定义用户模型的创建表单、修改表单以及Admin后台管理配置
# 适配Django Admin的用户管理逻辑支持自定义字段如nickname、source的后台操作
from .models import BlogUser
class BlogUserCreationForm(forms.ModelForm):
"""
自定义用户创建表单用于Django Admin后台添加新用户
扩展默认表单增加密码二次验证逻辑适配BlogUser模型的字段要求
"""
# 密码输入字段label支持国际化使用密码输入控件隐藏输入内容
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
# 密码二次确认字段:用于验证两次输入密码一致
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
model = BlogUser # 关联的模型accounts应用的BlogUser自定义用户模型
fields = ('email',) # 后台创建用户时,必填的核心字段(仅邮箱,用户名可后续补充或自动生成)
def clean_password2(self):
"""
密码二次验证的清洁方法Django表单验证机制
检查两次输入的密码是否一致不一致则抛出验证错误
"""
# 获取第一次和第二次输入的密码(已通过表单基础验证的清洁数据)
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):
"""
重写保存方法实现密码哈希存储和创建来源标记
Django默认会对密码进行哈希处理此处明确调用set_password确保安全性
"""
# 调用父类save方法先不提交到数据库commit=False
user = super().save(commit=False)
# 对密码进行哈希处理后存储避免明文存储符合Django安全规范
user.set_password(self.cleaned_data["password1"])
# 若需要提交到数据库默认commit=True
if commit:
user.source = 'adminsite' # 标记用户创建来源Django Admin后台
user.save() # 最终保存用户数据到数据库
return user
class BlogUserChangeForm(UserChangeForm):
"""
自定义用户修改表单用于Django Admin后台编辑用户信息
继承Django内置UserChangeForm适配BlogUser模型的所有字段
"""
class Meta:
model = BlogUser # 关联的模型accounts应用的BlogUser
fields = '__all__' # 后台可修改的字段:所有模型字段(支持自定义扩展字段)
field_classes = {'username': UsernameField} # 用户名字段的类使用Django内置UsernameField确保符合用户名验证规则
def __init__(self, *args, **kwargs):
"""
初始化表单调用父类构造方法保持默认逻辑
若后续需要扩展表单初始化行为如隐藏字段设置默认值可在此方法中添加
"""
super().__init__(*args, **kwargs)
class BlogUserAdmin(UserAdmin):
"""
自定义UserAdmin配置用于Django Admin后台管理BlogUser模型
配置后台显示字段排序规则搜索字段等优化用户管理体验
"""
form = BlogUserChangeForm # 关联用户修改表单使用自定义的BlogUserChangeForm
add_form = BlogUserCreationForm # 关联用户创建表单使用自定义的BlogUserCreationForm
# 后台列表页显示的字段(按业务优先级排序)
list_display = (
'id', # 用户ID唯一标识
'nickname', # 用户昵称(自定义扩展字段)
'username', # 用户名Django用户模型核心字段
'email', # 邮箱(用于登录和通知,核心字段)
'last_login', # 最后登录时间(安全审计字段)
'date_joined', # 注册时间(业务统计字段)
'source' # 创建来源(区分后台创建/前台注册等,自定义扩展字段)
)
# 后台列表页可点击跳转的字段(用于快速进入编辑页)
list_display_links = ('id', 'username')
# 后台列表页默认排序规则按ID倒序新创建的用户排在前面
ordering = ('-id',)
# 后台搜索支持的字段(支持模糊查询,提升管理效率)
search_fields = ('username', 'nickname', 'email')

@ -0,0 +1,12 @@
from django.apps import AppConfig
# 模块级注释——accounts应用的核心配置类文件
# 继承Django内置的AppConfig用于定义应用的基本元信息
# 是Django识别和加载accounts应用的关键配置
class AccountsConfig(AppConfig):
"""
accounts应用的配置类用于注册应用的核心信息
遵循Django应用配置规范定义应用的唯一标识名称
"""
name = 'accounts' # 应用的唯一标识名称,与应用目录名一致,
# Django通过该名称识别并加载应用关联应用内的模型、视图等组件

@ -0,0 +1,166 @@
from django import forms
from django.contrib.auth import get_user_model, password_validation
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 # 导入自定义用户模型
# 模块级注释——accounts应用的前台用户交互表单配置文件
# 包含登录、注册、忘记密码、验证码获取等核心业务表单,
# 负责用户输入数据的验证、前端样式适配如表单控件class、占位符
# 确保用户输入合法且符合业务规则(如邮箱唯一性、密码强度、验证码有效性)
class LoginForm(AuthenticationForm):
"""
前台用户登录表单继承Django内置AuthenticationForm
重写表单控件样式和占位符适配前端页面布局提升用户体验
"""
def __init__(self, *args, **kwargs):
"""
初始化登录表单重写用户名和密码字段的控件配置
"""
super(LoginForm, self).__init__(*args, **kwargs)
# 用户名输入框设置占位符和Bootstrap表单样式类适配前端页面
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# 密码输入框设置占位符和Bootstrap表单样式类使用密码隐藏控件
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)
# 用户名输入框:占位符+Bootstrap样式
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# 邮箱输入框使用EmailInput控件占位符+Bootstrap样式
self.fields['email'].widget = widgets.EmailInput(
attrs={'placeholder': "email", "class": "form-control"})
# 密码输入框1密码隐藏控件占位符+Bootstrap样式
self.fields['password1'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
# 密码确认框:密码隐藏控件,占位符+Bootstrap样式
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() # 关联Django当前激活的用户模型此处为BlogUser
fields = ("username", "email") # 注册表单需填写的核心字段:用户名、邮箱(密码字段由父类提供)
class ForgetPasswordForm(forms.Form):
"""
前台用户忘记密码重置表单
包含新密码密码确认邮箱验证码字段实现密码重置的全流程验证
"""
new_password1 = forms.CharField(
label=_("New password"), # 字段标签(支持国际化)
widget=forms.PasswordInput(
attrs={
"class": "form-control", # Bootstrap表单样式类
'placeholder': _("New password") # 占位符(支持国际化)
}
),
)
new_password2 = forms.CharField(
label="确认密码", # 密码确认字段标签
widget=forms.PasswordInput(
attrs={
"class": "form-control",
'placeholder': _("Confirm password") # 占位符(支持国际化)
}
),
)
email = forms.EmailField(
label='邮箱', # 邮箱字段标签
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': _("Email") # 占位符(支持国际化)
}
),
)
code = forms.CharField(
label=_('Code'), # 验证码字段标签(支持国际化)
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': _("Code") # 占位符(支持国际化)
}
),
)
def clean_new_password2(self):
"""
密码确认字段清洁验证
1. 检查两次输入的新密码是否一致
2. 验证密码是否符合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") # 获取经过基础验证的邮箱
# 查询数据库判断邮箱是否关联BlogUser
if not BlogUser.objects.filter(email=user_email).exists():
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改(原代码注释保留,提示后续优化隐私保护)
raise ValidationError(_("email does not exist")) # 抛出邮箱未注册错误
return user_email # 验证通过,返回邮箱
def clean_code(self):
"""
验证码字段清洁验证调用utils模块的verify方法验证验证码有效性
若验证码无效如过期不匹配则抛出错误
"""
code = self.cleaned_data.get("code") # 获取用户输入的验证码
# 调用工具函数验证验证码传入邮箱和验证码返回错误信息或None
error = utils.verify(email=self.cleaned_data.get("email"), code=code)
if error: # 若存在错误信息,抛出验证错误
raise ValidationError(error)
return code # 验证通过,返回验证码
class ForgetPasswordCodeForm(forms.Form):
"""
前台用户获取忘记密码验证码的表单
仅包含邮箱字段用于接收用户邮箱并发送验证码
"""
email = forms.EmailField(
label=_('Email'), # 字段标签(支持国际化)
)

@ -0,0 +1,38 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
#模块级注释——comments应用的初始数据库迁移文件用于创建`Comment`模型,实现文章评论功能,支持评论层级、文章关联、用户关联等业务逻辑
class Migration(migrations.Migration):
initial = True # xxx: 标记该迁移为应用的初始迁移
dependencies = [
('blog', '0001_initial'), # xxx: 依赖`blog`应用的`0001_initial`迁移,确保`Article`模型已存在
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # xxx: 依赖Django可交换的用户模型支持自定义用户模型场景
]
operations = [
migrations.CreateModel(
name='Comment', # xxx: 定义`Comment`模型,用于存储文章评论数据
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), # 主键字段,自增大整数类型
('body', models.TextField(max_length=300, verbose_name='正文')), # xxx: 评论正文字段文本类型最大长度300
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), # 评论创建时间字段,默认当前时间
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), # xxx: 评论修改时间字段,默认当前时间
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), # xxx: 控制评论是否显示的布尔字段,默认显示
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), # xxx: 外键关联`blog`应用的`Article`模型,文章删除时评论级联删除
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), # xxx: 外键关联用户模型,用户删除时评论级联删除
('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), # xxx: 自外键关联,支持评论层级结构,允许空值,上级评论删除时当前评论级联删除
],
options={
'verbose_name': '评论', # xxx: 模型单数显示名称
'verbose_name_plural': '评论', # xxx: 模型复数显示名称
'ordering': ['-id'], # xxx: 数据查询时按ID倒序排列
'get_latest_by': 'id', # xxx: 按ID字段获取最新记录
},
),
]

@ -0,0 +1,55 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
from django.db import migrations, models
import django.utils.timezone
# 模块级注释——accounts应用的模型更新迁移文件用于调整`BlogUser`模型的选项、字段名称及属性,
# 优化字段命名规范(如时间字段命名统一)、完善字段配置(如允许空值),确保模型设计更规范
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'), # 依赖`accounts`应用的初始迁移`0001_initial`,确保`BlogUser`模型已创建
]
operations = [
migrations.AlterModelOptions(
name='bloguser', # 目标模型:`accounts`应用的`BlogUser`(自定义用户模型)
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
# 调整模型选项:
# 1. get_latest_by: 按`id`字段获取最新记录
# 2. ordering: 查询时按`id`倒序排列(新用户在前)
# 3. verbose_name/verbose_name_plural: 模型单复数显示名称均为"user"
),
migrations.RemoveField(
model_name='bloguser',
name='created_time', # 删除原有的"创建时间"字段(字段名称规范调整,后续用`creation_time`替代)
),
migrations.RemoveField(
model_name='bloguser',
name='last_mod_time', # 删除原有的"修改时间"字段(字段名称规范调整,后续用`last_modify_time`替代)
),
migrations.AddField(
model_name='bloguser',
name='creation_time', # 新增标准化的"创建时间"字段(替代原`created_time`
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
# 字段配置:默认值为当前时间,后台显示名称为"creation time"
),
migrations.AddField(
model_name='bloguser',
name='last_modify_time', # 新增标准化的"修改时间"字段(替代原`last_mod_time`
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
# 字段配置:默认值为当前时间,后台显示名称为"last modify time"
),
migrations.AlterField(
model_name='bloguser',
name='nickname', # 调整`nickname`(昵称)字段属性
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
# 调整内容允许空值blank=True最大长度100后台显示名称为"nick name"
),
migrations.AlterField(
model_name='bloguser',
name='source', # 调整`source`(创建来源)字段属性
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
# 调整内容允许空值blank=True最大长度100后台显示名称为"create source"
),
]

@ -0,0 +1,60 @@
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 # 导入项目公共工具函数(获取当前站点域名)
# 模块级注释——accounts应用的核心数据模型文件
# 定义自定义用户模型`BlogUser`继承Django内置`AbstractUser`
# 扩展业务所需的自定义字段(如昵称、创建时间、创建来源),
# 并重写核心方法以适配项目业务逻辑如用户URL生成、字符串表示
# 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)
# 创建来源字段支持国际化标签最大长度100允许空值用于标记用户注册渠道如"adminsite"/"frontend"/"oauth"
source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
"""
重写Django模型的`get_absolute_url`方法获取用户的绝对路径URL
关联博客系统的"作者详情页"路由用于直接访问用户的个人主页
"""
return reverse(
'blog:author_detail', # 路由名称对应blog应用的作者详情页路由
kwargs={'author_name': self.username} # 路由参数:用户名(作为作者标识)
)
def __str__(self):
"""
重写模型的字符串表示方法返回用户邮箱作为标识
相比默认的用户名邮箱更具唯一性便于后台管理和日志输出时识别用户
"""
return self.email
def get_full_url(self):
"""
扩展方法获取用户个人主页的完整URL包含站点域名
用于需要分享用户主页的场景如邮件通知第三方分享
"""
site = get_current_site().domain # 通过公共工具函数获取当前站点的域名(如"example.com"
# 拼接域名和用户绝对路径生成完整URL支持HTTPS协议
url = "https://{site}{path}".format(site=site, path=self.get_absolute_url())
return url
class Meta:
ordering = ['-id'] # 数据查询时默认按ID倒序排列新注册用户优先展示
verbose_name = _('user') # 模型单数显示名称(支持国际化)
verbose_name_plural = verbose_name # 模型复数显示名称(与单数一致,简化管理)
get_latest_by = 'id' # 按ID字段获取最新创建的用户记录

@ -0,0 +1,296 @@
from django.test import Client, RequestFactory, TestCase
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 # 导入accounts应用自定义工具函数如验证码设置/验证、邮件发送)
# 模块级注释——accounts应用的单元测试文件
# 覆盖用户核心业务流程的测试场景:用户登录、注册、邮箱验证码验证、忘记密码重置等,
# 基于Django TestCase框架通过模拟HTTP请求和数据库操作验证业务逻辑的正确性
# 确保用户相关功能稳定可用(如权限控制、数据一致性、错误处理)
# Create your tests here.
class AccountTest(TestCase):
"""
用户相关核心功能测试类继承Django TestCase
集中测试用户登录注册验证码验证忘记密码等关键流程
每个测试方法对应一个独立的业务场景确保测试隔离性
"""
def setUp(self):
"""
测试初始化方法每个测试方法执行前自动调用
初始化测试所需的核心对象和测试数据避免重复代码
"""
self.client = Client() # 模拟HTTP客户端用于发送GET/POST请求
self.factory = RequestFactory() # 请求工厂,用于构建自定义请求对象(本测试未直接使用)
# 创建普通测试用户(用于后续登录、密码重置等测试)
self.blog_user = BlogUser.objects.create_user(
username="test", # 用户名
email="admin@admin.com", # 邮箱
password="12345678" # 密码明文create_user会自动哈希存储
)
self.new_test = "xxx123--=" # 测试用新密码(用于忘记密码重置场景)
def test_validate_account(self):
"""
测试用户登录管理员权限文章管理访问流程
验证点超级用户登录成功管理员页面访问权限文章创建后管理页访问权限
"""
# 获取当前站点域名用于URL生成本测试未直接使用
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) # 断言登录成功
# 访问管理员首页验证是否有权限状态码200表示访问成功
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
# 创建测试分类(用于后续文章关联)
category = Category()
category.name = "categoryaaa" # 分类名称
category.creation_time = timezone.now() # 创建时间
category.last_modify_time = timezone.now() # 最后修改时间
category.save() # 保存到数据库
# 创建测试文章(关联超级用户和分类)
article = Article()
article.title = "nicetitleaaa" # 文章标题
article.body = "nicecontentaaa" # 文章内容
article.author = user # 文章作者(超级用户)
article.category = category # 文章分类
article.type = 'a' # 文章类型(推测为"article"普通文章)
article.status = 'p' # 文章状态(推测为"published"已发布)
article.save() # 保存到数据库
# 访问文章的管理员编辑页,验证超级用户是否有权限
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200) # 断言访问成功
def test_validate_register(self):
"""
测试用户注册完整流程
验证点注册提交用户创建验证邮件链接访问登录后权限升级文章创建退出登录错误密码登录
"""
# 注册前检查目标邮箱是否存在预期不存在计数为0
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 模拟POST请求提交注册表单调用注册路由
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', # 密码确认(与密码一致)
})
# 注册后检查目标邮箱是否创建成功预期计数为1
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 获取刚注册的用户生成邮箱验证链接基于用户ID和加密签名
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 生成验证签名双重SHA256加密结合SECRET_KEY和用户ID确保链接安全性
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result') # 验证结果页路由
# 拼接完整的验证链接包含用户ID和签名
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign)
# 访问验证链接验证页面是否正常响应状态码200
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 # 允许访问admin后台
user.save() # 保存权限变更
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"
article.body = "nicecontentttt"
article.author = user
article.type = 'a'
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'))
# 断言退出登录响应状态码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' # 错误密码原密码为password123!q@wE#R$T
})
# 断言登录失败后的响应状态码(跳转或重新显示登录页)
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" # 测试邮箱已在setUp中创建关联用户
code = generate_code() # 生成随机验证码(项目工具函数)
utils.set_code(to_email, code) # 存储验证码(推测基于缓存或数据库存储)
utils.send_verify_email(to_email, code) # 发送验证邮件(模拟邮件发送流程)
# 验证:正确邮箱+正确验证码 → 预期无错误返回None
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
# 验证:错误邮箱+正确验证码 → 预期返回错误信息(字符串类型)
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
def test_forget_password_email_code_success(self):
"""
测试获取忘记密码验证码的成功场景
验证点输入已注册邮箱成功获取验证码响应"ok"
"""
# 模拟POST请求提交邮箱获取忘记密码验证码
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") # 断言响应内容为"ok"(表示验证码发送成功)
def test_forget_password_email_code_fail(self):
"""
测试获取忘记密码验证码的失败场景
验证点无邮箱输入邮箱格式错误均返回"错误的邮箱"
"""
# 场景1不传入邮箱参数 → 预期返回"错误的邮箱"
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict() # 空数据(无邮箱)
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 场景2传入格式错误的邮箱无@后的域名后缀)→ 预期返回"错误的邮箱"
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com") # 格式错误的邮箱
)
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(
new_password1=self.new_test, # 新密码
new_password2=self.new_test, # 新密码确认(与新密码一致)
email=self.blog_user.email, # 测试用户邮箱
code=code, # 正确的验证码
)
# 模拟POST请求提交重置密码表单
resp = self.client.post(
path=reverse("account:forget_password"), # 忘记密码重置路由
data=data
)
self.assertEqual(resp.status_code, 302) # 断言重置成功后跳转302为重定向
# 验证密码是否真正修改成功
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
).first() # 获取测试用户
self.assertNotEqual(blog_user, None) # 断言用户存在
# 验证新密码是否匹配check_password会自动哈希比对
self.assertEqual(blog_user.check_password(data["new_password1"]), True)
def test_forget_password_email_not_user(self):
"""
测试忘记密码重置的失败场景输入未注册的邮箱
验证点未注册邮箱提交重置请求返回200状态码表单重新显示错误
"""
# 构建重置密码表单数据(邮箱未注册)
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email="123@123.com", # 未注册的邮箱
code="123456", # 任意验证码
)
# 模拟POST请求提交重置密码表单
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 200) # 断言返回表单页面(显示邮箱不存在错误)
def test_forget_password_email_code_error(self):
"""
测试忘记密码重置的失败场景验证码错误
验证点输入正确邮箱错误验证码返回200状态码表单重新显示错误
"""
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="111111", # 错误验证码与绑定的code不一致
)
# 模拟POST请求提交重置密码表单
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 200) # 断言返回表单页面(显示验证码错误)

@ -0,0 +1,36 @@
from django.urls import path
from django.urls import re_path
from . import views # 导入accounts应用的视图模块包含登录、注册等核心视图
from .forms import LoginForm # 导入自定义登录表单(适配前端样式和验证规则)
app_name = "accounts" # 定义应用命名空间用于反向解析URL时区分不同应用的路由如`reverse('accounts:login')`
# URL路由配置映射用户核心业务的URL路径到对应视图覆盖登录、注册、退出、密码重置等功能
urlpatterns = [
# 登录路由:使用正则表达式匹配`/login/`路径
re_path(r'^login/$',
views.LoginView.as_view(success_url='/'), # 关联视图类`LoginView`,登录成功后重定向到网站根目录
name='login', # 路由名称,用于反向解析(如模板中`{% url 'accounts:login' %}`
kwargs={'authentication_form': LoginForm}), # 传入自定义登录表单`LoginForm`,替代默认表单
# 注册路由:匹配`/register/`路径
re_path(r'^register/$',
views.RegisterView.as_view(success_url="/"), # 关联视图类`RegisterView`,注册成功后重定向到网站根目录
name='register'), # 路由名称用于反向解析注册页面URL
# 退出登录路由:匹配`/logout/`路径
re_path(r'^logout/$',
views.LogoutView.as_view(), # 关联视图类`LogoutView`Django内置或自定义处理退出登录逻辑
name='logout'), # 路由名称用于反向解析退出登录URL
# 账号操作结果页路由:精确匹配`/account/result.html`路径
path(r'account/result.html',
views.account_result, # 关联函数视图`account_result`,展示账号操作结果(如邮箱验证成功/失败)
name='result'), # 路由名称用于反向解析结果页URL
# 忘记密码重置路由:匹配`/forget_password/`路径
re_path(r'^forget_password/$',
views.ForgetPasswordView.as_view(), # 关联视图类`ForgetPasswordView`,处理密码重置表单提交和验证
name='forget_password'), # 路由名称用于反向解析忘记密码页面URL
# 忘记密码验证码路由:匹配`/forget_password_code/`路径
re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(), # 关联视图类`ForgetPasswordEmailCode`,处理验证码发送逻辑
name='forget_password_code'), # 路由名称用于反向解析获取验证码的URL
]

@ -0,0 +1,55 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
# 模块级注释——accounts应用的自定义认证后端文件
# 继承Django内置ModelBackend扩展支持"用户名或邮箱"双登录方式,
# 适配项目中用户可能使用邮箱作为登录凭证的业务需求保持与Django认证系统的兼容性
class EmailOrUsernameModelBackend(ModelBackend):
"""
自定义用户认证后端继承Django内置ModelBackend
核心功能允许用户使用用户名邮箱两种方式登录
兼容Django默认认证流程不破坏原有用户模型和权限体系
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""
重写认证核心方法实现用户名/邮箱双登录逻辑
:param request: HTTP请求对象用于传递请求上下文本方法未直接使用
:param username: 登录输入的标识可能是用户名或邮箱
:param password: 登录输入的密码明文需通过check_password验证
:param kwargs: 额外关键字参数兼容Django认证系统的扩展需求
:return: 认证成功返回BlogUser对象失败返回None
"""
# 判断输入的标识是否包含@符号:含@则视为邮箱登录,否则视为用户名登录
if '@' in username:
kwargs = {'email': username} # 构造邮箱查询条件
else:
kwargs = {'username': username} # 构造用户名查询条件
try:
# 根据查询条件(用户名/邮箱)从数据库获取用户对象
# get_user_model()动态获取当前项目激活的用户模型此处为accounts.BlogUser
user = get_user_model().objects.get(**kwargs)
# 验证输入的密码是否正确check_password会自动比对明文与数据库中哈希后的密码
if user.check_password(password):
return user # 密码验证通过,返回用户对象(认证成功)
# 捕获用户不存在的异常(查询不到时触发)
except get_user_model().DoesNotExist:
return None # 用户不存在返回None认证失败
def get_user(self, username):
"""
重写用户获取方法根据用户主键pk查询用户对象
是Django认证后端必须实现的方法用于认证成功后获取完整用户信息
:param username: 实际为用户的主键IDDjango认证系统默认传递pk作为参数
:return: 查询成功返回BlogUser对象失败返回None
"""
try:
# 根据主键ID查询用户get_user_model()确保兼容自定义用户模型)
return get_user_model().objects.get(pk=username)
# 捕获用户不存在的异常
except get_user_model().DoesNotExist:
return None # 用户不存在返回None

@ -0,0 +1,79 @@
import typing
from datetime import timedelta
from django.core.cache import cache # Django缓存框架用于存储验证码内存/Redis等由项目配置决定
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _ # 国际化支持,用于生成多语言提示文本
from djangoblog.utils import send_email # 导入项目公共邮件发送工具函数
_code_ttl = timedelta(minutes=5) # 验证码有效期5分钟全局常量统一控制验证码过期时间
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""
发送密码重置验证邮件核心功能向目标邮箱发送含验证码的邮件
邮件内容支持国际化验证码有效期与全局常量`_code_ttl`保持一致5分钟
Args:
to_mail: 接收邮件的目标邮箱地址字符串类型需符合邮箱格式
code: 生成的随机验证码字符串类型用于后续密码重置验证
subject: 邮件主题可选参数默认值为国际化的"Verify Email"支持多语言切换
"""
# 构建邮件HTML内容国际化模板通过占位符注入验证码明确告知有效期
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
# 调用项目公共邮件发送函数发送验证码邮件收件人列表、主题、HTML内容
send_email([to_mail], subject, html_content)
def verify(email: str, code: str) -> typing.Optional[str]:
"""
验证验证码的有效性核心功能校验用户输入的验证码与缓存中存储的是否一致
Args:
email: 待验证的邮箱地址与验证码绑定的唯一标识确保验证码针对性
code: 用户输入的验证码需与缓存中存储的验证码比对
Return:
验证失败返回错误提示字符串支持国际化验证成功返回None
Note:
1. 原代码注释保留当前错误处理逻辑不合理建议改用raise抛出异常而非返回错误字符串
便于调用方统一捕获和处理减少错误处理冗余
2. 验证码校验逻辑通过邮箱作为缓存key获取存储的验证码与输入值直接比对大小写敏感
3. 若缓存中无该邮箱对应的验证码如过期未发送则默认返回验证码错误提示
"""
# 从缓存中获取该邮箱对应的验证码缓存key为邮箱地址value为之前存储的验证码
cache_code = get_code(email)
# 比对缓存中的验证码与用户输入的验证码,不一致则返回错误提示
if cache_code != code:
return gettext("Verification code error") # 国际化的验证码错误提示
def set_code(email: str, code: str):
"""
将邮箱与验证码绑定并存储到缓存中核心功能为后续验证提供数据支持
存储时自动设置过期时间与全局`_code_ttl`一致5分钟避免验证码永久有效
Args:
email: 验证码绑定的邮箱地址作为缓存的唯一key确保一对一关联
code: 需存储的随机验证码字符串类型建议由`generate_code`等工具函数生成
"""
# 缓存存储key=邮箱地址value=验证码timeout=有效期秒数由_timedelta转换
cache.set(email, code, _code_ttl.seconds)
def get_code(email: str) -> typing.Optional[str]:
"""
从缓存中获取指定邮箱对应的验证码核心功能为验证码校验提供数据源
Args:
email: 目标邮箱地址缓存key用于精准获取绑定的验证码
Return:
缓存中存在该邮箱对应的验证码则返回字符串类型的验证码否则返回None如过期未存储
"""
# 从缓存中获取值key为邮箱地址不存在或过期时返回None
return cache.get(email)

@ -0,0 +1,306 @@
import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth import get_user_model
from django.contrib.auth import logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
# 导入项目公共工具函数:邮件发送、加密、站点信息、验证码生成、缓存删除
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
from . import utils # 导入accounts应用自定义工具验证码发送/存储)
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm # 导入用户交互表单
from .models import BlogUser # 导入自定义用户模型
logger = logging.getLogger(__name__) # 初始化日志对象,用于记录视图层操作日志
# 模块级注释——accounts应用的核心视图文件
# 包含用户注册、登录、退出、邮箱验证、密码重置等核心业务的视图实现,
# 基于Django类视图FormView/RedirectView/View和函数视图
# 整合表单验证、邮件发送、缓存操作、权限控制等逻辑,处理用户交互的全流程
# Create your views here.
class RegisterView(FormView):
"""
用户注册视图继承Django 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):
"""
表单验证通过后的核心处理逻辑FormView的核心方法
流程创建未激活用户 生成邮箱验证链接 发送验证邮件 重定向到注册结果页
"""
if form.is_valid():
# 1. 表单验证通过先不提交到数据库commit=False后续补充字段
user = form.save(False)
user.is_active = False # 默认设置用户为未激活状态(需邮箱验证后激活)
user.source = 'Register' # 标记用户创建来源:前台注册(区别于后台创建/第三方登录)
user.save(True) # 最终保存用户数据到数据库
# 2. 生成邮箱验证链接包含站点域名、用户ID、加密签名确保链接安全性
site = get_current_site().domain # 获取当前站点域名(如"example.com"
# 双重SHA256加密结合SECRET_KEY和用户ID防止链接被篡改
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境适配DEBUG模式下使用本地测试域名127.0.0.1:8000
if settings.DEBUG:
site = '127.0.0.1:8000'
path = reverse('account:result') # 验证结果页路由
# 拼接完整的验证链接HTTP协议开发环境可用生产环境建议改为HTTPS
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 3. 构建验证邮件内容HTML格式包含验证链接
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您
<br />
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
# 发送验证邮件到用户注册邮箱
send_email(
emailto=[user.email], # 收件人(注册时填写的邮箱)
title='验证您的电子邮箱', # 邮件标题
content=content) # 邮件HTML内容
# 4. 重定向到注册结果页(告知用户验证邮件已发送)
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
# 表单验证失败(如用户名已存在、密码不匹配),重新渲染注册页面并显示错误
return self.render_to_response({'form': form})
class LogoutView(RedirectView):
"""
用户退出登录视图继承Django RedirectView专门处理重定向的类视图
核心功能执行退出登录逻辑清理缓存重定向到登录页
"""
url = '/login/' # 退出后重定向的目标URL登录页
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写分发方法添加never_cache装饰器
禁止浏览器缓存退出页避免用户后退到已登录状态
"""
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
"""
处理GET请求退出登录请求
流程执行退出登录 清理侧边栏缓存 重定向到登录页
"""
logout(request) # Django内置logout函数清除用户会话销毁登录状态
delete_sidebar_cache() # 清除侧边栏缓存(避免缓存中保留用户相关信息)
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginView(FormView):
"""
用户登录视图继承Django FormView
核心功能接收登录表单数据验证合法性执行登录逻辑处理"记住我"重定向到目标页面
"""
form_class = LoginForm # 关联自定义登录表单(适配前端样式)
template_name = 'account/login.html' # 登录页面模板路径
success_url = '/' # 登录成功后的默认重定向URL网站根目录
redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名(默认'redirect_to',用于跳转前的目标页面)
login_ttl = 2626560 # "记住我"的会话有效期2626560秒 ≈ 1个月
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写分发方法添加3个核心装饰器
1. sensitive_post_parameters('password')保护密码字段避免在错误报告中泄露
2. csrf_protect防跨站请求伪造攻击
3. never_cache禁止浏览器缓存登录页确保每次请求都是最新状态
"""
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
补充模板上下文数据传递重定向地址给模板
用于登录成功后跳转到登录前的目标页面如访问需要登录的页面时先跳转登录登录后返回原页面
"""
# 从GET请求中获取重定向地址如?redirect_to=/article/1/
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
redirect_to = '/' # 无重定向地址时,默认跳转到根目录
kwargs['redirect_to'] = redirect_to # 将重定向地址添加到上下文
return super(LoginView, self).get_context_data(** kwargs)
def form_valid(self, form):
"""
表单验证通过后的核心登录逻辑
流程验证表单 清理缓存 执行登录 处理"记住我" 重定向
"""
# 重新初始化Django内置AuthenticationForm确保使用正确的认证逻辑
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
delete_sidebar_cache() # 清理侧边栏缓存(更新用户相关的缓存数据)
logger.info(self.redirect_field_name) # 记录重定向字段名到日志
# 执行登录:将用户信息存入会话,创建登录状态
auth.login(self.request, form.get_user())
# 处理"记住我"选项若勾选则设置会话有效期为1个月
if self.request.POST.get("remember"):
self.request.session.set_expiry(self.login_ttl)
# 调用父类form_valid方法执行重定向
return super(LoginView, self).form_valid(form)
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)
# 校验重定向地址是否合法(仅允许本网站域名下的地址)
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[self.request.get_host()]):
redirect_to = self.success_url # 非法地址则使用默认重定向地址(根目录)
return redirect_to
def account_result(request):
"""
账号操作结果展示函数视图
处理两种场景1. 注册成功后的提示 2. 邮箱验证后的激活与提示
核心根据URL参数区分场景验证链接合法性激活用户渲染结果页面
"""
# 从GET请求中获取操作类型register/validation和用户ID
type = request.GET.get('type')
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':
# 场景1注册成功提示告知用户验证邮件已发送
content = '''
恭喜您注册成功一封验证邮件已经发送到您的邮箱请验证您的邮箱后登录本站
'''
title = '注册成功'
else:
# 场景2邮箱验证校验链接签名合法性激活用户
# 重新计算签名(与注册时的加密规则一致)
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign') # 从URL中获取传递的签名
# 签名不匹配则返回403禁止访问防止恶意篡改链接
if sign != c_sign:
return HttpResponseForbidden()
# 签名匹配激活用户设置is_active为True
user.is_active = True
user.save()
# 验证成功提示
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
'''
title = '验证成功'
# 渲染结果页面,传递标题和内容
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
# 非法操作类型,重定向到首页
return HttpResponseRedirect('/')
class ForgetPasswordView(FormView):
"""
忘记密码重置视图继承Django 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()
# 哈希处理新密码make_password自动使用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):
"""
忘记密码验证码发送视图继承Django View
核心功能接收邮箱 验证邮箱合法性 生成验证码 发送验证邮件 存储验证码到缓存
"""
def post(self, request: HttpRequest):
"""
处理POST请求接收邮箱发送验证码
"""
# 初始化忘记密码验证码表单,验证请求数据
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱") # 表单验证失败(如邮箱格式错误),返回错误提示
# 表单验证通过,获取目标邮箱
to_email = form.cleaned_data["email"]
# 生成随机验证码(项目工具函数)
code = generate_code()
# 发送验证码邮件调用accounts应用自定义工具函数
utils.send_verify_email(to_email, code)
# 存储验证码到缓存key=邮箱value=验证码有效期5分钟由utils模块的_code_ttl控制
utils.set_code(to_email, code)
return HttpResponse("ok") # 验证码发送成功,返回"ok"提示n
Loading…
Cancel
Save