yx:代码注释

yx_branch
yx 7 months ago
parent 4028036a88
commit fd8fd44d0e

@ -1,52 +1,81 @@
# 导入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
# 自定义用户创建表单
class BlogUserCreationForm(forms.ModelForm):
# 密码字段1使用密码输入控件
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
# 密码字段2用于确认密码使用密码输入控件
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
# 表单元数据配置
class Meta:
# 指定关联的模型
model = BlogUser
# 表单中包含的字段
fields = ('email',)
# 密码确认字段的清理验证方法
def clean_password2(self):
# Check that the two password entries match
# 检查两个密码输入是否匹配
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 the provided password in hashed format
# 保存提供的密码为哈希格式
user = super().save(commit=False)
# 设置用户密码
user.set_password(self.cleaned_data["password1"])
if commit:
# 设置用户来源为管理站点
user.source = 'adminsite'
# 保存用户到数据库
user.save()
return user
# 自定义用户修改表单继承自UserChangeForm
class BlogUserChangeForm(UserChangeForm):
# 表单元数据配置
class Meta:
# 指定关联的模型
model = BlogUser
# 包含所有字段
fields = '__all__'
# 字段类型配置
field_classes = {'username': UsernameField}
# 初始化方法
def __init__(self, *args, **kwargs):
# 调用父类初始化方法
super().__init__(*args, **kwargs)
# 自定义用户管理类继承自UserAdmin
class BlogUserAdmin(UserAdmin):
# 指定修改表单
form = BlogUserChangeForm
# 指定添加表单
add_form = BlogUserCreationForm
# 列表页面显示的字段
list_display = (
'id',
'nickname',
@ -55,6 +84,9 @@ class BlogUserAdmin(UserAdmin):
'last_login',
'date_joined',
'source')
# 列表页面可点击的链接字段
list_display_links = ('id', 'username')
# 默认排序字段
ordering = ('-id',)
search_fields = ('username', 'nickname', 'email')
# 搜索字段
search_fields = ('username', 'nickname', 'email')

@ -1,5 +1,8 @@
# 导入Django应用配置类
from django.apps import AppConfig
# 定义账户应用的配置类
class AccountsConfig(AppConfig):
name = 'accounts'
# 指定应用的名称
name = 'accounts'

@ -1,47 +1,75 @@
# 导入Django表单模块
from django import forms
# 导入获取用户模型函数和密码验证工具
from django.contrib.auth import get_user_model, password_validation
# 导入Django内置的认证表单和用户创建表单
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
# 导入验证错误异常
from django.core.exceptions import ValidationError
# 导入表单小部件
from django.forms import widgets
# 导入国际化翻译函数
from django.utils.translation import gettext_lazy as _
# 导入自定义工具模块
from . import utils
# 导入自定义用户模型
from .models import BlogUser
# 登录表单类继承自Django内置的AuthenticationForm
class LoginForm(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"})
# 注册表单类继承自Django内置的UserCreationForm
class RegisterForm(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"})
# 设置密码字段的小部件为密码输入框添加占位符和CSS类
self.fields['password1'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
# 设置确认密码字段的小部件为密码输入框添加占位符和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:
# 指定关联的模型为当前激活的用户模型
model = get_user_model()
# 表单中包含的字段
fields = ("username", "email")
# 忘记密码表单类继承自forms.Form
class ForgetPasswordForm(forms.Form):
# 新密码字段1
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
@ -52,6 +80,7 @@ class ForgetPasswordForm(forms.Form):
),
)
# 新密码字段2用于确认密码
new_password2 = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
@ -62,6 +91,7 @@ class ForgetPasswordForm(forms.Form):
),
)
# 邮箱字段
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
@ -72,6 +102,7 @@ class ForgetPasswordForm(forms.Form):
),
)
# 验证码字段
code = forms.CharField(
label=_('Code'),
widget=forms.TextInput(
@ -82,36 +113,50 @@ 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"))
# 使用Django的密码验证器验证密码强度
password_validation.validate_password(password2)
return password2
# 邮箱字段的清理验证方法
def clean_email(self):
# 获取清理后的邮箱数据
user_email = self.cleaned_data.get("email")
# 检查邮箱是否存在于用户模型中
if not BlogUser.objects.filter(
email=user_email
).exists():
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
# 如果邮箱不存在,抛出验证错误
# 注释说明:这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
raise ValidationError(_("email does not exist"))
return user_email
# 验证码字段的清理验证方法
def clean_code(self):
# 获取清理后的验证码数据
code = self.cleaned_data.get("code")
# 使用工具函数验证验证码
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'),
)
)

@ -1,4 +1,4 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 由Django 4.1.7于2023-03-02 07:14自动生成
import django.contrib.auth.models
import django.contrib.auth.validators
@ -7,43 +7,65 @@ import django.utils.timezone
class Migration(migrations.Migration):
# 初始迁移类
initial = True
# 依赖的迁移文件
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
# 迁移操作列表
operations = [
migrations.CreateModel(
name='BlogUser',
name='BlogUser', # 自定义用户模型名称
fields=[
# 主键ID字段自增BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
# 密码字段,存储加密后的密码
('password', models.CharField(max_length=128, verbose_name='password')),
# 最后登录时间字段,可为空
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
# 超级用户状态字段
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
# 用户名字段,具有唯一性验证和字符验证
('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')),
# 名字字段,可为空
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
# 姓氏字段,可为空
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
# 邮箱字段,可为空
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
# 员工状态字段,决定是否可以登录管理后台
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
# 活跃状态字段,决定用户是否活跃
('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', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
# 昵称字段,自定义字段,可为空
('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='修改时间')),
# 创建来源字段,记录用户创建来源
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
# 用户组多对多关系
('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')),
# 用户权限多对多关系
('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'],
'get_latest_by': 'id',
'verbose_name': '用户', # 单数显示名称
'verbose_name_plural': '用户', # 复数显示名称
'ordering': ['-id'], # 默认按ID降序排列
'get_latest_by': 'id', # 指定获取最新记录的字段
},
# 模型管理器
managers=[
# 使用Django默认的用户管理器
('objects', django.contrib.auth.models.UserManager()),
],
),
]
]

@ -1,46 +1,55 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
# 由Django 4.2.5于2023-09-06 13:13自动生成
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
# 数据库迁移类
dependencies = [
# 依赖accounts应用的0001_initial迁移文件
('accounts', '0001_initial'),
]
operations = [
# 修改BlogUser模型的元选项
migrations.AlterModelOptions(
name='bloguser',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
),
# 删除created_time字段
migrations.RemoveField(
model_name='bloguser',
name='created_time',
),
# 删除last_mod_time字段
migrations.RemoveField(
model_name='bloguser',
name='last_mod_time',
),
# 新增creation_time字段
migrations.AddField(
model_name='bloguser',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
# 新增last_modify_time字段
migrations.AddField(
model_name='bloguser',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
# 修改nickname字段的verbose_name
migrations.AlterField(
model_name='bloguser',
name='nickname',
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
),
# 修改source字段的verbose_name
migrations.AlterField(
model_name='bloguser',
name='source',
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
),
]
]

@ -1,35 +1,58 @@
# 导入Django认证系统的抽象用户基类
from django.contrib.auth.models import AbstractUser
# 导入Django数据库模型
from django.db import models
# 导入URL反向解析函数
from django.urls import reverse
# 导入时区工具,获取当前时间
from django.utils.timezone import now
# 导入国际化翻译函数
from django.utils.translation import gettext_lazy as _
# 导入自定义工具函数,获取当前站点
from djangoblog.utils import get_current_site
# Create your models here.
# 在此创建模型
# 自定义博客用户模型继承自Django的AbstractUser
class BlogUser(AbstractUser):
# 昵称字段最大长度100字符允许为空
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
# 创建时间字段,默认值为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now)
# 最后修改时间字段,默认值为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 创建来源字段最大长度100字符允许为空
source = models.CharField(_('create source'), max_length=100, blank=True)
# 获取用户绝对URL的方法
def get_absolute_url(self):
# 使用反向解析生成作者详情页URL
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
# 对象的字符串表示方法
def __str__(self):
# 使用邮箱作为对象的字符串表示
return self.email
# 获取完整URL的方法包含域名
def get_full_url(self):
# 获取当前站点域名
site = get_current_site().domain
# 构建完整的HTTPS URL
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
return url
# 模型元数据配置
class Meta:
# 默认按ID降序排列
ordering = ['-id']
# 单数显示名称
verbose_name = _('user')
# 复数显示名称(与单数相同)
verbose_name_plural = verbose_name
get_latest_by = 'id'
# 指定获取最新记录的字段
get_latest_by = 'id'

@ -1,135 +1,186 @@
# 导入Django测试相关模块
from django.test import Client, RequestFactory, TestCase
# 导入URL反向解析
from django.urls import reverse
# 导入时区工具
from django.utils import timezone
# 导入国际化翻译函数
from django.utils.translation import gettext_lazy as _
# 导入账户模型
from accounts.models import BlogUser
# 导入博客文章和分类模型
from blog.models import Article, Category
# 导入工具函数
from djangoblog.utils import *
# 导入当前模块的工具函数
from . import utils
# Create your tests here.
# 在此创建测试
# 账户测试类继承自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)
# 访问管理后台
response = self.client.get('/admin/')
# 断言访问成功
self.assertEqual(response.status_code, 200)
# 创建分类
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
# 创建文章
article = Article()
article.title = "nicetitleaaa"
article.body = "nicecontentaaa"
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.type = 'a' # 文章类型
article.status = 'p' # 发布状态
article.save()
# 访问文章管理页面
response = self.client.get(article.get_admin_url())
# 断言访问成功
self.assertEqual(response.status_code, 200)
# 测试注册功能
def test_validate_register(self):
# 断言邮箱尚未注册
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 发送注册请求
response = self.client.post(reverse('account:register'), {
'username': 'user1233',
'email': 'user123@user.com',
'password1': 'password123!q@wE#R$T',
'password2': 'password123!q@wE#R$T',
})
# 断言用户创建成功
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
# 获取新创建的用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 生成验证签名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result')
# 构建验证URL
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign)
# 访问验证页面
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# 登录新用户
self.client.login(username='user1233', password='password123!q@wE#R$T')
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 设置为超级用户和员工
user.is_superuser = True
user.is_staff = True
user.save()
# 删除侧边栏缓存
delete_sidebar_cache()
# 创建分类
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.type = 'a' # 文章类型
article.status = 'p' # 发布状态
article.save()
# 访问文章管理页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
# 测试登出功能
response = self.client.get(reverse('account:logout'))
self.assertIn(response.status_code, [301, 302, 200])
# 登出后访问管理页面应该重定向
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
# 使用错误密码登录
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
})
self.assertIn(response.status_code, [301, 302, 200])
# 登录失败后访问管理页面应该重定向
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
# 测试邮箱验证码功能
def test_verify_email_code(self):
to_email = "admin@admin.com"
# 生成验证码
code = generate_code()
# 设置验证码
utils.set_code(to_email, code)
# 发送验证邮件
utils.send_verify_email(to_email, code)
# 测试正确验证码验证
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
# 测试错误邮箱验证
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
# 测试忘记密码邮箱验证码成功情况
def test_forget_password_email_code_success(self):
resp = self.client.post(
path=reverse("account:forget_password_code"),
@ -139,21 +190,26 @@ class AccountTest(TestCase):
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
# 测试忘记密码邮箱验证码失败情况
def test_forget_password_email_code_fail(self):
# 测试空数据提交
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 测试错误邮箱格式
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com")
)
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,
@ -165,15 +221,18 @@ class AccountTest(TestCase):
path=reverse("account:forget_password"),
data=data
)
# 断言重定向响应
self.assertEqual(resp.status_code, 302)
# 验证用户密码是否修改成功
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
).first() # type: BlogUser
).first() # 类型注解: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,
@ -188,7 +247,7 @@ class AccountTest(TestCase):
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)
@ -196,12 +255,11 @@ class AccountTest(TestCase):
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,52 @@
# 导入Django URL路由相关模块
from django.urls import path
from django.urls import re_path
# 导入当前应用的视图模块
from . import views
# 导入登录表单类
from .forms import LoginForm
# 定义应用命名空间
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 = [
# 登录URL使用正则表达式匹配以login/结尾的路径
re_path(r'^login/$',
# 使用LoginView类视图登录成功后重定向到首页
views.LoginView.as_view(success_url='/'),
name='login', # URL名称
# 传递额外参数,指定认证表单类
kwargs={'authentication_form': LoginForm}),
# 注册URL使用正则表达式匹配以register/结尾的路径
re_path(r'^register/$',
# 使用RegisterView类视图注册成功后重定向到首页
views.RegisterView.as_view(success_url="/"),
name='register'), # URL名称
# 登出URL使用正则表达式匹配以logout/结尾的路径
re_path(r'^logout/$',
# 使用LogoutView类视图
views.LogoutView.as_view(),
name='logout'), # URL名称
# 账户结果页面URL使用path精确匹配路径
path(r'account/result.html',
# 使用account_result函数视图
views.account_result,
name='result'), # URL名称
# 忘记密码URL使用正则表达式匹配以forget_password/结尾的路径
re_path(r'^forget_password/$',
# 使用ForgetPasswordView类视图
views.ForgetPasswordView.as_view(),
name='forget_password'), # URL名称
# 忘记密码验证码URL使用正则表达式匹配以forget_password_code/结尾的路径
re_path(r'^forget_password_code/$',
# 使用ForgetPasswordEmailCode类视图
views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'), # URL名称
]

@ -1,26 +1,42 @@
# 导入获取用户模型函数
from django.contrib.auth import get_user_model
# 导入Django认证后端基类
from django.contrib.auth.backends import ModelBackend
# 自定义认证后端类,允许使用邮箱或用户名登录
class EmailOrUsernameModelBackend(ModelBackend):
"""
允许使用用户名或邮箱登录
"""
# 用户认证方法
def authenticate(self, request, username=None, password=None, **kwargs):
# 判断输入是否包含@符号,以区分邮箱和用户名
if '@' in username:
# 如果是邮箱设置查询参数为email
kwargs = {'email': username}
else:
# 如果是用户名设置查询参数为username
kwargs = {'username': username}
try:
# 根据查询参数获取用户对象
user = get_user_model().objects.get(**kwargs)
# 验证密码是否正确
if user.check_password(password):
# 密码正确,返回用户对象
return user
# 捕获用户不存在的异常
except get_user_model().DoesNotExist:
# 用户不存在返回None
return None
# 根据用户ID获取用户对象的方法
def get_user(self, username):
try:
# 根据主键用户ID获取用户对象
return get_user_model().objects.get(pk=username)
# 捕获用户不存在的异常
except get_user_model().DoesNotExist:
return None
# 用户不存在返回None
return None

@ -1,15 +1,22 @@
# 导入类型提示模块
import typing
# 导入时间间隔模块
from datetime import timedelta
# 导入Django缓存模块
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
# 设置验证码过期时间为5分钟
_code_ttl = timedelta(minutes=5)
# 发送验证邮件函数
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""发送重设密码验证码
Args:
@ -17,12 +24,15 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email"))
subject: 邮件主题
code: 验证码
"""
# 构建邮件HTML内容包含验证码信息
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
# 调用发送邮件函数
send_email([to_mail], subject, html_content)
# 验证验证码函数
def verify(email: str, code: str) -> typing.Optional[str]:
"""验证code是否有效
Args:
@ -34,16 +44,23 @@ 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")
# 设置验证码到缓存函数
def set_code(email: str, code: str):
"""设置code"""
# 将验证码存入缓存使用邮箱作为key设置过期时间
cache.set(email, code, _code_ttl.seconds)
# 从缓存获取验证码函数
def get_code(email: str) -> typing.Optional[str]:
"""获取code"""
return cache.get(email)
# 从缓存中获取对应邮箱的验证码
return cache.get(email)

@ -1,59 +1,89 @@
# 导入日志模块
import logging
# 导入国际化翻译函数
from django.utils.translation import gettext_lazy as _
# 导入Django配置模块
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
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
# 导入HTTP响应模块
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
# 导入快捷函数
from django.shortcuts import get_object_or_404
from django.shortcuts import render
# 导入URL反向解析
from django.urls import reverse
# 导入方法装饰器
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
# 导入视图类
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
# 导入工具函数
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
# 导入当前应用的工具模块
from . import utils
# 导入表单类
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
# 导入用户模型
from .models import BlogUser
# 获取日志器
logger = logging.getLogger(__name__)
# Create your views here.
# 在此创建视图
# 注册视图类继承自FormView
class RegisterView(FormView):
# 指定使用的表单类
form_class = RegisterForm
# 指定模板名称
template_name = 'account/registration_form.html'
# 使用CSRF保护装饰器
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 调用父类的dispatch方法
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
# 生成验证签名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 调试模式下使用本地地址
if settings.DEBUG:
site = '127.0.0.1:8000'
# 获取结果页面路径
path = reverse('account:result')
# 构建验证URL
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 构建邮件内容
content = """
<p>请点击下面链接验证您的邮箱</p>
@ -64,6 +94,7 @@ class RegisterView(FormView):
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
# 发送验证邮件
send_email(
emailto=[
user.email,
@ -71,43 +102,60 @@ class RegisterView(FormView):
title='验证您的电子邮箱',
content=content)
# 构建注册成功重定向URL
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/'
# 使用不缓存装饰器
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
return super(LogoutView, self).dispatch(request, *args, **kwargs)
# GET请求处理
def get(self, request, *args, **kwargs):
# 执行登出操作
logout(request)
# 删除侧边栏缓存
delete_sidebar_cache()
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 # 一个月的时间
# 登录会话有效期(一个月)
login_ttl = 2626560
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
# 使用多个装饰器保护登录视图
@method_decorator(sensitive_post_parameters('password')) # 敏感参数保护
@method_decorator(csrf_protect) # CSRF保护
@method_decorator(never_cache) # 不缓存
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
# 获取上下文数据
def get_context_data(self, **kwargs):
# 获取重定向URL
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
redirect_to = '/'
@ -115,26 +163,35 @@ class LoginView(FormView):
return super(LoginView, self).get_context_data(**kwargs)
# 表单验证通过后的处理
def form_valid(self, 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()]):
@ -142,63 +199,93 @@ class LoginView(FormView):
return redirect_to
# 账户结果页面视图函数
def account_result(request):
# 获取类型参数
type = request.GET.get('type')
# 获取用户ID参数
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 = '''
恭喜您注册成功一封验证邮件已经发送到您的邮箱请验证您的邮箱后登录本站
'''
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")
return HttpResponse("ok")
Loading…
Cancel
Save