Compare commits

...

3 Commits

Author SHA1 Message Date
sjh 96a7cf98e1 第7-8周代码注释
3 months ago
sjh 9737adf7c8 第5周
4 months ago
sjh 0c3e637d34 add new file
4 months ago

@ -0,0 +1,87 @@
# 导入Django表单基类
from django import forms
# 导入Django内置的用户管理员类用于扩展自定义用户的admin配置
from django.contrib.auth.admin import UserAdmin
# 导入Django内置的用户修改表单用于扩展自定义用户的修改表单
from django.contrib.auth.forms import UserChangeForm
# 导入Django内置的用户名字段类用于自定义用户名字段验证
from django.contrib.auth.forms import UsernameField
# 导入翻译工具,用于实现字段名称的国际化
from django.utils.translation import gettext_lazy as _
# 注册模型时需要导入自定义的用户模型
from .models import BlogUser
class BlogUserCreationForm(forms.ModelForm):
"""自定义用户创建表单用于在admin站点添加新用户时使用"""
# 密码字段,使用密码输入框(输入内容隐藏),标签支持国际化
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
# 确认密码字段,用于验证两次输入的密码是否一致
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
# 关联的模型为自定义的BlogUser
model = BlogUser
# 创建用户时需要填写的字段这里仅指定email其他字段可默认或后续补充
fields = ('email',)
def clean_password2(self):
"""验证两次输入的密码是否一致"""
# 获取清洗后的密码1和密码2
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):
"""保存用户,将密码加密处理,并设置创建来源"""
# 调用父类的save方法先不提交到数据库commit=False
user = super().save(commit=False)
# 对密码进行加密处理Django内置的密码哈希方法
user.set_password(self.cleaned_data["password1"])
# 如果需要提交到数据库
if commit:
# 设置用户的创建来源为'adminsite'表示通过admin站点创建
user.source = 'adminsite'
user.save()
return user
class BlogUserChangeForm(UserChangeForm):
"""自定义用户修改表单用于在admin站点编辑用户信息时使用"""
class Meta:
# 关联的模型为BlogUser
model = BlogUser
# 显示所有字段(可根据需要指定具体字段)
fields = '__all__'
# 指定用户名字段的处理类为UsernameField提供内置验证
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
"""初始化方法,调用父类的初始化逻辑"""
super().__init__(*args, **kwargs)
class BlogUserAdmin(UserAdmin):
"""自定义用户管理员类用于在admin站点配置BlogUser的展示和操作"""
# 指定修改用户时使用的表单
form = BlogUserChangeForm
# 指定添加用户时使用的表单
add_form = BlogUserCreationForm
# 列表页展示的字段
list_display = (
'id', # 用户ID
'nickname', # 昵称
'username', # 用户名
'email', # 邮箱
'last_login', # 最后登录时间
'date_joined', # 注册时间
'source' # 创建来源
)
# 列表页中可点击跳转详情页的字段
list_display_links = ('id', 'username')
# 列表页的排序方式按ID降序即最新创建的用户在前
ordering = ('-id',)

@ -0,0 +1,12 @@
# 从Django的apps模块导入AppConfig类用于定义应用的配置信息
from django.apps import AppConfig
class AccountsConfig(AppConfig):
"""
accounts应用的配置类用于设置应用的基本信息
继承自Django的AppConfig通过此类可以配置应用的名称默认自动生成的主键类型等元数据
"""
# 定义应用的名称Django通过此名称识别该应用与项目settings.py中INSTALLED_APPS里的配置对应
name = 'accounts'

@ -0,0 +1,153 @@
# 导入Django表单基类
from django import forms
# 导入Django用户模型工具及密码验证功能
from django.contrib.auth import get_user_model, password_validation
# 导入Django内置的认证表单登录、用户创建
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
# 导入Django验证错误类用于自定义表单验证
from django.core.exceptions import ValidationError
# 导入Django表单控件用于自定义输入框样式
from django.forms import widgets
# 导入翻译工具,实现字段名称/提示的国际化
from django.utils.translation import gettext_lazy as _
# 导入自定义工具类(可能用于验证码验证等)
from . import utils
# 导入自定义用户模型
from .models import BlogUser
class LoginForm(AuthenticationForm):
"""自定义登录表单继承自Django内置的AuthenticationForm"""
def __init__(self, *args, **kwargs):
"""初始化方法,重写父类初始化逻辑以自定义表单控件样式"""
super(LoginForm, self).__init__(*args, **kwargs)
# 自定义用户名字段的输入控件设置占位符和CSS类
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# 自定义密码字段的输入控件使用密码输入框设置占位符和CSS类
self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
class RegisterForm(UserCreationForm):
"""自定义注册表单继承自Django内置的UserCreationForm"""
def __init__(self, *args, **kwargs):
"""初始化方法,重写父类初始化逻辑以自定义表单控件样式"""
super(RegisterForm, self).__init__(*args, **kwargs)
# 自定义用户名字段控件文本输入框设置占位符和CSS类
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# 自定义邮箱字段控件邮箱输入框设置占位符和CSS类
self.fields['email'].widget = widgets.EmailInput(
attrs={'placeholder': "email", "class": "form-control"})
# 自定义密码1字段控件密码输入框设置占位符和CSS类
self.fields['password1'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
# 自定义密码2字段控件密码输入框确认密码设置占位符和CSS类
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:
# 关联的用户模型通过get_user_model获取项目配置的用户模型
model = get_user_model()
# 注册表单需填写的字段:用户名和邮箱
fields = ("username", "email")
class ForgetPasswordForm(forms.Form):
"""忘记密码表单,用于用户重置密码(包含密码重置、邮箱验证、验证码验证)"""
# 新密码字段标签国际化使用密码输入框设置CSS类和占位符
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
attrs={
"class": "form-control",
'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):
"""验证两次输入的新密码是否一致,并验证密码强度"""
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():
# 注意:此处提示可能暴露邮箱是否注册,可根据需求修改
raise ValidationError(_("email does not exist"))
return user_email
def clean_code(self):
"""验证验证码是否有效调用自定义工具类的verify方法"""
code = self.cleaned_data.get("code")
# 调用utils.verify验证邮箱和验证码是否匹配返回错误信息若有
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,77 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 以上注释为Django自动生成标识该迁移文件由Django 4.1.7版本在2023-03-02 07:14生成
# 导入Django内置的用户模型相关模块
import django.contrib.auth.models
# 导入Django内置的用户验证器
import django.contrib.auth.validators
# 从django.db导入迁移和模型相关类
from django.db import migrations, models
# 导入Django的时区工具
import django.utils.timezone
# 定义迁移类继承自migrations.Migration
class Migration(migrations.Migration):
# 标识这是初始迁移(首次创建模型的迁移)
initial = True
# 依赖的其他迁移文件这里依赖auth应用的0012_alter_user_first_name_max_length迁移
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
# 迁移操作列表,包含要执行的数据库操作
operations = [
# 创建BlogUser模型的迁移操作
migrations.CreateModel(
name='BlogUser', # 模型名称
fields=[ # 模型字段定义列表
# 自增主键字段BigAutoField适用于大数据量场景
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
# 密码字段最大长度128显示名称为'password'
('password', models.CharField(max_length=128, verbose_name='password')),
# 最后登录时间字段,可为空且允许空白,显示名称为'last login'
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
# 是否为超级用户字段默认False包含帮助文本和显示名称
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
# 用户名字段包含错误信息、帮助文本、最大长度150、唯一约束、验证器和显示名称
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
# 名字段允许空白最大长度150显示名称为'first name'
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
# 姓字段允许空白最大长度150显示名称为'last name'
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
# 邮箱字段允许空白最大长度254显示名称为'email address'
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
# 是否为管理员可登录admin站点默认False包含帮助文本和显示名称
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
# 是否激活字段默认True包含帮助文本建议通过此选项禁用账户而非删除和显示名称
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
# 加入日期字段,默认值为当前时区时间,显示名称为'date joined'
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
# 自定义昵称字段允许空白最大长度100显示名称为'昵称'
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
# 自定义创建时间字段,默认值为当前时区时间,显示名称为'创建时间'
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
# 自定义最后修改时间字段,默认值为当前时区时间,显示名称为'修改时间'
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
# 自定义创建来源字段允许空白最大长度100显示名称为'创建来源'
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
# 与auth.Group的多对多关系用于用户组权限管理包含帮助文本、关联名称和显示名称
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
# 与auth.Permission的多对多关系用于用户单独权限管理包含帮助文本、关联名称和显示名称
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={ # 模型的元数据配置
'verbose_name': '用户', # 模型的单数显示名称
'verbose_name_plural': '用户', # 模型的复数显示名称
'ordering': ['-id'], # 排序方式按id降序
'get_latest_by': 'id', # 获取最新记录的依据字段id
},
managers=[ # 模型的管理器配置
# 使用Django内置的UserManager作为模型的管理器
('objects', django.contrib.auth.models.UserManager()),
],
),
]

@ -0,0 +1,68 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
# 以上注释为Django自动生成标识该迁移文件由Django 4.2.5版本在2023-09-06 13:13生成
# 从django.db导入迁移和模型相关类用于定义数据库迁移操作
from django.db import migrations, models
# 导入Django的时区工具用于处理时间字段的默认值
import django.utils.timezone
# 定义迁移类继承自migrations.Migration用于描述数据库结构的变更
class Migration(migrations.Migration):
# 依赖的其他迁移文件依赖于accounts应用的0001_initial迁移
# 表示当前迁移需要在0001_initial迁移执行之后才能运行
dependencies = [
('accounts', '0001_initial'),
]
# 迁移操作列表,包含一系列对数据库模型的修改操作
operations = [
# 修改BlogUser模型的元数据配置
migrations.AlterModelOptions(
name='bloguser', # 目标模型名称
# 新的元数据选项:
# get_latest_by指定通过id字段获取最新记录
# ordering按id降序排序
# verbose_name/verbose_name_plural模型的显示名称单数和复数改为'user'
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
),
# 从BlogUser模型中移除'created_time'字段
migrations.RemoveField(
model_name='bloguser', # 目标模型名称
name='created_time', # 要移除的字段名
),
# 从BlogUser模型中移除'last_mod_time'字段
migrations.RemoveField(
model_name='bloguser', # 目标模型名称
name='last_mod_time', # 要移除的字段名
),
# 向BlogUser模型添加'creation_time'字段
migrations.AddField(
model_name='bloguser', # 目标模型名称
name='creation_time', # 新增字段名
# 字段类型为DateTimeField默认值为当前时区时间显示名称为'creation time'
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
# 向BlogUser模型添加'last_modify_time'字段
migrations.AddField(
model_name='bloguser', # 目标模型名称
name='last_modify_time', # 新增字段名
# 字段类型为DateTimeField默认值为当前时区时间显示名称为'last modify time'
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
# 修改BlogUser模型的'nickname'字段属性
migrations.AlterField(
model_name='bloguser', # 目标模型名称
name='nickname', # 要修改的字段名
# 字段仍为CharField允许空白最大长度100显示名称改为'nick name'
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
),
# 修改BlogUser模型的'source'字段属性
migrations.AlterField(
model_name='bloguser', # 目标模型名称
name='source', # 要修改的字段名
# 字段仍为CharField允许空白最大长度100显示名称改为'create source'
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
),
]

@ -0,0 +1,64 @@
# 导入Django内置的抽象用户模型用于扩展自定义用户功能包含基础用户名、密码等字段
from django.contrib.auth.models import AbstractUser
# 导入Django模型相关类用于定义数据库表结构
from django.db import models
# 导入reverse函数用于通过URL名称生成对应的URL路径
from django.urls import reverse
# 导入now函数用于获取当前时区的时间作为字段默认值
from django.utils.timezone import now
# 导入翻译工具,用于实现模型字段名称的国际化
from django.utils.translation import gettext_lazy as _
# 导入自定义工具函数get_current_site用于获取当前站点的域名信息
from djangoblog.utils import get_current_site
class BlogUser(AbstractUser):
"""
自定义用户模型继承自Django的AbstractUser
扩展了内置用户模型增加了昵称创建时间修改时间创建来源等自定义字段
"""
# 昵称字段支持国际化标签最大长度100允许空白不强制填写
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
# 创建时间字段支持国际化标签默认值为当前时间调用now函数
creation_time = models.DateTimeField(_('creation time'), default=now)
# 最后修改时间字段:支持国际化标签,默认值为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 创建来源字段记录用户创建的渠道如adminsite、frontend等支持国际化标签允许空白
source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
"""
定义模型实例的绝对URL标准Django方法
通过URL名称'blog:author_detail'生成用户详情页的URL参数为用户名
"""
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username}) # kwargs传递URL所需的用户名参数
def __str__(self):
"""
定义模型实例的字符串表示
当打印或引用用户实例时返回用户的邮箱地址便于识别
"""
return self.email
def get_full_url(self):
"""
生成用户详情页的完整URL包含站点域名
结合当前站点域名和get_absolute_url生成的相对路径组成完整链接
"""
# 获取当前站点的域名如www.example.com
site = get_current_site().domain
# 拼接域名和相对路径形成完整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判断ID最大的为最新

@ -0,0 +1,276 @@
# 导入Django测试所需的核心类Client模拟HTTP请求、RequestFactory创建请求对象、TestCase测试基类
from django.test import Client, RequestFactory, TestCase
# 导入reverse通过URL名称生成路径用于测试中定位接口
from django.urls import reverse
# 导入timezone处理时间相关字段用于创建测试数据
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
# 定义账户相关测试类继承Django的TestCase提供测试框架支持
class AccountTest(TestCase):
def setUp(self):
"""
测试初始化方法在每个测试方法执行前自动调用
用于创建共用的测试对象避免代码重复
"""
# 创建测试客户端用于模拟用户发送HTTP请求
self.client = Client()
# 创建请求工厂,用于生成原始请求对象(按需使用)
self.factory = RequestFactory()
# 创建普通测试用户用户名test、邮箱admin@admin.com、密码12345678
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
)
# 定义测试用的新密码,后续忘记密码测试中使用
self.new_test = "xxx123--="
def test_validate_account(self):
"""测试账户基础功能超级用户创建、登录验证、admin访问、内容创建与管理"""
# 获取当前站点域名(测试中未实际使用,保持原逻辑)
site = get_current_site().domain
# 创建超级用户拥有admin管理权限
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')
# 断言登录成功返回True
self.assertEqual(loginresult, True)
# 模拟访问admin后台页面
response = self.client.get('/admin/')
# 断言admin页面访问成功状态码200
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' # 假设'a'代表普通文章类型
article.status = 'p' # 假设'p'代表已发布状态
article.save()
# 模拟访问文章的admin管理页面通过文章模型的get_admin_url方法获取路径
response = self.client.get(article.get_admin_url())
# 断言文章管理页面访问成功状态码200
self.assertEqual(response.status_code, 200)
def test_validate_register(self):
"""测试用户注册流程:注册请求、注册后用户存在性、邮箱验证、登录与权限升级、内容管理"""
# 注册前断言邮箱为user123@user.com的用户不存在初始状态
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 模拟发送注册请求向account:register接口提交用户名、邮箱、两次一致的密码
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',
})
# 注册后断言邮箱为user123@user.com的用户存在注册成功
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))) # 双重SHA256加密签名
path = reverse('accounts:result') # 验证结果页的URL路径
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign) # 拼接完整验证URL
# 模拟访问邮箱验证链接
response = self.client.get(url)
# 断言验证页面访问成功状态码200
self.assertEqual(response.status_code, 200)
# 模拟刚注册的用户登录
self.client.login(username='user1233', password='password123!q@wE#R$T')
# 升级用户权限为超级用户便于测试admin功能
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()
# 模拟访问文章的admin管理页面
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])
# 登出后尝试访问文章admin页面应被拒绝或重定向
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'
})
# 断言登录请求响应正常(无论成功失败,状态码合法)
self.assertIn(response.status_code, [301, 302, 200])
# 错误登录后尝试访问文章admin页面应被拒绝或重定向
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)
# 验证1使用正确的邮箱和验证码断言无错误返回验证成功
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
# 验证2使用错误的邮箱与存储的验证码不匹配断言返回错误信息字符串类型
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
def test_forget_password_email_code_success(self):
"""测试获取忘记密码验证码的成功场景:提交正确邮箱,返回成功响应"""
# 模拟向account:forget_password_code接口提交正确邮箱
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
)
# 断言请求成功状态码200且返回内容为"ok"(表示验证码发送成功)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "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提交格式错误的邮箱admin@com不符合标准格式
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,
)
# 模拟发送密码重置请求
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
# 断言重置成功重定向到结果页状态码302
self.assertEqual(resp.status_code, 302)
# 验证密码是否真的修改成功:查询用户并校验新密码
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
).first() # 获取用户实例
self.assertNotEqual(blog_user, None) # 断言用户存在
# 使用check_password方法验证新密码是否匹配Django内置密码校验自动处理哈希
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,
email="123@123.com",
code="123456",
)
# 模拟发送密码重置请求
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
# 断言请求响应正常页面返回错误提示状态码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,
email=self.blog_user.email,
code="111111", # 错误的验证码
)
# 模拟发送密码重置请求
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
# 断言请求响应正常页面返回验证码错误提示状态码200
self.assertEqual(resp.status_code, 200)

@ -0,0 +1,45 @@
# 导入Django的URL路径定义工具path用于精确路径匹配re_path支持正则表达式匹配
from django.urls import path
from django.urls import re_path
# 导入当前应用accounts的视图模块包含登录、注册等业务逻辑处理
from . import views
# 导入当前应用的自定义登录表单,用于登录页面的表单渲染和验证
from .forms import LoginForm
# 定义应用的命名空间为"accounts"避免不同应用间URL名称冲突
app_name = "accounts"
# URL路由配置列表映射URL路径到对应的视图
urlpatterns = [
# 1. 登录页面URL
re_path(r'^login/$', # 正则匹配路径:以"login/"开头并结束(即精确匹配"/login/"
views.LoginView.as_view(success_url='/'), # 关联LoginView视图登录成功后重定向到网站根路径"/"
name='login', # URL的命名用于模板或视图中通过reverse('accounts:login')生成路径
kwargs={'authentication_form': LoginForm}), # 传递参数指定登录使用自定义的LoginForm表单
# 2. 注册页面URL
re_path(r'^register/$', # 正则匹配路径:精确匹配"/register/"
views.RegisterView.as_view(success_url="/"), # 关联RegisterView视图注册成功后重定向到网站根路径
name='register'), # URL命名用于反向生成注册页面路径
# 3. 登出功能URL
re_path(r'^logout/$', # 正则匹配路径:精确匹配"/logout/"
views.LogoutView.as_view(), # 关联LogoutView视图处理登出逻辑默认登出后重定向到登录页
name='logout'), # URL命名用于反向生成登出路径
# 4. 账户操作结果页URL如登录/注册/密码重置后的结果提示)
path(r'account/result.html', # 精确路径匹配:固定路径"/account/result.html"
views.account_result, # 关联普通函数视图account_result处理结果页渲染
name='result'), # URL命名用于反向生成结果页路径
# 5. 忘记密码页面URL密码重置表单页
re_path(r'^forget_password/$', # 正则匹配路径:精确匹配"/forget_password/"
views.ForgetPasswordView.as_view(), # 关联ForgetPasswordView视图处理密码重置表单逻辑
name='forget_password'), # URL命名用于反向生成忘记密码页面路径
# 6. 获取忘记密码验证码的URL发送验证码到邮箱
re_path(r'^forget_password_code/$', # 正则匹配路径:精确匹配"/forget_password_code/"
views.ForgetPasswordEmailCode.as_view(), # 关联ForgetPasswordEmailCode视图处理发送验证码逻辑
name='forget_password_code'), # URL命名用于反向生成获取验证码的路径
]

@ -0,0 +1,53 @@
# 导入Django的用户模型工具用于获取项目配置的用户模型支持自定义用户模型
from django.contrib.auth import get_user_model
# 导入Django内置的模型认证后端基类用于扩展自定义认证逻辑
from django.contrib.auth.backends import ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
"""
自定义认证后端继承自Django的ModelBackend
功能允许用户使用用户名或邮箱地址进行登录验证
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""
重写认证方法实现用户名/邮箱登录逻辑
:param request: 请求对象
:param username: 登录时输入的标识可能是用户名或邮箱
:param password: 登录密码
:param kwargs: 其他关键字参数
:return: 验证成功返回用户对象失败返回None
"""
# 判断输入的"username"是否包含@符号,若包含则视为邮箱登录
if '@' in username:
# 构建查询条件使用email字段匹配
kwargs = {'email': username}
else:
# 否则视为用户名登录构建查询条件使用username字段匹配
kwargs = {'username': username}
try:
# 根据构建的条件查询用户(使用项目配置的用户模型)
user = get_user_model().objects.get(**kwargs)
# 验证查询到的用户密码是否正确Django内置的密码校验自动处理哈希对比
if user.check_password(password):
# 密码正确,返回用户对象
return user
except get_user_model().DoesNotExist:
# 若用户不存在查询失败返回None表示认证失败
return None
def get_user(self, username):
"""
重写获取用户的方法根据用户主键获取用户对象
Django认证系统会调用此方法获取已认证用户的详细信息
:param username: 实际为用户的主键pk
:return: 存在则返回用户对象不存在返回None
"""
try:
# 根据主键查询用户
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
# 用户不存在返回None
return None

@ -0,0 +1,81 @@
# 导入类型提示模块,用于定义函数参数和返回值的类型(增强代码可读性和类型检查)
import typing
# 导入timedelta用于定义时间间隔此处用于设置验证码有效期
from datetime import timedelta
# 导入Django缓存模块用于临时存储验证码避免数据库频繁读写
from django.core.cache import cache
# 导入Django翻译工具gettext用于实时翻译字符串gettext_lazy用于延迟翻译适合定义常量时使用
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
# 导入项目自定义的发送邮件工具函数,用于实际发送验证码邮件
from djangoblog.utils import send_email
# 定义验证码的有效期5分钟全局常量所有验证码共用此有效期
_code_ttl = timedelta(minutes=5)
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""
发送密码重置验证邮件将验证码通过邮件发送给指定邮箱
Args:
to_mail: 接收邮件的目标邮箱地址字符串类型
code: 生成的验证码字符串类型用于后续验证
subject: 邮件主题默认值为"Verify Email"支持国际化可根据语言设置自动翻译
"""
# 构造邮件的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}
# 调用自定义的send_email函数发送邮件参数为收件人列表、邮件主题、邮件内容
send_email([to_mail], subject, html_content)
def verify(email: str, code: str) -> typing.Optional[str]:
"""
验证用户输入的验证码是否有效与缓存中存储的验证码对比
Args:
email: 用户提交的邮箱地址用于匹配缓存中对应的验证码
code: 用户输入的验证码需要验证的字符串
Return:
验证失败时返回错误提示字符串"Verification code error"验证成功时返回None
Note:
原代码注释说明当前错误处理方式不合理建议通过raise抛出异常替代返回错误字符串
避免调用方需要额外处理返回的错误信息使错误处理更符合Python规范
"""
# 从缓存中获取该邮箱对应的验证码调用get_code函数
cache_code = get_code(email)
# 对比用户输入的验证码与缓存中的验证码,不一致则返回错误提示
if cache_code != code:
return gettext("Verification code error")
def set_code(email: str, code: str):
"""
将邮箱与对应的验证码存储到Django缓存中并设置过期时间使用全局的_code_ttl
Args:
email: 作为缓存键key的邮箱地址确保每个邮箱的验证码唯一
code: 作为缓存值value的验证码需要存储的字符串
"""
# 调用cache.set存储数据key=emailvalue=codetimeout=_code_ttl.seconds有效期转换为秒数
cache.set(email, code, _code_ttl.seconds)
def get_code(email: str) -> typing.Optional[str]:
"""
根据邮箱地址从Django缓存中获取对应的验证码
Args:
email: 用于查询的缓存键key即目标邮箱地址
Return:
缓存中存在该邮箱对应的验证码时返回字符串类型的验证码不存在或过期时返回None
"""
# 调用cache.get获取缓存值key=email不存在则返回None
return cache.get(email)

@ -0,0 +1,330 @@
# 导入日志模块,用于记录视图操作中的关键信息(如请求类型、用户状态等)
import logging
# 导入Django国际化翻译工具用于视图中文本的多语言支持
from django.utils.translation import gettext_lazy as _
# 导入Django项目配置用于获取SECRET_KEY等全局设置
from django.conf import settings
# 导入Django认证相关模块auth处理登录逻辑REDIRECT_FIELD_NAME定义重定向参数名get_user_model获取自定义用户模型
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 # 登出功能函数
# 导入Django内置认证表单用于登录表单的基础验证
from django.contrib.auth.forms import AuthenticationForm
# 导入密码哈希工具,用于生成加密后的密码(忘记密码功能中更新密码时使用)
from django.contrib.auth.hashers import make_password
# 导入DjangoHTTP响应类重定向、403禁止访问、基础响应
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
# 导入Django快捷函数get_object_or_404获取对象不存在时返回404、render渲染模板
from django.shortcuts import get_object_or_404
from django.shortcuts import render
# 导入reverse函数通过URL名称生成路径避免硬编码路径
from django.urls import reverse
# 导入Django视图装饰器method_decorator为类视图方法添加装饰器、never_cache禁止缓存、csrf_protectCSRF保护、sensitive_post_parameters保护敏感POST参数
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme # 验证重定向地址是否安全
# 导入Django类视图基类View基础视图类、FormView处理表单的视图类、RedirectView处理重定向的视图类
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
# 导入项目自定义工具函数发送邮件、SHA256加密、获取当前站点、生成验证码、删除侧边栏缓存
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
# 导入当前应用accounts的工具函数验证码相关操作
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 # 关联的表单类自定义的RegisterForm
template_name = 'account/registration_form.html' # 渲染的模板文件路径
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
"""
重写dispatch方法为视图添加CSRF保护装饰器
dispatch是类视图的入口方法所有请求GET/POST都会先经过此方法
"""
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
"""
表单数据验证通过后执行的逻辑用户提交的注册信息合法时
"""
if form.is_valid():
# 1. 创建用户但不立即提交到数据库commit=False后续手动设置额外字段
user = form.save(False)
# 2. 设置用户初始状态:未激活(需邮箱验证后激活)、注册来源为"Register"
user.is_active = False
user.source = 'Register'
# 3. 提交用户数据到数据库
user.save(True)
# 4. 生成邮箱验证链接包含当前站点域名、验证路径、用户ID、加密签名
site = get_current_site().domain # 获取当前站点域名如www.example.com
# 双重SHA256加密使用SECRET_KEY+用户ID生成签名防止链接被篡改
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境DEBUG=True站点域名替换为本地地址127.0.0.1:8000
if settings.DEBUG:
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)
# 5. 构造验证邮件内容(包含验证链接)
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您
<br />
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
# 6. 发送验证邮件到用户注册邮箱
send_email(
emailto=[user.email], # 收件人列表
title='验证您的电子邮箱', # 邮件标题
content=content # 邮件HTML内容
)
# 7. 重定向到注册结果页携带注册成功的类型和用户ID参数
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
# 表单验证失败(如用户名已存在、邮箱格式错误),重新渲染表单并显示错误信息
return self.render_to_response({'form': form})
class LogoutView(RedirectView):
"""
处理用户登出的类视图继承自RedirectView专门处理重定向的视图基类
功能执行登出逻辑删除侧边栏缓存重定向到登录页
"""
url = '/login/' # 登出后默认重定向的目标路径(登录页)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写dispatch方法添加禁止缓存装饰器
避免浏览器缓存登出页面防止用户后退到已登出的页面
"""
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
"""
处理GET请求用户访问登出URL时
"""
# 1. 执行Django内置的登出函数清除用户的session信息
logout(request)
# 2. 删除侧边栏缓存(可能存储了用户相关信息,登出后需更新)
delete_sidebar_cache()
# 3. 调用父类的get方法执行重定向到登录页
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginView(FormView):
"""
处理用户登录的类视图继承自FormView
功能展示登录表单验证登录信息处理"记住我"功能重定向到目标页面
"""
form_class = LoginForm # 关联的表单类自定义的LoginForm
template_name = 'account/login.html' # 渲染的登录模板路径
success_url = '/' # 登录成功后的默认重定向路径(网站根目录)
redirect_field_name = REDIRECT_FIELD_NAME # 重定向参数名(默认是"next"
login_ttl = 2626560 # 记住登录状态的有效期(秒),约等于一个月
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
"""
重写dispatch方法添加三个装饰器
1. sensitive_post_parameters('password')保护密码参数不在错误日志中显示
2. csrf_protect开启CSRF保护防止跨站请求伪造
3. never_cache禁止缓存登录页面确保每次访问都是最新状态
"""
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
扩展上下文数据将重定向地址next参数传递到模板中
模板中可根据该参数决定登录成功后跳转到哪里
"""
# 从GET请求中获取重定向地址如访问需要登录的页面时会携带next参数
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):
"""
表单数据验证通过后执行的逻辑用户提交的登录信息合法时
注意此处重新初始化了AuthenticationForm用于Django内置的登录验证
"""
# 用请求的POST数据和request对象初始化Django内置的认证表单
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
# 1. 登录成功,删除侧边栏缓存(可能存储了未登录状态的内容)
delete_sidebar_cache()
# 2. 记录重定向参数名到日志(用于调试)
logger.info(self.redirect_field_name)
# 3. 执行Django内置的登录函数将用户信息存入session
auth.login(self.request, form.get_user())
# 4. 处理"记住我"功能:如果用户勾选了"remember"设置session有效期
if self.request.POST.get("remember"):
self.request.session.set_expiry(self.login_ttl)
# 5. 调用父类的form_valid方法执行重定向到成功页面
return super(LoginView, self).form_valid(form)
else:
# 表单验证失败(如用户名不存在、密码错误),重新渲染表单并显示错误
return self.render_to_response({'form': form})
def get_success_url(self):
"""
自定义登录成功后的重定向地址
优先使用POST请求中的"next"参数如果合法否则使用默认的success_url
"""
# 从POST请求中获取重定向地址用户登录时提交的next参数
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):
"""
处理账户操作结果的函数视图注册成功提示邮箱验证结果
功能根据URL参数type和id展示不同的结果信息激活用户邮箱
"""
# 从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)
# 如果用户已激活is_active=True直接重定向到根目录无需再展示结果
if user.is_active:
return HttpResponseRedirect('/')
# 如果操作类型合法属于register或validation处理对应的逻辑
if type and type in ['register', 'validation']:
if type == 'register':
# 1. 注册成功场景:展示注册成功提示,告知用户验证邮件已发送
content = '''
恭喜您注册成功一封验证邮件已经发送到您的邮箱请验证您的邮箱后登录本站
'''
title = '注册成功'
else:
# 2. 邮箱验证场景:验证签名是否合法,合法则激活用户
# 重新生成签名用于与请求中的sign参数对比
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 从GET请求中获取签名参数
sign = request.GET.get('sign')
# 如果签名不匹配链接被篡改返回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):
"""
处理忘记密码的类视图继承自FormView
功能展示忘记密码表单验证表单数据验证码邮箱密码更新用户密码
"""
form_class = ForgetPasswordForm # 关联的表单类自定义的ForgetPasswordForm
template_name = 'account/forget_password.html' # 渲染的忘记密码模板路径
def form_valid(self, form):
"""
表单数据验证通过后执行的逻辑验证码邮箱密码均合法时
"""
if form.is_valid():
# 1. 根据表单中的邮箱查询对应的用户
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
# 2. 用新密码(加密处理)更新用户密码
blog_user.password = make_password(form.cleaned_data["new_password2"])
# 3. 保存密码更新结果到数据库
blog_user.save()
# 4. 重定向到登录页(密码重置成功后需重新登录)
return HttpResponseRedirect('/login/')
else:
# 表单验证失败(如验证码错误、密码不一致),重新渲染表单并显示错误
return self.render_to_response({'form': form})
class ForgetPasswordEmailCode(View):
"""
处理发送忘记密码验证码的类视图继承自基础View类
功能接收邮箱验证邮箱格式生成验证码发送验证邮件存储验证码到缓存
"""
def post(self, request: HttpRequest):
"""
处理POST请求用户提交邮箱以获取验证码时
"""
# 1. 用请求的POST数据初始化验证码表单
form = ForgetPasswordCodeForm(request.POST)
# 2. 验证表单(主要验证邮箱格式是否合法)
if not form.is_valid():
# 表单验证失败(邮箱格式错误),返回"错误的邮箱"响应
return HttpResponse("错误的邮箱")
# 3. 表单验证通过,获取清洗后的邮箱地址
to_email = form.cleaned_data["email"]
# 4. 生成随机验证码
code = generate_code()
# 5. 发送验证码邮件到用户邮箱调用utils中的send_verify_email函数
utils.send_verify_email(to_email, code)
# 6. 将验证码存储到缓存(关联邮箱,设置有效期)
utils.set_code(to_email, code)
# 7. 发送成功,返回"ok"响应
return HttpResponse("ok")
Loading…
Cancel
Save