Merge remote-tracking branch 'origin/shw_branch' into develop

# Conflicts:
#	src/DjangoBlog/accounts/forms.py
#	src/DjangoBlog/accounts/migrations/0001_initial.py
#	src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
#	src/DjangoBlog/accounts/models.py
#	src/DjangoBlog/accounts/tests.py
#	src/DjangoBlog/accounts/views.py
develop
dynastxu 3 months ago
commit aadcbfbfc3

3
.gitmodules vendored

@ -0,0 +1,3 @@
[submodule "src/DjangoBlog"]
path = src/DjangoBlog
url = https://github.com/ETOofficial/DjangoBlog.git

@ -1,60 +1,87 @@
#shw 导入Django的表单模块
from django import forms
#shw 导入Django后台管理的基础用户管理类
from django.contrib.auth.admin import UserAdmin
#shw 导入Django后台用于修改用户信息的表单
from django.contrib.auth.forms import UserChangeForm
#shw 导入Django后台用于用户名的字段类
from django.contrib.auth.forms import UsernameField
#shw 导入Django的国际化和翻译工具
from django.utils.translation import gettext_lazy as _
# Register your models here.
#shw 注册你的模型到这里(这是一个注释提示,实际注册在文件末尾)
from .models import BlogUser
class BlogUserCreationForm(forms.ModelForm):
#shw 自定义用户创建表单用于在Django Admin后台添加新用户。
#shw 它继承自 ModelForm并增加了密码输入和确认的逻辑。
#shw 定义第一个密码字段使用PasswordInput控件隐藏输入内容
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
#shw 定义第二个密码字段,用于确认密码
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
model = BlogUser
fields = ('email',)
#shw Meta类用于配置表单与模型的关联
model = BlogUser #shw 指定该表单对应的模型是 BlogUser
fields = ('email',) #shw 在创建用户时,除了密码外,只显示邮箱字段
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
#shw 自定义验证方法,用于检查两次输入的密码是否一致
#shw Check that the two password entries match
password1 = self.cleaned_data.get("password1") #shw 从已清洗的数据中获取第一个密码
password2 = self.cleaned_data.get("password2") #shw 从已清洗的数据中获取第二个密码
#shw 如果两个密码都存在且不相等,则抛出验证错误
if password1 and password2 and password1 != password2:
raise forms.ValidationError(_("passwords do not match"))
return password2
return password2 #shw 返回第二个密码作为清洗后的数据
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
#shw 重写save方法以便在保存用户时处理密码哈希
#shw Save the provided password in hashed format
user = super().save(commit=False) #shw 调用父类的save方法但先不提交到数据库commit=False
user.set_password(self.cleaned_data["password1"]) #shw 使用Django的set_password方法将明文密码加密后存储
if commit:
user.source = 'adminsite'
user.save()
return user
user.source = 'adminsite' #shw 如果决定提交,则设置用户的来源为 'adminsite'
user.save() #shw 将用户对象保存到数据库
return user #shw 返回保存后的用户对象
class BlogUserChangeForm(UserChangeForm):
#shw 自定义用户修改表单用于在Django Admin后台编辑现有用户信息。
#shw 它继承自Django的UserChangeForm以复用大部分功能。
class Meta:
model = BlogUser
fields = '__all__'
#shw Meta类用于配置表单与模型的关联
model = BlogUser #shw 指定该表单对应的模型是 BlogUser
fields = '__all__' #shw 在修改用户时,显示模型中的所有字段
#shw 指定 'username' 字段使用的字段类为 UsernameField
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#shw 重写初始化方法,可以在这里添加自定义的初始化逻辑
super().__init__(*args, **kwargs) #shw 调用父类的初始化方法
class BlogUserAdmin(UserAdmin):
form = BlogUserChangeForm
add_form = BlogUserCreationForm
#shw 自定义用户管理类用于在Django Admin后台定制 BlogUser 模型的显示和操作方式。
#shw 它继承自Django的UserAdmin以复用用户管理的标准功能。
form = BlogUserChangeForm #shw 指定修改用户时使用的表单
add_form = BlogUserCreationForm #shw 指定添加用户时使用的表单
#shw 定义在用户列表页面显示的列
list_display = (
'id',
'nickname',
'username',
'email',
'last_login',
'date_joined',
'source')
'id', #shw 用户ID
'nickname', #shw 昵称
'username', #shw 用户名
'email', #shw 邮箱
'last_login', #shw 最后登录时间
'date_joined', #shw 注册时间
'source') #shw 创建来源
#shw 定义在列表页面中,哪些列可以点击进入编辑页面
list_display_links = ('id', 'username')
#shw 定义用户列表的默认排序方式按ID降序排列
ordering = ('-id',)
#shw 定义搜索框可以搜索的字段
search_fields = ('username', 'nickname', 'email')

@ -1,5 +1,11 @@
#shw 导入Django的应用配置基类
from django.apps import AppConfig
class AccountsConfig(AppConfig):
#shw 这是accounts应用的配置类。
#shw 它用于定义该应用的各种元数据和行为。
#shw 指定这个配置类所属的应用的完整Python路径。
#shw Django通过这个name来找到并加载这个应用。
name = 'accounts'

@ -1,27 +1,45 @@
#shw 导入Django的表单模块
from django import forms
#shw 导入获取当前激活用户模型的函数
from django.contrib.auth import get_user_model, password_validation
#shw 导入Django内置的认证表单和用户创建表单
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
#shw 导入Django的验证错误类
from django.core.exceptions import ValidationError
#shw 导入Django的表单控件模块
from django.forms import widgets
#shw 导入Django的国际化和翻译工具
from django.utils.translation import gettext_lazy as _
from . import utils
#shw 导入本地的BlogUser模型
from .models import BlogUser
class LoginForm(AuthenticationForm):
#shw 自定义登录表单继承自Django的AuthenticationForm。
#shw 主要用于自定义前端显示的样式。
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
#shw 重写初始化方法,以自定义字段的控件属性。
super(LoginForm, self).__init__(*args, **kwargs) #shw 调用父类的初始化方法
#shw 自定义 'username' 字段的控件添加placeholder和CSS类
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
#shw 自定义 'password' 字段的控件添加placeholder和CSS类
self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
class RegisterForm(UserCreationForm):
#shw 自定义注册表单继承自Django的UserCreationForm。
#shw 增加了邮箱唯一性验证和前端样式自定义。
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
#shw 重写初始化方法,以自定义字段的控件属性。
super(RegisterForm, self).__init__(*args, **kwargs) #shw 调用父类的初始化方法
#shw 为各个字段添加Bootstrap风格的CSS类和placeholder
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
self.fields['email'].widget = widgets.EmailInput(
@ -32,17 +50,23 @@ class RegisterForm(UserCreationForm):
attrs={'placeholder': "repeat password", "class": "form-control"})
def clean_email(self):
email = self.cleaned_data['email']
#shw 自定义邮箱字段的验证方法,确保邮箱在系统中是唯一的。
email = self.cleaned_data['email'] #shw 获取清洗后的邮箱数据
#shw 检查数据库中是否已存在该邮箱
if get_user_model().objects.filter(email=email).exists():
raise ValidationError(_("email already exists"))
return email
raise ValidationError(_("email already exists")) #shw 如果存在,抛出验证错误
return email #shw 返回清洗后的邮箱
class Meta:
model = get_user_model()
fields = ("username", "email")
#shw Meta类用于配置表单与模型的关联
model = get_user_model() #shw 动态获取用户模型而不是硬编码BlogUser更具可复用性
fields = ("username", "email") #shw 指定注册表单中显示的字段
class ForgetPasswordForm(forms.Form):
#shw 忘记密码/重置密码表单继承自基础的Form类。
#shw 它不直接与模型关联,用于处理通过邮箱和验证码重置密码的流程。
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
@ -54,7 +78,7 @@ class ForgetPasswordForm(forms.Form):
)
new_password2 = forms.CharField(
label="确认密码",
label="确认密码", #shw 这里使用了中文硬编码,建议使用 _("Confirm password") 以支持国际化
widget=forms.PasswordInput(
attrs={
"class": "form-control",
@ -64,7 +88,7 @@ class ForgetPasswordForm(forms.Form):
)
email = forms.EmailField(
label='邮箱',
label='邮箱', #shw 这里使用了中文硬编码,建议使用 _("Email")
widget=forms.TextInput(
attrs={
'class': 'form-control',
@ -84,35 +108,47 @@ class ForgetPasswordForm(forms.Form):
)
def clean_new_password2(self):
password1 = self.data.get("new_password1")
password2 = self.data.get("new_password2")
#shw 自定义验证方法,检查两次输入的新密码是否一致,并验证密码强度。
password1 = self.data.get("new_password1") #shw 从原始数据中获取密码1
password2 = self.data.get("new_password2") #shw 从原始数据中获取密码2
#shw 检查两次密码是否一致
if password1 and password2 and password1 != password2:
raise ValidationError(_("passwords do not match"))
#shw 使用Django内置的密码验证器来检查密码强度
password_validation.validate_password(password2)
return password2
return password2 #shw 返回验证通过的新密码
def clean_email(self):
user_email = self.cleaned_data.get("email")
#shw 自定义验证方法,检查输入的邮箱是否存在于数据库中。
user_email = self.cleaned_data.get("email") #shw 获取清洗后的邮箱
#shw 检查该邮箱是否已注册
if not BlogUser.objects.filter(
email=user_email
).exists():
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
#shwtodo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
#shw 这是一个安全提示,直接告诉攻击者邮箱未注册可能会被利用。
raise ValidationError(_("email does not exist"))
return user_email
return user_email #shw 返回清洗后的邮箱
def clean_code(self):
code = self.cleaned_data.get("code")
#shw 自定义验证方法,验证邮箱验证码是否正确。
code = self.cleaned_data.get("code") #shw 获取清洗后的验证码
#shw 调用工具函数验证邮箱和验证码是否匹配
error = utils.verify(
email=self.cleaned_data.get("email"),
code=code,
)
#shw 如果工具函数返回错误信息,则抛出验证错误
if error:
raise ValidationError(error)
return code
return code #shw 返回验证通过的验证码
class ForgetPasswordCodeForm(forms.Form):
#shw 发送忘记密码验证码的表单。
#shw 它只包含一个邮箱字段,用于用户输入接收验证码的邮箱地址。
email = forms.EmailField(
label=_('Email'),
label=_('Email'), #shw 邮箱字段,标签支持国际化
)

@ -1,36 +1,55 @@
#shw 导入Django内置的抽象用户模型基类
from django.contrib.auth.models import AbstractUser
#shw 导入Django的数据库模型模块
from django.db import models
#shw 导入Django的URL反向解析函数
from django.urls import reverse
#shw 导入Django的时区工具用于获取当前时间
from django.utils.timezone import now
#shw 导入Django的国际化和翻译工具
from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
# Create your models here.
#shw 在这里创建你的模型。
class BlogUser(AbstractUser):
#shw 自定义用户模型继承自Django的AbstractUser。
#shw 它扩展了默认用户模型,增加了博客系统所需的额外字段。
#shw 用户昵称字段,可为空
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
#shw 用户创建时间字段,默认为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now)
#shw 用户最后修改时间字段,默认为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
#shw 用户创建来源字段(如:'adminsite', 'register'),可为空
source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
#shw 定义获取用户详情页绝对路径的方法。
#shw Django Admin和其他地方会使用这个方法来获取对象的URL。
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
'author_name': self.username}) #shw 反向解析到博客应用的作者详情页URL参数为用户名
def __str__(self):
return self.email
#shw 定义对象的字符串表示形式。
#shw 在Django Admin或打印对象时会显示这个字符串。
return self.email #shw 返回用户的邮箱作为其字符串表示
def get_full_url(self):
site = get_current_site().domain
#shw 定义获取用户详情页完整URL包含域名的方法。
site = get_current_site().domain #shw 获取当前站点的域名
#shw 拼接协议、域名和绝对路径形成完整的URL
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
return url
class Meta:
ordering = ['-id']
verbose_name = _('user')
verbose_name_plural = verbose_name
get_latest_by = 'id'
#shw Meta类用于定义模型的元数据选项。
ordering = ['-id'] #shw 默认按ID降序排列
verbose_name = _('user') #shw 在Django Admin中显示的单数名称支持国际化
verbose_name_plural = verbose_name #shw 在Django Admin中显示的复数名称
get_latest_by = 'id' #shw 当使用 .latest() 方法时,默认按 'id' 字段查找

@ -1,25 +1,38 @@
#shw 导入Django的测试客户端、请求工厂和测试用例基类
from django.test import Client, RequestFactory, TestCase
#shw 导入Django的URL反向解析函数
from django.urls import reverse
#shw 导入Django的时区工具
from django.utils import timezone
#shw 导入本地的BlogUser模型
from accounts.models import BlogUser
#shw 导入博客应用的Article和Category模型
from blog.models import Article, Category
#shw 从项目工具模块导入所有函数
from djangoblog.utils import *
#shw 导入本地的工具模块
from . import utils
# Create your tests here.
#shw 在这里创建你的测试。
class AccountTest(TestCase):
#shw 账户应用的测试用例集继承自Django的TestCase。
#shw TestCase提供了数据库事务回滚和客户端模拟等功能。
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
#shw 每个测试方法执行前都会运行的初始化方法。
#shw 用于创建测试所需的公共数据和环境。
self.client = Client() #shw 创建一个模拟的HTTP客户端用于发送请求
self.factory = RequestFactory() #shw 创建一个请求工厂,用于生成请求对象
#shw 创建一个普通用户用于测试
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
)
self.new_test = "xxx123--="
self.new_test = "xxx123--=" #shw 定义一个测试用的新密码
def test_validate_account(self):
get_current_site().domain
@ -27,21 +40,24 @@ class AccountTest(TestCase):
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="qwer!@#$ggg")
BlogUser.objects.get(username='liangliangyy1')
BlogUser.objects.get(username='liangliangyy1') #shw 从数据库中获取刚创建的超级用户
#shw 使用client模拟登录
loginresult = self.client.login(
username='liangliangyy1',
password='qwer!@#$ggg')
self.assertEqual(loginresult, True)
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
self.assertEqual(loginresult, True) #shw 断言登录成功
response = self.client.get('/admin/') #shw 模拟访问后台管理页面
self.assertEqual(response.status_code, 200) #shw 断言访问成功状态码为200
#shw 创建一个文章分类用于测试
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
#shw 创建一篇文章用于测试
article = Article()
article.title = "nicetitleaaa"
article.body = "nicecontentaaa"
@ -51,10 +67,13 @@ class AccountTest(TestCase):
article.status = 'p'
article.save()
#shw 模拟访问文章的后台编辑页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200) #shw 断言访问成功
def test_validate_register(self):
#shw 测试用户注册、邮箱验证、登录、登出等一系列流程。
#shw 断言注册前,数据库中不存在该邮箱的用户
self.assertEquals(
0, len(
BlogUser.objects.filter(
@ -65,24 +84,30 @@ class AccountTest(TestCase):
'password1': 'password123!q@wE#R$T',
'password2': 'password123!q@wE#R$T',
})
#shw 断言注册后,数据库中存在该邮箱的用户
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
user = BlogUser.objects.filter(email='user123@user.com')[0]
user = BlogUser.objects.filter(email='user123@user.com')[0] #shw 获取新注册的用户
#shw 生成用于邮箱验证的签名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result')
path = reverse('accounts:result') #shw 获取验证结果页面的URL路径
#shw 构造完整的验证URL
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)
response = self.client.get(url) #shw 模拟用户点击邮箱中的验证链接
self.assertEqual(response.status_code, 200) #shw 断言访问成功
#shw 模拟用户登录
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 = BlogUser.objects.filter(email='user123@user.com')[0] #shw 重新获取用户对象
user.is_superuser = True #shw 将用户提升为超级用户,以便访问后台
user.is_staff = True
user.save()
delete_sidebar_cache()
delete_sidebar_cache() #shw 删除侧边栏缓存
#shw 创建测试用的分类和文章
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
@ -94,66 +119,79 @@ class AccountTest(TestCase):
article.title = "nicetitle333"
article.body = "nicecontentttt"
article.author = user
article.type = 'a'
article.status = 'p'
article.save()
#shw 登录状态下访问文章后台页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
#shw 模拟用户登出
response = self.client.get(reverse('account:logout'))
self.assertIn(response.status_code, [301, 302, 200])
self.assertIn(response.status_code, [301, 302, 200]) #shw 断言登出成功重定向或OK
#shw 登出后再次访问文章后台页面
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
self.assertIn(response.status_code, [301, 302, 200]) #shw 断言被重定向到登录页
#shw 模拟使用错误的密码登录
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
'password': 'password123' #shw 错误的密码
})
self.assertIn(response.status_code, [301, 302, 200])
self.assertIn(response.status_code, [301, 302, 200]) #shw 断言登录失败,页面重定向
#shw 登录失败后访问文章后台页面
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
self.assertIn(response.status_code, [301, 302, 200]) #shw 断言依然无法访问
def test_verify_email_code(self):
#shw 测试邮箱验证码的生成、发送和验证功能。
to_email = "admin@admin.com"
code = generate_code()
utils.set_code(to_email, code)
utils.send_verify_email(to_email, code)
code = generate_code() #shw 生成一个验证码
utils.set_code(to_email, code) #shw 将验证码与邮箱关联(通常是存入缓存或数据库)
utils.send_verify_email(to_email, code) #shw 发送验证码邮件(测试环境中可能不会真的发送)
#shw 使用正确的邮箱和验证码进行验证
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
self.assertEqual(err, None) #shw 断言验证成功,无错误信息返回
#shw 使用错误的邮箱进行验证
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
self.assertEqual(type(err), str) #shw 断言验证失败,返回一个字符串类型的错误信息
def test_forget_password_email_code_success(self):
#shw 测试成功发送忘记密码验证码的场景。
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
data=dict(email="admin@admin.com") #shw 使用一个已存在的邮箱
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
self.assertEqual(resp.status_code, 200) #shw 断言请求成功
self.assertEqual(resp.content.decode("utf-8"), "ok") #shw 断言返回内容为"ok"
def test_forget_password_email_code_fail(self):
#shw 测试发送忘记密码验证码失败的场景(如邮箱格式错误)。
#shw 测试不提供邮箱的情况
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #shw 断言返回错误提示
#shw 测试提供格式错误的邮箱的情况
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com")
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #shw 断言返回错误提示
def test_forget_password_email_success(self):
code = generate_code()
utils.set_code(self.blog_user.email, code)
#shw 测试成功重置密码的场景。
code = generate_code() #shw 生成一个验证码
utils.set_code(self.blog_user.email, code) #shw 为测试用户设置验证码
#shw 构造重置密码的请求数据
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
@ -164,20 +202,21 @@ class AccountTest(TestCase):
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.status_code, 302) #shw 断言请求成功并重定向
# 验证用户密码是否修改成功
#shw 验证用户密码是否真的被修改了
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) #shw 断言用户依然存在
self.assertEqual(blog_user.check_password(data["new_password1"]), True) #shw 断言新密码是正确的
def test_forget_password_email_not_user(self):
#shw 测试重置一个不存在用户的密码的场景。
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email="123@123.com",
email="123@123.com", #shw 使用一个不存在的邮箱
code="123456",
)
resp = self.client.post(
@ -185,22 +224,23 @@ class AccountTest(TestCase):
data=data
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.status_code, 200) #shw 断言请求未重定向,停留在原页面并显示错误
def test_forget_password_email_code_error(self):
code = generate_code()
utils.set_code(self.blog_user.email, code)
#shw 测试使用错误验证码重置密码的场景。
code = generate_code() #shw 生成一个验证码
utils.set_code(self.blog_user.email, code) #shw 为测试用户设置验证码
#shw 构造重置密码的请求数据,但验证码是错误的
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code="111111",
code="111111", #shw 错误的验证码
)
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.status_code, 200) #shw 断言请求未重定向,停留在原页面并显示错误

@ -1,28 +1,44 @@
from django.urls import path
from django.urls import re_path
#shw 导入Django的path和re_path函数用于定义URL路由
from django.urls import path, re_path
#shw 导入本地的视图模块
from . import views
#shw 导入本地的LoginForm表单用于传递给登录视图
from .forms import LoginForm
#shw 定义应用的命名空间用于在模板中反向解析URL如 {% url 'accounts:login' %}
app_name = "accounts"
urlpatterns = [re_path(r'^login/$',
views.LoginView.as_view(success_url='/'),
name='login',
kwargs={'authentication_form': LoginForm}),
re_path(r'^register/$',
views.RegisterView.as_view(success_url="/"),
name='register'),
re_path(r'^logout/$',
views.LogoutView.as_view(),
name='logout'),
path(r'account/result.html',
views.account_result,
name='result'),
re_path(r'^forget_password/$',
views.ForgetPasswordView.as_view(),
name='forget_password'),
re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'),
]
#shw 定义URL模式列表
urlpatterns = [
#shw 定义登录页面的URL路由
re_path(r'^login/$',
views.LoginView.as_view(success_url='/'), #shw 关联到LoginView类视图并指定登录成功后重定向到根路径
name='login', #shw 为这个URL模式命名为 'login'
kwargs={'authentication_form': LoginForm}), #shw 向LoginView传递额外的关键字参数指定使用自定义的LoginForm
#shw 定义注册页面的URL路由
re_path(r'^register/$',
views.RegisterView.as_view(success_url="/"), #shw 关联到RegisterView类视图并指定注册成功后重定向到根路径
name='register'), #shw 命名为 'register'
#shw 定义登出页面的URL路由
re_path(r'^logout/$',
views.LogoutView.as_view(), #shw 关联到LogoutView类视图
name='logout'), #shw 命名为 'logout'
#shw 定义注册/操作结果页面的URL路由
path(r'account/result.html',
views.account_result, #shw 关联到account_result函数视图
name='result'), #shw 命名为 'result'
#shw 定义忘记密码页面的URL路由用于输入新密码和验证码
re_path(r'^forget_password/$',
views.ForgetPasswordView.as_view(), #shw 关联到ForgetPasswordView类视图
name='forget_password'), #shw 命名为 'forget_password'
#shw 定义发送忘记密码验证码的URL路由
re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(), #shw 关联到ForgetPasswordEmailCode类视图
name='forget_password_code'), #shw 命名为 'forget_password_code'
]

@ -1,4 +1,6 @@
#shw 导入Django的获取用户模型函数
from django.contrib.auth import get_user_model
#shw 导入Django默认的基于模型的认证后端基类
from django.contrib.auth.backends import ModelBackend
@ -6,21 +8,33 @@ class EmailOrUsernameModelBackend(ModelBackend):
"""
允许使用用户名或邮箱登录
"""
#shw 自定义认证后端继承自ModelBackend。
#shw 它扩展了Django默认的认证功能使用户既可以使用用户名也可以使用邮箱进行登录。
def authenticate(self, request, username=None, password=None, **kwargs):
#shw 重写authenticate方法这是认证的核心逻辑。
#shw Django的login视图会调用这个方法来验证用户身份。
#shw 判断用户输入的 'username' 字段是否包含 '@' 符号,以此来区分是邮箱还是用户名
if '@' in username:
kwargs = {'email': username}
kwargs = {'email': username} #shw 如果是邮箱,则设置查询条件为 email
else:
kwargs = {'username': username}
kwargs = {'username': username} #shw 如果是用户名,则设置查询条件为 username
try:
#shw 使用动态构建的查询条件email或username去数据库中查找用户
user = get_user_model().objects.get(**kwargs)
#shw 如果找到了用户,则调用 check_password 方法来验证密码是否正确
if user.check_password(password):
return user
return user #shw 密码正确,返回用户对象,认证成功
except get_user_model().DoesNotExist:
return None
#shw 如果根据email或username找不到用户捕获异常
return None #shw 返回None表示认证失败
def get_user(self, username):
#shw 重写get_user方法。
#shw Django的认证中间件会在每个请求中调用此方法根据session中的user_id来获取用户对象。
try:
return get_user_model().objects.get(pk=username)
#shw 注意这里的参数名是username但实际传入的是用户的主键pk通常是ID
return get_user_model().objects.get(pk=username) #shw 根据主键pk查找用户
except get_user_model().DoesNotExist:
return None
#shw 如果根据主键找不到用户,捕获异常
return None #shw 返回None

@ -1,12 +1,19 @@
#shw 导入类型提示模块,用于增强代码可读性和健壮性
import typing
#shw 导入时间间隔类,用于定义验证码有效期
from datetime import timedelta
#shw 导入Django的缓存模块
from django.core.cache import cache
#shw 导入Django的翻译函数
from django.utils.translation import gettext
#shw 导入Django的延迟翻译函数用于在类或模型定义等场景
from django.utils.translation import gettext_lazy as _
#shw 从项目工具模块导入发送邮件的函数
from djangoblog.utils import send_email
#shw 定义验证码的生存时间Time To Live为5分钟
_code_ttl = timedelta(minutes=5)
@ -17,9 +24,11 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email"))
subject: 邮件主题
code: 验证码
"""
#shw 构造邮件的HTML内容使用国际化字符串并将验证码动态插入
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
#shw 调用项目通用的邮件发送函数来完成发送
send_email([to_mail], subject, html_content)
@ -32,19 +41,24 @@ def verify(email: str, code: str) -> typing.Optional[str]:
如果有错误就返回错误str
Node:
这里的错误处理不太合理应该采用raise抛出
否测调用方需要对error进行处理
否测调用方需要对error进行处理
"""
#shw 从缓存中获取指定邮箱对应的验证码
cache_code = get_code(email)
#shw 比较用户输入的验证码和缓存中的验证码是否一致
if cache_code != code:
#shw 如果不一致,返回一个翻译后的错误信息字符串
return gettext("Verification code error")
return None
def set_code(email: str, code: str):
"""设置code"""
#shw 将验证码存入缓存键为邮箱值为验证码并设置5分钟的过期时间
cache.set(email, code, _code_ttl.seconds)
def get_code(email: str) -> typing.Optional[str]:
"""获取code"""
#shw 从缓存中根据邮箱(键)获取验证码(值)
return cache.get(email)

@ -1,59 +1,84 @@
#shw 导入日志模块
import logging
from django.conf import settings
#shw 导入Django的认证模块
from django.contrib import auth
#shw 导入登录后重定向字段的常量名
from django.contrib.auth import REDIRECT_FIELD_NAME
#shw 导入获取用户模型的函数
from django.contrib.auth import get_user_model
#shw 导入登出函数
from django.contrib.auth import logout
#shw 导入Django内置的认证表单
from django.contrib.auth.forms import AuthenticationForm
#shw 导入密码哈希生成函数
from django.contrib.auth.hashers import make_password
#shw 导入HTTP响应相关类
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
#shw 导入Django的快捷函数
from django.shortcuts import get_object_or_404
from django.shortcuts import render
#shw 导入URL反向解析函数
from django.urls import reverse
#shw 导入方法装饰器
from django.utils.decorators import method_decorator
#shw 导入URL安全检查函数
from django.utils.http import url_has_allowed_host_and_scheme
#shw 导入Django的视图基类
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
#shw 从项目工具模块导入所需函数
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
#shw 导入本地的工具模块和表单
from . import utils
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
#shw 导入本地的模型
from .models import BlogUser
#shw 获取当前模块的日志记录器
logger = logging.getLogger(__name__)
# Create your views here.
#shw 在这里创建你的视图。
class RegisterView(FormView):
form_class = RegisterForm
template_name = 'account/registration_form.html'
#shw 用户注册视图继承自FormView用于处理用户注册逻辑。
form_class = RegisterForm #shw 指定使用的表单类
template_name = 'account/registration_form.html' #shw 指定渲染的模板
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
#shw 为视图的dispatch方法添加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.save(True)
site = get_current_site().domain
#shw 当表单验证通过时执行此方法
if form.is_valid(): #shw 再次确认表单有效
user = form.save(False) #shw 保存表单数据但先不提交到数据库commit=False
user.is_active = False #shw 将用户状态设为未激活,需要邮箱验证
user.source = 'Register' #shw 设置用户来源为注册
user.save(True) #shw 现在将用户对象保存到数据库
site = get_current_site().domain #shw 获取当前站点域名
#shw 生成用于邮箱验证的双重哈希签名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
#shw 如果是调试模式,则使用本地地址
if settings.DEBUG:
site = '127.0.0.1:8000'
path = reverse('account:result')
path = reverse('account:result') #shw 获取结果页面的URL路径
#shw 构造完整的邮箱验证链接
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
#shw 构造邮件内容
content = """
<p>请点击下面链接验证您的邮箱</p>
@ -64,6 +89,7 @@ class RegisterView(FormView):
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
#shw 发送验证邮件
send_email(
emailto=[
user.email,
@ -71,135 +97,159 @@ class RegisterView(FormView):
title='验证您的电子邮箱',
content=content)
#shw 构造注册成功后的跳转URL提示用户去查收邮件
url = reverse('accounts:result') + \
'?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
return HttpResponseRedirect(url) #shw 重定向到结果页面
else:
#shw 如果表单无效,重新渲染注册页面并显示错误
return self.render_to_response({
'form': form
})
class LogoutView(RedirectView):
url = '/login/'
#shw 用户登出视图继承自RedirectView用于处理用户登出逻辑。
url = '/login/' #shw 登出后重定向的URL
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
#shw 为视图添加never_cache装饰器确保该页面不被缓存
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
logout(request)
delete_sidebar_cache()
return super(LogoutView, self).get(request, *args, **kwargs)
logout(request) #shw 调用Django的logout函数清除session信息
delete_sidebar_cache() #shw 删除侧边栏缓存
return super(LogoutView, self).get(request, *args, **kwargs) #shw 执行重定向
class LoginView(FormView):
form_class = LoginForm
template_name = 'account/login.html'
success_url = '/'
redirect_field_name = REDIRECT_FIELD_NAME
login_ttl = 2626560 # 一个月的时间
#shw 用户登录视图继承自FormView用于处理用户登录逻辑。
form_class = LoginForm #shw 指定使用的表单类
template_name = 'account/login.html' #shw 指定渲染的模板
success_url = '/' #shw 登录成功后默认的重定向URL
redirect_field_name = REDIRECT_FIELD_NAME #shw 指定包含重定向URL的GET参数名
login_ttl = 2626560 # 一个月的时间用于“记住我”功能的session过期时间
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
#shw 为视图添加多个装饰器保护密码参数、CSRF保护、禁止缓存
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
redirect_to = self.request.GET.get(self.redirect_field_name)
#shw 向模板上下文中添加额外的数据
redirect_to = self.request.GET.get(self.redirect_field_name) #shw 获取GET参数中的重定向URL
if redirect_to is None:
redirect_to = '/'
kwargs['redirect_to'] = redirect_to
redirect_to = '/' #shw 如果没有,则默认为根路径
kwargs['redirect_to'] = redirect_to #shw 将重定向URL添加到上下文
return super(LoginView, self).get_context_data(**kwargs)
def form_valid(self, form):
#shw 当表单验证通过时执行此方法
#shw 使用Django内置的AuthenticationForm再次验证因为它会调用自定义的认证后端
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
delete_sidebar_cache()
logger.info(self.redirect_field_name)
delete_sidebar_cache() #shw 删除侧边栏缓存
logger.info(self.redirect_field_name) #shw 记录日志
auth.login(self.request, form.get_user())
auth.login(self.request, form.get_user()) #shw 调用Django的login函数将用户信息存入session
#shw 如果用户勾选了“记住我”
if self.request.POST.get("remember"):
self.request.session.set_expiry(self.login_ttl)
return super(LoginView, self).form_valid(form)
# return HttpResponseRedirect('/')
self.request.session.set_expiry(self.login_ttl) #shw 设置session的过期时间为一个月
return super(LoginView, self).form_valid(form) #shw 调用父类方法,处理重定向
else:
#shw 如果验证失败,重新渲染登录页面并显示错误
return self.render_to_response({
'form': form
})
def get_success_url(self):
redirect_to = self.request.POST.get(self.redirect_field_name)
#shw 获取登录成功后应重定向的URL
redirect_to = self.request.POST.get(self.redirect_field_name) #shw 从POST数据中获取重定向URL
#shw 检查URL是否安全防止开放重定向攻击
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[
self.request.get_host()]):
redirect_to = self.success_url
redirect_to = self.success_url #shw 如果URL不安全则使用默认的success_url
return redirect_to
def account_result(request):
type = request.GET.get('type')
id = request.GET.get('id')
#shw 函数视图,用于处理注册和邮箱验证的结果展示。
type = request.GET.get('type') #shw 获取URL参数中的类型
id = request.GET.get('id') #shw 获取URL参数中的用户ID
user = get_object_or_404(get_user_model(), id=id)
logger.info(type)
if user.is_active:
user = get_object_or_404(get_user_model(), id=id) #shw 根据ID获取用户对象如果不存在则返回404
logger.info(type) #shw 记录日志
if user.is_active: #shw 如果用户已经激活,则直接跳转到首页
return HttpResponseRedirect('/')
#shw 处理两种类型:注册成功提示和邮箱验证
if type and type in ['register', 'validation']:
if type == 'register':
#shw 如果是注册类型,显示注册成功、请查收邮件的提示
content = '''
恭喜您注册成功一封验证邮件已经发送到您的邮箱请验证您的邮箱后登录本站
'''
title = '注册成功'
else:
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign')
if sign != c_sign:
#shw 如果是验证类型,需要验证签名
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) #shw 重新计算正确的签名
sign = request.GET.get('sign') #shw 获取URL中的签名
if sign != c_sign: #shw 比较签名如果不一致则返回403禁止访问
return HttpResponseForbidden()
user.is_active = True
user.save()
user.is_active = True #shw 激活用户
user.save() #shw 保存用户状态
#shw 显示验证成功的提示
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
'''
title = '验证成功'
#shw 渲染结果页面
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
#shw 如果类型不匹配,则跳转到首页
return HttpResponseRedirect('/')
class ForgetPasswordView(FormView):
form_class = ForgetPasswordForm
template_name = 'account/forget_password.html'
#shw 忘记密码视图,用于处理通过验证码重置密码的逻辑。
form_class = ForgetPasswordForm #shw 指定使用的表单
template_name = 'account/forget_password.html' #shw 指定渲染的模板
def form_valid(self, form):
if form.is_valid():
#shw 当表单验证通过时执行此方法
if form.is_valid(): #shw 再次确认表单有效
#shw 根据邮箱获取用户对象
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
#shw 使用make_password对新密码进行哈希处理
blog_user.password = make_password(form.cleaned_data["new_password2"])
blog_user.save()
return HttpResponseRedirect('/login/')
blog_user.save() #shw 保存用户的新密码
return HttpResponseRedirect('/login/') #shw 重定向到登录页面
else:
#shw 如果表单无效,重新渲染页面并显示错误
return self.render_to_response({'form': form})
class ForgetPasswordEmailCode(View):
#shw 发送忘记密码验证码的视图继承自基础的View。
@staticmethod
def post(request: HttpRequest):
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱")
to_email = form.cleaned_data["email"]
#shw 只处理POST请求
form = ForgetPasswordCodeForm(request.POST) #shw 用POST数据实例化表单
if not form.is_valid(): #shw 验证表单(主要是验证邮箱格式)
return HttpResponse("错误的邮箱") #shw 如果无效,返回错误信息
to_email = form.cleaned_data["email"] #shw 获取清洗后的邮箱
code = generate_code()
utils.send_verify_email(to_email, code)
utils.set_code(to_email, code)
code = generate_code() #shw 生成一个验证码
utils.send_verify_email(to_email, code) #shw 调用工具函数发送验证邮件
utils.set_code(to_email, code) #shw 调用工具函数将验证码存入缓存
return HttpResponse("ok")
return HttpResponse("ok") #shw 返回成功信息

Loading…
Cancel
Save