Merge branch 'ZYY_branch' of https://bdgit.educoder.net/pn39ysei2/project into ZYY_branch

ZYY_branch
ymq 4 months ago
commit 462cb83b5c

Binary file not shown.

@ -1,59 +1,100 @@
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 _
#django核心组件导入
from django import forms# Django 表单处理模块
from django.contrib.auth.admin import UserAdmin # Django 默认用户管理后台类
from django.contrib.auth.forms import UserChangeForm # 用户信息修改表单基类
from django.contrib.auth.forms import UsernameField# 用户名专用表单字段
from django.utils.translation import gettext_lazy as _ # 国际化翻译函数
# 本地应用导入
# Register your models here.
from .models import BlogUser
from .models import BlogUser # 导入自定义用户模型
class BlogUserCreationForm(forms.ModelForm):
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
"""
自定义用户创建表单(用于管理后台添加新用户)
继承自 ModelForm专门处理 BlogUser 模型的创建
"""
# 定义密码输入字段(需要输入两次以确保一致)
password1 = forms.CharField(label=_('password'), # 字段标签(支持国际化)
widget=forms.PasswordInput) # 密码输入控件
password2 = forms.CharField(label=_('Enter password again'), # 确认密码标签
widget=forms.PasswordInput) # 密码输入控件
class Meta:
model = BlogUser
fields = ('email',)
model = BlogUser# 关联的模型类
fields = ('email',)# 创建用户时显示的字段这里只显示email字段
def clean_password2(self):
"""
验证两次输入的密码是否一致
Django 表单验证方法方法名必须以 clean_ 开头
"""
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
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
raise forms.ValidationError(_("passwords do not match"))# 错误信息(支持国际化)
return password2# 返回验证后的值
def save(self, commit=True):
"""
重写保存方法在保存用户前处理密码哈希
"""
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
user = super().save(commit=False) # 调用父类保存方法但不提交到数据库
user.set_password(self.cleaned_data["password1"]) # 对密码进行哈希加密
if commit:
user.source = 'adminsite'
user.save()
return user
user.source = 'adminsite' # 设置用户来源标记(表示通过管理后台创建)
user.save()# 保存到数据库
return user# 返回用户对象
class BlogUserChangeForm(UserChangeForm):
"""
自定义用户信息修改表单用于管理后台编辑用户
继承自 Django 自带的 UserChangeForm
"""
class Meta:
model = BlogUser
fields = '__all__'
field_classes = {'username': UsernameField}
model = BlogUser # 关联的模型类
fields = '__all__'# 显示所有字段
field_classes = {'username': UsernameField}# 指定用户名使用专用字段类型
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
表单初始化方法
可以在这里对表单字段进行自定义设置
"""
super().__init__(*args, **kwargs) # 调用父类初始化方法
# 可以在这里添加自定义逻辑,如修改字段属性等
class BlogUserAdmin(UserAdmin):
form = BlogUserChangeForm
add_form = BlogUserCreationForm
"""
自定义用户管理后台配置
继承自 Django 自带的 UserAdmin
"""
# 指定使用的表单类
form = BlogUserChangeForm # 编辑用户时使用的表单
add_form = BlogUserCreationForm# 添加用户时使用的表单
# 管理后台列表页显示配置
list_display = (
'id',
'nickname',
'username',
'email',
'last_login',
'date_joined',
'source')
'id', # 用户ID
'nickname', # 用户昵称
'username',# 用户名
'email', # 电子邮箱
'last_login', # 最后登录时间
'date_joined', # 注册时间
'source')# 用户来源标记
# 设置哪些字段可以点击跳转到编辑页
list_display_links = ('id', 'username')
# 默认排序规则按ID降序排列
ordering = ('-id',)

@ -1,5 +1,11 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
"""
Accounts 应用的配置类
功能
1. 定义应用名称 Django 内部识别
2. 可在此处覆盖 ready() 方法以注册信号等
"""
name = 'accounts'# 必须与项目中的应用目录名完全一致

@ -2,6 +2,7 @@ from django import forms #导入 Django 表单模块,用于创建自定义表
from django.contrib.auth import get_user_model, password_validation #get_user_model 用于获取项目中自定义的用户模型(遵循 Django 推荐的用户模型扩展方式)。
<<<<<<< HEAD
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm #UserCreationForm导入 Django 内置的认证表单AuthenticationForm 用于登录和用户创建表单UserCreationForm 用于注册),作为自定义表单的基类。
from django.core.exceptions import ValidationError #导入 Django 的验证异常类,用于在表单验证时抛出自定义错误。
@ -18,18 +19,29 @@ class LoginForm(AuthenticationForm): #继承 Django 内置的 AuthenticationForm
super(LoginForm, self).__init__(*args, **kwargs) #调用父类构造方法,确保基础功能正常。
=======
# 登录表单继承自Django内置的AuthenticationForm
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
>>>>>>> 5d714be542986b7f935eb0ec4df9b0918e75eeca
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"}) #为用户名字段设置文本输入小部件,定义占位符和 Bootstrap 样式类form-control
self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"}) #为密码字段设置密码输入小部件,同样定义占位符和样式类。
<<<<<<< HEAD
class RegisterForm(UserCreationForm): #继承 Django 内置的 UserCreationForm自定义注册表单的字段、样式和验证逻辑。
=======
# 注册表单继承自Django内置的UserCreationForm
class RegisterForm(UserCreationForm):
>>>>>>> 5d714be542986b7f935eb0ec4df9b0918e75eeca
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
# 自定义用户名、邮箱和密码字段的HTML属性
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
self.fields['email'].widget = widgets.EmailInput(
@ -38,18 +50,18 @@ class RegisterForm(UserCreationForm): #继承 Django 内置的 UserCreationForm
attrs={'placeholder': "password", "class": "form-control"})
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()
fields = ("username", "email")
# 忘记密码表单(验证邮箱和验证码)
class ForgetPasswordForm(forms.Form):
new_password1 = forms.CharField(
label=_("New password"),
@ -60,7 +72,7 @@ class ForgetPasswordForm(forms.Form):
}
),
)
# 新密码字段2用于确认
new_password2 = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
@ -70,7 +82,7 @@ class ForgetPasswordForm(forms.Form):
}
),
)
# 邮箱字段
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
@ -80,7 +92,7 @@ class ForgetPasswordForm(forms.Form):
}
),
)
# 验证码字段
code = forms.CharField(
label=_('Code'),
widget=forms.TextInput(
@ -90,16 +102,16 @@ class ForgetPasswordForm(forms.Form):
}
),
)
# 验证两次输入的密码是否一致,并检查密码强度
def clean_new_password2(self):
password1 = self.data.get("new_password1")
password2 = self.data.get("new_password2")
if password1 and password2 and password1 != password2:
raise ValidationError(_("passwords do not match"))
password_validation.validate_password(password2)
password_validation.validate_password(password2)# 使用Django的密码验证器
return password2
# 验证邮箱是否已注册
def clean_email(self):
user_email = self.cleaned_data.get("email")
if not BlogUser.objects.filter(
@ -108,10 +120,10 @@ class ForgetPasswordForm(forms.Form):
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
raise ValidationError(_("email does not exist"))
return user_email
# 验证用户输入的验证码是否正确
def clean_code(self):
code = self.cleaned_data.get("code")
error = utils.verify(
error = utils.verify(# 调用工具函数验证验证码
email=self.cleaned_data.get("email"),
code=code,
)
@ -119,7 +131,7 @@ class ForgetPasswordForm(forms.Form):
raise ValidationError(error)
return code
# 忘记密码功能中的验证码发送表单(仅需邮箱字段)
class ForgetPasswordCodeForm(forms.Form):
email = forms.EmailField(
label=_('Email'),

@ -7,29 +7,33 @@ from djangoblog.utils import get_current_site
# Create your models here.
# 自定义用户模型继承Django内置的AbstractUser
class BlogUser(AbstractUser):
# 用户昵称(可选)
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
# 账号创建时间(默认当前时间)
creation_time = models.DateTimeField(_('creation time'), default=now)
# 最后修改时间(默认当前时间)
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 账号创建来源(如:网站注册/第三方登录等,可选)
source = models.CharField(_('create source'), max_length=100, blank=True)
# 获取用户详情页的绝对URL用于模板中的{% url %}反向解析)
def get_absolute_url(self):
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
# 定义对象的字符串表示Admin后台和shell中显示
def __str__(self):
return self.email
# 获取用户详情页的完整URL包含域名用于分享链接
def get_full_url(self):
site = get_current_site().domain
site = get_current_site().domain# 获取当前站点域名
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'
ordering = ['-id'] # 默认按ID降序排列
verbose_name = _('user') # 单数形式名称(后台显示)
verbose_name_plural = verbose_name# 复数形式名称(后台显示)
get_latest_by = 'id'# 指定最新记录的排序字段

@ -10,86 +10,106 @@ from . import utils
# Create your tests here.
# 创建测试类继承Django的TestCase
class AccountTest(TestCase):
# 测试初始化方法(每个测试方法运行前都会执行)
def setUp(self):
# 初始化测试客户端(模拟浏览器请求)
self.client = Client()
# 初始化请求工厂(用于生成请求对象)
self.factory = RequestFactory()
# 创建一个普通测试用户
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
)
# 测试用的随机字符串
self.new_test = "xxx123--="
# 测试用户账号验证功能
def test_validate_account(self):
# 获取当前站点域名
site = get_current_site().domain
# 创建一个超级用户(用于测试管理员权限)
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="qwer!@#$ggg")
# 从数据库获取刚创建的超级用户(验证是否创建成功)
testuser = BlogUser.objects.get(username='liangliangyy1')
# 测试登录功能
loginresult = self.client.login(
username='liangliangyy1',
password='qwer!@#$ggg')
self.assertEqual(loginresult, True)
self.assertEqual(loginresult, True)# 验证登录成功
# 测试访问管理员后台
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200)# 验证返回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.status = 'p'
article.author = user# 关联超级用户
article.category = category# 关联上面创建的分类
article.type = 'a' # 文章类型
article.status = 'p' # 发布状态
article.save()
# 测试访问文章的管理URL
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200) # 验证返回200状态码
# 测试用户注册功能
def test_validate_register(self):
# 验证测试邮箱初始不存在
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 模拟注册请求
response = self.client.post(reverse('account:register'), {
'username': 'user1233',
'email': 'user123@user.com',
'password1': 'password123!q@wE#R$T',
'password2': 'password123!q@wE#R$T',
})
# 验证用户已创建(通过邮箱查询)
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 获取刚注册的用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 生成验证签名(用于邮箱验证等场景)
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 构造验证URL
path = reverse('accounts:result')
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign)
# 测试访问验证URL
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200) # 验证返回200状态码
# 使用测试客户端登录
self.client.login(username='user1233', password='password123!q@wE#R$T')
# 获取指定邮箱的用户并设置为超级用户和工作人员
user = BlogUser.objects.filter(email='user123@user.com')[0]
user.is_superuser = True
user.is_staff = True
user.save()
# 删除侧边栏缓存
delete_sidebar_cache()
# 创建分类
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"
@ -99,73 +119,76 @@ class AccountTest(TestCase):
article.type = 'a'
article.status = 'p'
article.save()
# 测试已登录用户访问文章管理URL
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
# 测试注销功能
response = self.client.get(reverse('account:logout'))
self.assertIn(response.status_code, [301, 302, 200])
# 测试注销后访问文章管理URL应重定向
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
# 测试使用错误密码登录
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
'password': 'password123'# 注意这里密码与登录时使用的不同
})
self.assertIn(response.status_code, [301, 302, 200])
# 测试使用错误密码登录后访问文章管理URL应重定向
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
# 测试邮箱验证码验证
def test_verify_email_code(self):
to_email = "admin@admin.com"
code = generate_code()
utils.set_code(to_email, code)
utils.send_verify_email(to_email, code)
code = generate_code() # 生成验证码
utils.set_code(to_email, code)# 存储验证码
utils.send_verify_email(to_email, code) # 发送验证邮件(实际测试中可能不会真的发送)
# 测试正确验证码
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
# 测试错误邮箱
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
self.assertEqual(type(err), str)# 应返回错误信息字符串
# 测试忘记密码发送验证码功能 - 成功情况
def test_forget_password_email_code_success(self):
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
data=dict(email="admin@admin.com") # 使用正确邮箱格式
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
self.assertEqual(resp.content.decode("utf-8"), "ok")# 验证返回成功消息
# 测试忘记密码发送验证码功能 - 失败情况
def test_forget_password_email_code_fail(self):
# 测试不提供邮箱
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 测试提供错误格式邮箱
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com")
)
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)
utils.set_code(self.blog_user.email, code)# 为测试用户设置验证码
# 准备重置密码数据
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code=code,
new_password1=self.new_test, # 新密码
new_password2=self.new_test,# 确认密码
email=self.blog_user.email,# 用户邮箱
code=code, # 验证码
)
# 提交重置密码请求
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.status_code, 302) # 应重定向
# 验证用户密码是否修改成功
blog_user = BlogUser.objects.filter(
@ -173,12 +196,12 @@ class AccountTest(TestCase):
).first() # type: BlogUser
self.assertNotEqual(blog_user, None)
self.assertEqual(blog_user.check_password(data["new_password1"]), True)
# 测试忘记密码重置功能 - 用户不存在情况
def test_forget_password_email_not_user(self):
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email="123@123.com",
email="123@123.com",# 不存在的邮箱
code="123456",
)
resp = self.client.post(
@ -186,22 +209,23 @@ class AccountTest(TestCase):
data=data
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.status_code, 200) # 应返回错误页面而非重定向
# 测试忘记密码重置功能 - 验证码错误情况
def test_forget_password_email_code_error(self):
code = generate_code()
utils.set_code(self.blog_user.email, code)
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="111111",# 错误验证码
)
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.status_code, 200)# 应返回错误页面而非重定向

@ -1,28 +1,37 @@
from django.urls import path
from django.urls import re_path
from . import views
from .forms import LoginForm
# 导入 Django 的 URL 路由工具
from django.urls import path# 用于简单路径匹配(如 'account/result.html'
from django.urls import re_path# 用于正则表达式路径匹配(如 '^login/$'
# 导入当前应用的视图和表单
from . import views# 导入 views.py 中的所有视图类/函数
from .forms import LoginForm# 导入自定义登录表单(用于覆盖默认表单)
# 定义应用的命名空间(用于反向解析 URL 时避免冲突)
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'),
# 定义 URL 路由列表
urlpatterns = [
# 登录视图(使用正则路径)
re_path(r'^login/$', # 正则匹配路径:以 login 结尾(斜杠必需)
views.LoginView.as_view(success_url='/'), # 使用 Django 内置 LoginView登录成功后跳转到首页
name='login',# URL 名称(用于模板或代码中反向解析)
kwargs={'authentication_form': LoginForm}# 传递额外参数:覆盖默认登录表单
),
# 注册视图
re_path(r'^register/$', # 正则匹配路径:以 register 结尾
views.RegisterView.as_view(success_url="/"),# 自定义注册视图,注册成功后跳转首页
name='register'), # URL 名称
# 登出视图
re_path(r'^logout/$', # 正则匹配路径:以 logout 结尾
views.LogoutView.as_view(),# 使用 Django 内置 LogoutView
name='logout'),# URL 名称
# 账户结果页面(使用 path非正则
path(r'account/result.html',# 简单路径匹配(自动添加开头结尾的 ^$
views.account_result,# 指向普通视图函数(非类视图)
name='result'),# URL 名称
# 忘记密码视图
re_path(r'^forget_password/$', # 正则匹配路径:以 forget_password 结尾
views.ForgetPasswordView.as_view(),# 自定义忘记密码视图
name='forget_password'),# URL 名称
# 忘记密码验证码处理视图
re_path(r'^forget_password_code/$',# 正则匹配路径:以 forget_password_code 结尾
views.ForgetPasswordEmailCode.as_view(), # 自定义验证码处理视图
name='forget_password_code'), # URL 名称
]

@ -1,26 +1,57 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
# 导入 Django 认证系统所需的模块
from django.contrib.auth import get_user_model# 动态获取当前项目的 User 模型
from django.contrib.auth.backends import ModelBackend# Django 默认的认证后端基类
class EmailOrUsernameModelBackend(ModelBackend):
"""
允许使用用户名或邮箱登录
承自 Django ModelBackend重写 authenticate get_user 方法
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""
重写认证方法支持用户名或邮箱登录
参数:
request: HttpRequest 对象可能包含额外的认证上下文
username: 用户输入的用户名或邮箱前端传递的字段名固定为 username
password: 用户输入的密码
**kwargs: 其他可能的参数如通过信号传递的额外参数
返回:
如果认证成功返回 User 对象否则返回 None
"""
# 判断输入是邮箱还是用户名(通过 '@' 符号区分)
if '@' in username:
# 如果是邮箱,设置查询条件为 email 字段
kwargs = {'email': username}
else:
# 否则按用户名查询
kwargs = {'username': username}
try:
# 尝试从数据库获取用户(使用当前项目的自定义 User 模型)
user = get_user_model().objects.get(**kwargs)
# 验证密码是否正确(使用 Django 的密码哈希校验)
if user.check_password(password):
return user
return user# 认证成功返回用户对象
except get_user_model().DoesNotExist:
# 用户不存在时返回 NoneDjango 会继续尝试其他认证后端)
return None
def get_user(self, username):
"""
根据用户 ID 获取用户对象用于 Session 认证等场景
参数:
username: 实际是用户的 primary key通常由 Session 存储
返回:
找到用户返回 User 对象否则返回 None
"""
try:
# 通过主键查询用户Django 默认行为)
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
# 用户不存在时返回 None
return None

@ -1,12 +1,15 @@
import typing
from datetime import timedelta
# 导入标准库模块
import typing # 用于类型注解
from datetime import timedelta # 用于处理时间间隔
from django.core.cache import cache
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from djangoblog.utils import send_email
# 导入 Django 核心组件
from django.core.cache import cache # Django 缓存系统
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)
@ -17,9 +20,11 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email"))
subject: 邮件主题
code: 验证码
"""
# 构造邮件正文(使用国际化字符串,并插入动态验证码)
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
# 调用项目封装的邮件发送函数
send_email([to_mail], subject, html_content)
@ -34,16 +39,42 @@ def verify(email: str, code: str) -> typing.Optional[str]:
这里的错误处理不太合理应该采用raise抛出
否测调用方也需要对error进行处理
"""
# 从缓存获取该邮箱对应的正确验证码
cache_code = get_code(email)
# 比对用户输入的验证码和缓存中的验证码
if cache_code != code:
return gettext("Verification code error")
return gettext("Verification code error") # 返回翻译后的错误信息
# 隐含逻辑:验证成功时返回 None调用方需检查返回值是否为 None
def set_code(email: str, code: str):
"""
将验证码存入缓存
Args:
email (str): 用户邮箱作为缓存键
code (str): 要存储的验证码
Note:
验证码有效期由全局变量 _code_ttl 控制5分钟
"""
# 使用 Django 缓存设置键值对,并指定过期时间(转换为秒)
"""设置code"""
cache.set(email, code, _code_ttl.seconds)
def get_code(email: str) -> typing.Optional[str]:
"""
从缓存获取验证码
Args:
email (str): 用户邮箱缓存键
Returns:
typing.Optional[str]:
- 存在则返回验证码字符串
- 不存在或过期返回 None
"""
# 直接调用 Django 缓存的 get 方法
"""获取code"""
return cache.get(email)

@ -1,59 +1,92 @@
# 导入日志模块,用于记录运行时的信息和错误
import logging
# Django 国际化工具,`gettext_lazy` 用于延迟翻译(适合模块级字符串)
from django.utils.translation import gettext_lazy as _
# Django 配置项,用于访问 settings.py 中的设置
from django.conf import settings
# Django 认证系统核心模块
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
# HTTP 响应类(重定向、禁止访问等)
from django.http import HttpResponseRedirect, HttpResponseForbidden
# HTTP 请求和响应的类型提示(可选,用于类型检查)
from django.http.request import HttpRequest
from django.http.response import HttpResponse
# 快捷函数(如获取对象或返回 404
from django.shortcuts import get_object_or_404
# 渲染模板的快捷方式
from django.shortcuts import render
# URL 反转工具(通过名称生成 URL
from django.urls import reverse
# 视图装饰器工具
from django.utils.decorators import method_decorator
# URL 安全验证工具(防止重定向攻击)
from django.utils.http import url_has_allowed_host_and_scheme
# 基础视图类
from django.views import View
# 缓存控制装饰器(禁用缓存)
from django.views.decorators.cache import never_cache
# CSRF 防护装饰器
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
# 当前应用的表单(注册、登录、忘记密码等)
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
# 当前应用的模型(博客用户)
from .models import BlogUser
# 初始化日志记录器__name__ 表示当前模块名)
logger = logging.getLogger(__name__)
# Create your views here.
# 注册视图类(继承自 FormView处理表单提交
class RegisterView(FormView):
# 指定使用的表单类
form_class = RegisterForm
template_name = 'account/registration_form.html'
# 使用装饰器确保视图禁用缓存never_cache并启用 CSRF 防护
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 调用父类方法处理请求
return super(RegisterView, self).dispatch(*args, **kwargs)
# 表单验证通过后的处理逻辑
def form_valid(self, form):
# 再次检查表单有效性(冗余,因为 FormView 已验证)
if form.is_valid():
user = form.save(False)
user.is_active = False
user.source = 'Register'
user.save(True)
# 保存用户对象(但暂不激活 is_active=False
user = form.save(False)
user.is_active = False# 用户需验证邮箱后才能登录
user.source = 'Register' # 标记用户来源为注册
user.save(True)# 实际保存到数据库
# 获取当前站点域名(用于生成验证链接)
site = get_current_site().domain
# 生成签名(双重 SHA256 哈希,用于验证链接安全性)
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境使用本地地址(避免因域名未配置导致链接失效)
if settings.DEBUG:
site = '127.0.0.1:8000'
# 生成验证结果的 URL如 /account/result/?type=validation&id=1&sign=abc123
path = reverse('account:result')
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 构造邮件内容(包含验证链接)
content = """
<p>请点击下面链接验证您的邮箱</p>
@ -64,92 +97,123 @@ class RegisterView(FormView):
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
# 发送验证邮件
send_email(
emailto=[
user.email,
user.email, # 收件人列表
],
title='验证您的电子邮箱',
content=content)
title='验证您的电子邮箱', # 邮件标题
content=content)# 邮件正文
# 重定向到注册结果页面(附带用户 ID
url = reverse('accounts:result') + \
'?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
# 表单无效时重新渲染表单(显示错误信息)
return self.render_to_response({
'form': form
})
# 登出视图继承自RedirectView重定向到登录页面
class LogoutView(RedirectView):
# 登出后重定向的URL
url = '/login/'
# 使用never_cache装饰器确保视图不会被缓存
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
# 调用父类的dispatch方法处理请求
return super(LogoutView, self).dispatch(request, *args, **kwargs)
# 处理GET请求
def get(self, request, *args, **kwargs):
# 执行登出操作
logout(request)
# 删除侧边栏缓存
delete_sidebar_cache()
# 调用父类的get方法完成重定向
return super(LogoutView, self).get(request, *args, **kwargs)
# 登录视图继承自FormView
class LoginView(FormView):
# 使用的表单类
form_class = LoginForm
# 模板文件路径
template_name = 'account/login.html'
# 登录成功后跳转的URL
success_url = '/'
# 重定向字段名
redirect_field_name = REDIRECT_FIELD_NAME
# 登录会话有效期(一个月的时间,单位:秒)
login_ttl = 2626560 # 一个月的时间
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
# 使用多个装饰器装饰dispatch方法
@method_decorator(sensitive_post_parameters('password'))# 标记密码参数为敏感信息
@method_decorator(csrf_protect) # 启用CSRF保护
@method_decorator(never_cache)# 禁止缓存
def dispatch(self, request, *args, **kwargs):
# 调用父类的dispatch方法处理请求
return super(LoginView, self).dispatch(request, *args, **kwargs)
# 获取模板上下文数据
def get_context_data(self, **kwargs):
# 从GET参数中获取重定向URL
redirect_to = self.request.GET.get(self.redirect_field_name)
# 如果不存在则设置为根路径
if redirect_to is None:
redirect_to = '/'
# 将重定向URL添加到上下文
kwargs['redirect_to'] = redirect_to
# 调用父类方法获取其他上下文数据
return super(LoginView, self).get_context_data(**kwargs)
# 表单验证通过后的处理
def form_valid(self, form):
# 重新创建认证表单这里可能有逻辑问题因为form已经传入
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())
# 如果用户选择了"记住我"
if self.request.POST.get("remember"):
# 设置较长的会话过期时间
self.request.session.set_expiry(self.login_ttl)
# 调用父类方法处理成功跳转
return super(LoginView, self).form_valid(form)
# return HttpResponseRedirect('/')
else:
# 表单无效,重新渲染表单并显示错误
return self.render_to_response({
'form': form
})
# 获取成功后的跳转URL
def get_success_url(self):
# 从POST参数中获取重定向URL
redirect_to = self.request.POST.get(self.redirect_field_name)
# 检查URL是否安全
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[
self.request.get_host()]):
# 如果不安全则使用默认成功URL
redirect_to = self.success_url
return redirect_to
# 账户操作结果页面(如注册成功、邮箱验证等)
def account_result(request):
# 从GET参数中获取类型和用户ID
type = request.GET.get('type')
id = request.GET.get('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':
content = '''
@ -157,48 +221,69 @@ def account_result(request):
'''
title = '注册成功'
else:
# 生成验证签名
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 获取请求中的签名
sign = request.GET.get('sign')
# 验证签名是否匹配
if sign != c_sign:
return HttpResponseForbidden()
# 激活用户账户
user.is_active = True
user.save()
# 验证成功提示内容
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
'''
title = '验证成功'
# 渲染结果页面
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
# 无效类型重定向到首页
return HttpResponseRedirect('/')
# 忘记密码视图继承自FormView
class ForgetPasswordView(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()
# 设置新密码(加密)
blog_user.password = make_password(form.cleaned_data["new_password2"])
# 保存用户对象
blog_user.save()
# 重定向到登录页面
return HttpResponseRedirect('/login/')
else:
# 表单无效,重新渲染表单并显示错误
return self.render_to_response({'form': form})
# 忘记密码验证码发送视图继承自View
class ForgetPasswordEmailCode(View):
# 处理POST请求
def post(self, request: HttpRequest):
# 验证表单
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
# 表单无效返回错误
return HttpResponse("错误的邮箱")
# 获取邮箱地址
to_email = form.cleaned_data["email"]
# 生成验证码
code = generate_code()
# 发送验证邮件
utils.send_verify_email(to_email, code)
# 存储验证码(通常有有效期)
utils.set_code(to_email, code)
# 返回成功响应
return HttpResponse("ok")

@ -3,3 +3,4 @@ from django.apps import AppConfig
class CommentsConfig(AppConfig):
name = 'comments'

@ -1,4 +1,3 @@
# Create your views here.
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404

Loading…
Cancel
Save