Merge branch 'xh_branch' into develop

develop
安琪 4 months ago
commit f8f5a4fa87

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

@ -1,6 +1,7 @@
<<<<<<< HEAD
<<<<<<< HEAD
from django import forms
<<<<<<< HEAD
from django.contrib.auth.admin import UserAdmin # 导入Django默认的用户管理Admin类基础模板
from django.contrib.auth.forms import UserChangeForm # 导入默认的用户编辑表单(用于继承修改)
from django.contrib.auth.forms import UsernameField# 用户名字段的专用类(自带验证逻辑)
@ -15,6 +16,16 @@ from django.utils.translation import gettext_lazy as _# 导入国际化翻译工
# Register your models here.
from .models import BlogUser # 导入自定义的用户模型替代Django默认User
=======
from django.contrib.auth.admin import UserAdmin # xh:导入Django默认的用户管理Admin类基础模板
from django.contrib.auth.forms import UserChangeForm # xh:导入默认的用户编辑表单(用于继承修改)
from django.contrib.auth.forms import UsernameField # xh:用户名字段的专用类(自带验证逻辑)
from django.utils.translation import gettext_lazy as _ # xh:国际化支持,文本可翻译
# Register your models here.
from .models import BlogUser # xh:导入自定义的用户模型替代Django默认User)
>>>>>>> xh_branch
# 自定义用户创建表单(用于在后台添加新用户)
class BlogUserCreationForm(forms.ModelForm):
@ -22,12 +33,21 @@ class BlogUserCreationForm(forms.ModelForm):
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)# 确认密码字段(再次输入密码)
class Meta:
<<<<<<< HEAD
model = BlogUser # 绑定自定义的BlogUser模型
fields = ('email',) # 新增用户时,默认显示的核心字段(仅邮箱,其他字段可后续编辑)
# 密码验证逻辑:检查两次输入的密码是否一致
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")# 获取第一个密码框的清洗后数据
=======
model = BlogUser # xh:绑定自定义的BlogUser模型
fields = ('email',) # xh:新增用户时,默认显示的核心字段(仅邮箱,其他字段可后续编辑)
def clean_password2(self):
# xh:密码验证逻辑:检查两次输入的密码是否一致
password1 = self.cleaned_data.get("password1")# xh:获取第一个密码框的清洗后数据
>>>>>>> xh_branch
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
# 如果密码不一致,抛出验证错误
@ -40,6 +60,7 @@ from django.contrib.auth.forms import UserChangeForm# 从 django 内置的 auth
from django.contrib.auth.forms import UsernameField# 从 django 内置的 auth 表单模块导入 UsernameField用于用户名字段的处理
from django.utils.translation import gettext_lazy as _# 从 django 翻译工具模块导入 gettext_lazy 并别名成 _用于实现国际化翻译延迟翻译
<<<<<<< HEAD
# Register your models here.
from .models import BlogUser# 从当前目录的 models 模块导入自定义的 BlogUser 模型
@ -64,6 +85,10 @@ class BlogUserCreationForm(forms.ModelForm):# 定义 BlogUserCreationForm 类,
def save(self, commit=True): # 定义 save 方法用于保存用户数据commit 参数控制是否立即提交到数据库
# Save the provided password in hashed format
<<<<<<< HEAD
=======
def save(self, commit=True):
#xh:将提供的密码以哈希格式保存
>>>>>>> xh_branch
user = super().save(commit=False)
# 使用 Django 内置方法加密密码
user.set_password(self.cleaned_data["password1"])

@ -1,5 +1,8 @@
from django.apps import AppConfig
from django.apps import AppConfig # xh:导入Django的应用配置基类
class AccountsConfig(AppConfig):
#xh:Accounts应用的配置类,继承自Django的AppConfig基类用于配置accounts应用的各种设置
# xh:应用名称Django使用这个名称来识别应用
# xh:必须与应用的目录名保持一致
name = 'accounts'

@ -9,62 +9,91 @@ from .models import BlogUser
class LoginForm(AuthenticationForm):
#xh:用户登录表单,继承自Django内置的AuthenticationForm提供用户认证功能
def __init__(self, *args, **kwargs):
#xh:初始化方法自定义表单字段的widget属性,设置输入框的placeholder和CSS类
super(LoginForm, self).__init__(*args, **kwargs)
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# xh:设置用户名字段的输入框属性
self.fields['username'].widget =widgets.TextInput(
attrs={
'placeholder': "username", # xh:占位符文本
"class": "form-control" # xh:Bootstrap样式类
})
# 设置密码字段的输入框属性
self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
attrs={
'placeholder': "password", # xh:占位符文本
"class": "form-control" # xh:Bootstrap样式类
})
class RegisterForm(UserCreationForm):
#xh:用户注册表单,继承自Django内置的UserCreationForm提供用户注册功能,自动包含密码验证逻辑(密码强度、两次密码一致性等)
def __init__(self, *args, **kwargs):
#xh:初始化方法自定义所有表单字段的widget属性
super(RegisterForm, self).__init__(*args, **kwargs)
# xh:设置用户名字段的输入框属性
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# xh:设置邮箱字段的输入框属性
self.fields['email'].widget = widgets.EmailInput(
attrs={'placeholder': "email", "class": "form-control"})
# xh:设置密码字段的输入框属性
self.fields['password1'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
# xh:设置确认密码字段的输入框属性
self.fields['password2'].widget = widgets.PasswordInput(
attrs={'placeholder': "repeat password", "class": "form-control"})
def clean_email(self):
#xh:邮箱字段验证方法,确保邮箱地址在系统中唯一
email = self.cleaned_data['email']
# xh:检查邮箱是否已存在
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")
#xh:表单元数据配置"""
model = get_user_model() # xh:使用项目中配置的用户模型
fields = ("username", "email") # xh:表单包含的字段
class ForgetPasswordForm(forms.Form):
#xh:忘记密码表单(用于验证码和密码重置),包含新密码设置、邮箱验证和验证码验证
# xh:新密码字段
new_password1 = forms.CharField(
label=_("New password"),
label=_("New password"), # xh:字段标签(支持国际化)
widget=forms.PasswordInput(
attrs={
"class": "form-control",
'placeholder': _("New password")
"class": "form-control", # xh:Bootstrap样式
'placeholder': _("New password") # xh:占位符文本
}
),
)
# xh:确认密码字段
new_password2 = forms.CharField(
label="确认密码",
label="确认密码", # xh:中文标签
widget=forms.PasswordInput(
attrs={
"class": "form-control",
'placeholder': _("Confirm password")
'placeholder': _("Confirm password") # xh:英文占位符
}
),
)
# xh:邮箱字段(用于验证用户身份)
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
label='邮箱', # xh:中文标签
widget=forms.TextInput( # xh:使用TextInput而不是EmailInput以便更好地控制样式
attrs={
'class': 'form-control',
'placeholder': _("Email")
@ -72,8 +101,9 @@ class ForgetPasswordForm(forms.Form):
),
)
# xh:验证码字段(用于二次验证)
code = forms.CharField(
label=_('Code'),
label=_('Code'), # xh:验证码标签
widget=forms.TextInput(
attrs={
'class': 'form-control',
@ -83,35 +113,44 @@ class ForgetPasswordForm(forms.Form):
)
def clean_new_password2(self):
#xh:确认密码验证方法,验证两次输入的密码是否一致,并检查密码强度
password1 = self.data.get("new_password1")
password2 = self.data.get("new_password2")
# xh:检查两次密码是否一致
if password1 and password2 and password1 != password2:
raise ValidationError(_("passwords do not match"))
# xh:使用Django内置的密码验证器验证密码强度
password_validation.validate_password(password2)
return password2
def clean_email(self):
#xh:邮箱验证方法,验证邮箱是否在系统中已注册
user_email = self.cleaned_data.get("email")
if not BlogUser.objects.filter(
email=user_email
).exists():
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
# xh:检查邮箱是否存在(是否已注册)
if not BlogUser.objects.filter(email=user_email).exists():
# TODO: 这里的报错提示可能会暴露邮箱是否注册,根据安全需求可以修改
raise ValidationError(_("email does not exist"))
return user_email
def clean_code(self):
#xh:验证码验证方法,调用utils模块的验证函数检查验证码是否正确
code = self.cleaned_data.get("code")
# xh:调用验证工具函数检查验证码
error = utils.verify(
email=self.cleaned_data.get("email"),
code=code,
email=self.cleaned_data.get("email"), # xh:传入邮箱
code=code, # xh:传入验证码
)
if error:
raise ValidationError(error)
raise ValidationError(error) # xh:验证失败,抛出错误
return code
class ForgetPasswordCodeForm(forms.Form):
#xh:忘记密码验证码请求表单,简化版表单,仅用于请求发送密码重置验证码
email = forms.EmailField(
label=_('Email'),
)
label=_('Email'), # xh:只需要邮箱字段来请求发送验证码
)

@ -1,4 +1,5 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# xh:自动生成的Django迁移文件记录数据库结构变更
import django.contrib.auth.models
import django.contrib.auth.validators
@ -7,43 +8,102 @@ import django.utils.timezone
class Migration(migrations.Migration):
#xh:数据库迁移类用于创建BlogUser用户模型
initial = True
initial = True # xh:标记为初始迁移
# xh:依赖的迁移文件
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('auth', '0012_alter_user_first_name_max_length'), # xh:依赖Django的auth应用
]
operations = [
migrations.CreateModel(
name='BlogUser',
name='BlogUser', # xh:创建BlogUser模型表
fields=[
# xh:主键ID自增BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
# xh:Django认证系统必需的密码字段
('password', models.CharField(max_length=128, verbose_name='password')),
# xh:最后登录时间,可为空
('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')),
# xh:超级用户标志
('is_superuser', models.BooleanField(default=False,
help_text='Designates that this user has all permissions without explicitly assigning them.',
verbose_name='superuser status')),
# xh:用户名字段,具有唯一性验证和字符限制
('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')),
# xh:名字字段,可为空
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
# xh:姓氏字段,可为空
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
# xh:邮箱字段,可为空
('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')),
# xh:员工状态决定是否可以登录admin后台
('is_staff', models.BooleanField(default=False,
help_text='Designates whether the user can log into this admin site.',
verbose_name='staff status')),
# xh:活跃状态,用于软删除
('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')),
# xh:账户创建时间
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
# xh:自定义字段:昵称
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
# 自定义字段:记录创建时间
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
# xh:自定义字段:最后修改时间
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
# xh:自定义字段:用户创建来源(如:网站注册、第三方登录等)
('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')),
# xh:用户组多对多关系
('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')),
# xh:用户权限多对多关系
('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')),
],
# xh:模型元数据配置
options={
'verbose_name': '用户',
'verbose_name_plural': '用户',
'ordering': ['-id'],
'get_latest_by': 'id',
'verbose_name': '用户', # xh:单数显示名称
'verbose_name_plural': '用户', # xh:复数显示名称
'ordering': ['-id'], # xh:默认按ID降序排列
'get_latest_by': 'id', # xh:指定获取最新记录的字段
},
# xh:指定模型管理器
managers=[
('objects', django.contrib.auth.models.UserManager()),
('objects', django.contrib.auth.models.UserManager()), # xh:使用Django默认的用户管理器
],
),
]
]

@ -1,46 +1,81 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
# xh:自动生成的Django迁移文件用于修改BlogUser模型结构
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
#xh:数据库迁移类用于修改BlogUser模型的字段和配置
# xh:依赖之前的初始迁移文件
dependencies = [
('accounts', '0001_initial'),
('accounts', '0001_initial'), # xh:依赖accounts应用的初始迁移
]
operations = [
# xh:修改模型的元数据选项
migrations.AlterModelOptions(
name='bloguser',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
options={
'get_latest_by': 'id', # xh:指定按id字段获取最新记录
'ordering': ['-id'], # xh:按id降序排列
'verbose_name': 'user', # xh:修改单数显示名称为英文'user'
'verbose_name_plural': 'user', # xh:修改复数显示名称为英文'user'
},
),
# xh:删除旧的创建时间字段
migrations.RemoveField(
model_name='bloguser',
name='created_time',
name='created_time', # xh:移除原有的created_time字段
),
# xh:删除旧的修改时间字段
migrations.RemoveField(
model_name='bloguser',
name='last_mod_time',
name='last_mod_time', # xh:移除原有的last_mod_time字段
),
# xh:添加新的创建时间字段(重命名)
migrations.AddField(
model_name='bloguser',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
field=models.DateTimeField(
default=django.utils.timezone.now, # xh:默认值为当前时间
verbose_name='creation time' # xh:字段显示名称为英文
),
),
# xh:添加新的最后修改时间字段(重命名)
migrations.AddField(
model_name='bloguser',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
field=models.DateTimeField(
default=django.utils.timezone.now, # xh:默认值为当前时间
verbose_name='last modify time' # xh:字段显示名称为英文
),
),
# xh:修改昵称字段的显示名称
migrations.AlterField(
model_name='bloguser',
name='nickname',
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
field=models.CharField(
blank=True,
max_length=100,
verbose_name='nick name' # xh:从中文'昵称'改为英文'nick name'
),
),
# xh:修改来源字段的显示名称
migrations.AlterField(
model_name='bloguser',
name='source',
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
field=models.CharField(
blank=True,
max_length=100,
verbose_name='create source' # xh:从中文'创建来源'改为英文'create source'
),
),
]
]

@ -1,35 +1,64 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
# xh:导入必要的模块
from django.contrib.auth.models import AbstractUser # xh:Django抽象用户基类
from django.db import models # xh:Django模型字段
from django.urls import reverse # xh:URL反向解析
from django.utils.timezone import now # xh:获取当前时间
from django.utils.translation import gettext_lazy as _ # xh:国际化翻译
from djangoblog.utils import get_current_site # xh:自定义工具函数,获取当前站点
# Create your models here.
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)
#xh:自定义用户模型,继承自Django的AbstractUser扩展了博客系统的用户功能
# xh:昵称字段 - 用户的显示名称
nickname = models.CharField(
_('nick name'), # xh:字段显示名称(支持国际化)
max_length=100, # xh:最大长度100字符
blank=True # xh:允许为空
)
# xh:创建时间 - 记录用户注册时间
creation_time = models.DateTimeField(
_('creation time'), # xh:字段显示名称
default=now # xh:默认值为当前时间
)
# xh:最后修改时间 - 记录用户信息最后修改时间
last_modify_time = models.DateTimeField(
_('last modify time'), # xh:字段显示名称
default=now # xh:默认值为当前时间
)
# xh:创建来源 - 记录用户的注册渠道
source = models.CharField(
_('create source'), # xh:字段显示名称
max_length=100, # xh:最大长度100字符
blank=True # xh:允许为空
)
def get_absolute_url(self):
#xh:获取用户的绝对URL用于后台管理等场景,返回用户详情页的URL
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
'blog:author_detail', # xh:URL配置的名称
kwargs={'author_name': self.username} # xh:传递用户名参数
)
def __str__(self):
return self.email
#xh:对象的字符串表示方法,在Django admin和管理界面中显示
return self.email # xh:使用邮箱作为对象的显示标识
def get_full_url(self):
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
#xh:获取用户的完整URL包含域名,用于生成可在站外访问的完整用户链接
site = get_current_site().domain # xh:获取当前站点的域名
url = "https://{site}{path}".format(
site=site, # xh:域名
path=self.get_absolute_url() # xh:相对路径
)
return url
class Meta:
ordering = ['-id']
verbose_name = _('user')
verbose_name_plural = verbose_name
get_latest_by = 'id'
#xh:模型的元数据配置,定义模型的各种行为和显示选项
ordering = ['-id'] # xh:默认按ID降序排列最新的在前
verbose_name = _('user') # xh:单数显示名称
verbose_name_plural = verbose_name # xh:复数显示名称(与单数相同)
get_latest_by = 'id' # xh:指定按id字段获取最新记录

@ -9,81 +9,104 @@ from djangoblog.utils import *
from . import utils
# Create your tests here.
class AccountTest(TestCase):
#xh:用户账户功能测试类,测试用户注册、登录、密码重置等核心功能
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
#xh:测试前置设置方法,在每个测试方法执行前运行,用于创建测试数据
self.client = Client() # xh:Django测试客户端用于模拟HTTP请求
self.factory = RequestFactory() # xh:用于创建请求对象
# xh:创建测试用户
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
)
self.new_test = "xxx123--="
self.new_test = "xxx123--=" # xh:测试用的新密码
def test_validate_account(self):
#xh:测试账户验证功能,验证超级用户创建、登录、文章管理等功能
site = get_current_site().domain
# xh:创建超级用户
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="qwer!@#$ggg")
testuser = BlogUser.objects.get(username='liangliangyy1')
# xh:测试用户登录
loginresult = self.client.login(
username='liangliangyy1',
password='qwer!@#$ggg')
self.assertEqual(loginresult, True)
self.assertEqual(loginresult, True) # xh:断言登录成功
# xh:测试访问管理员页面
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200) # xh:断言可以访问admin
# xh:创建测试分类
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
# xh:创建测试文章
article = Article()
article.title = "nicetitleaaa"
article.body = "nicecontentaaa"
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.type = 'a' # xh:文章类型
article.status = 'p' # xh:发布状态
article.save()
# xh:测试访问文章管理页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200) # xh:断言可以访问文章管理页
def test_validate_register(self):
#xh:测试用户注册流程,验证用户注册、邮箱验证、登录、权限管理等完整流程
# xh:验证注册前用户不存在
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
0, len(BlogUser.objects.filter(email='user123@user.com')))
# xh:提交注册表单
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',
'password1': 'password123!q@wE#R$T', # xh:密码1
'password2': 'password123!q@wE#R$T', # xh:确认密码
})
# xh:验证用户创建成功
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
1, len(BlogUser.objects.filter(email='user123@user.com')))
user = BlogUser.objects.filter(email='user123@user.com')[0]
# xh:生成邮箱验证签名(模拟验证流程)
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result')
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign)
# xh:访问验证结果页面
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# xh:测试用户登录
self.client.login(username='user1233', password='password123!q@wE#R$T')
# xh:提升用户权限为超级用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
user.is_superuser = True
user.is_staff = True
user.save()
delete_sidebar_cache()
delete_sidebar_cache() # xh:清理侧边栏缓存
# xh:创建测试数据
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
@ -95,90 +118,110 @@ class AccountTest(TestCase):
article.title = "nicetitle333"
article.body = "nicecontentttt"
article.author = user
article.type = 'a'
article.status = 'p'
article.save()
# xh:测试文章管理页面访问
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
# xh:测试用户登出
response = self.client.get(reverse('account:logout'))
self.assertIn(response.status_code, [301, 302, 200])
self.assertIn(response.status_code, [301, 302, 200]) # xh:重定向或成功
# xh:登出后访问管理页面应该被重定向
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
# xh:测试错误密码登录
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
'password': 'password123' # xh:错误密码
})
self.assertIn(response.status_code, [301, 302, 200])
# xh:错误密码登录后仍无法访问管理页面
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
def test_verify_email_code(self):
#xh:测试邮箱验证码功能,验证验证码的生成、发送和验证流程
to_email = "admin@admin.com"
code = generate_code()
code = generate_code() # xh:生成验证码
# xh:保存验证码到缓存/数据库
utils.set_code(to_email, code)
# xh:发送验证邮件(测试环境可能不会实际发送)
utils.send_verify_email(to_email, code)
# xh:测试正确验证码验证
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
self.assertEqual(err, None) # xh:断言验证成功(无错误)
# xh:测试错误邮箱验证
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
self.assertEqual(type(err), str) # xh:断言返回错误信息
def test_forget_password_email_code_success(self):
#xh:测试忘记密码验证码请求成功情况
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
data=dict(email="admin@admin.com") # xh:正确邮箱格式
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
self.assertEqual(resp.status_code, 200) # xh:断言请求成功
self.assertEqual(resp.content.decode("utf-8"), "ok") # xh:断言返回成功消息
def test_forget_password_email_code_fail(self):
#xh:测试忘记密码验证码请求失败情况,验证空数据和错误邮箱格式的处理
# xh:测试空数据提交
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
data=dict() # xh:空数据
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# xh:测试错误邮箱格式
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com")
data=dict(email="admin@com") # xh:无效邮箱格式
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
def test_forget_password_email_success(self):
#xh:测试密码重置成功流程,验证完整的密码重置功能
code = generate_code()
utils.set_code(self.blog_user.email, code)
utils.set_code(self.blog_user.email, code) # xh:设置验证码
# xh:构造密码重置数据
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code=code,
new_password1=self.new_test, # xh:新密码
new_password2=self.new_test, # xh:确认密码
email=self.blog_user.email, # xh:用户邮箱
code=code, # xh:正确验证码
)
# xh:提交密码重置请求
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.status_code, 302) # xh:断言重定向(成功)
# 验证用户密码是否修改成功
# xh:验证用户密码是否修改成功
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) # xh:断言用户存在
self.assertEqual(blog_user.check_password(data["new_password1"]), True) # xh:断言密码修改成功
def test_forget_password_email_not_user(self):
#xh:测试不存在的用户密码重置,验证系统对不存在用户的处理
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email="123@123.com",
email="123@123.com", # xh:不存在的邮箱
code="123456",
)
resp = self.client.post(
@ -186,22 +229,22 @@ class AccountTest(TestCase):
data=data
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.status_code, 200) # xh:断言返回错误页面(非重定向)
def test_forget_password_email_code_error(self):
#xh:测试验证码错误情况,验证错误验证码的处理
code = generate_code()
utils.set_code(self.blog_user.email, code)
utils.set_code(self.blog_user.email, code) # xh:设置正确验证码
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code="111111",
code="111111", # xh:错误验证码
)
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.status_code, 200) # xh:断言验证失败(非重定向)

@ -1,28 +1,54 @@
from django.urls import path
from django.urls import re_path
# xh:导入URL路由相关的模块
from django.urls import path # xh:用于简单路径匹配
from django.urls import re_path # xh:用于正则表达式路径匹配
from . import views
from .forms import LoginForm
# xh:导入当前应用的视图和表单
from . import views # xh:导入当前目录下的views模块
from .forms import LoginForm # xh:导入自定义登录表单
# xh:应用命名空间用于URL反向解析时区分不同应用的相同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'),
]
# xh:URL模式配置列表定义URL路径与视图的映射关系
urlpatterns = [
# xh:用户登录路由 - 使用正则表达式匹配
re_path(r'^login/$', # xh:匹配以/login/结尾的URL
views.LoginView.as_view( # xh:使用类视图,转换为视图函数
success_url='/' # xh:登录成功后的重定向URL首页
),
name='login', # xh:URL名称用于反向解析
kwargs={'authentication_form': LoginForm} # xh:额外参数:指定登录表单
),
# xh:用户注册路由
re_path(r'^register/$', # xh:匹配以/register/结尾的URL
views.RegisterView.as_view(
success_url="/" # xh:注册成功后的重定向URL首页
),
name='register' # xh:URL名称register
),
# xh:用户登出路由
re_path(r'^logout/$', # xh:匹配以/logout/结尾的URL
views.LogoutView.as_view(), # xh:登出视图,无额外参数
name='logout' # xh:URL名称logout
),
# xh:账户操作结果页面路由 - 使用path更简洁的路径语法
path(r'account/result.html', # xh:匹配固定路径/account/result.html
views.account_result, # xh:使用函数视图
name='result' # xh:URL名称result
),
# xh:忘记密码页面路由(提交密码重置表单)
re_path(r'^forget_password/$', # xh:匹配以/forget_password/结尾的URL
views.ForgetPasswordView.as_view(), # xh:密码重置视图
name='forget_password' # xh:URL名称forget_password
),
# xh:忘记密码验证码请求路由(获取验证码)
re_path(r'^forget_password_code/$', # xh:匹配以/forget_password_code/结尾的URL
views.ForgetPasswordEmailCode.as_view(), # xh:验证码请求视图
name='forget_password_code' # xh:URL名称forget_password_code
),
]

@ -1,26 +1,39 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
# xh:导入必要的模块
from django.contrib.auth import get_user_model # xh:获取项目中使用的用户模型
from django.contrib.auth.backends import ModelBackend # xh:Django认证后端基类
class EmailOrUsernameModelBackend(ModelBackend):
"""
允许使用用户名或邮箱登录
"""
#xh:自定义认证后端,允许用户使用用户名或邮箱地址进行登录,继承自Django的ModelBackend扩展了认证方式
def authenticate(self, request, username=None, password=None, **kwargs):
#xh:用户认证方法,根据用户名或邮箱验证用户身份
# xh:判断输入的是邮箱还是用户名
if '@' in username:
# xh:如果包含@符号,按邮箱处理
kwargs = {'email': username}
else:
# xh:否则按用户名处理
kwargs = {'username': username}
try:
# xh:根据用户名或邮箱查找用户
user = get_user_model().objects.get(**kwargs)
# xh:验证密码是否正确(自动处理哈希比较)
if user.check_password(password):
return user
return user # xh:认证成功,返回用户对象
except get_user_model().DoesNotExist:
# xh:用户不存在返回None表示认证失败
return None
def get_user(self, username):
def get_user(self, user_id):
#xh:根据用户ID获取用户对象,用于会话认证,在请求处理期间获取用户信息
try:
return get_user_model().objects.get(pk=username)
# xh:根据主键ID查找用户
return get_user_model().objects.get(pk=user_id)
except get_user_model().DoesNotExist:
# xh:用户不存在返回None
return None

@ -26,179 +26,222 @@ from . import utils
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
from .models import BlogUser
# xh:配置日志记录器
logger = logging.getLogger(__name__)
# Create your views here.
class RegisterView(FormView):
form_class = RegisterForm
template_name = 'account/registration_form.html'
#xh:用户注册视图,处理新用户的注册流程,包括表单验证、用户创建和邮箱验证
form_class = RegisterForm # xh:使用的表单类
template_name = 'account/registration_form.html' # xh:模板路径
@method_decorator(csrf_protect)
@method_decorator(csrf_protect) # xh:CSRF保护防止跨站请求伪造
def dispatch(self, *args, **kwargs):
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
#xh:表单验证通过后的处理逻辑
if form.is_valid():
# xh:保存用户但不提交到数据库commit=False
user = form.save(False)
user.is_active = False
user.source = 'Register'
user.save(True)
user.is_active = False # xh:设置用户为未激活状态,需要邮箱验证
user.source = 'Register' # xh:记录用户来源
user.save(True) # xh:保存到数据库
# xh:获取当前站点信息
site = get_current_site().domain
# xh:生成邮箱验证签名使用双重SHA256加密
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# xh:调试模式下使用本地地址
if settings.DEBUG:
site = '127.0.0.1:8000'
# xh:构建验证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)
# xh:邮件内容模板
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您
<br />
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您
<br />
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
# xh:发送验证邮件
send_email(
emailto=[
user.email,
],
emailto=[user.email],
title='验证您的电子邮箱',
content=content)
url = reverse('accounts:result') + \
'?type=register&id=' + str(user.id)
# xh:重定向到结果页面
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
return self.render_to_response({
'form': form
})
# xh:表单无效,重新渲染表单页
return self.render_to_response({'form': form})
class LogoutView(RedirectView):
url = '/login/'
#xh:用户登出视图,处理用户登出逻辑,清理会话和缓存
url = '/login/' # xh:登出后重定向的URL
@method_decorator(never_cache)
@method_decorator(never_cache) # xh:禁止缓存,确保每次都是最新状态
def dispatch(self, request, *args, **kwargs):
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
logout(request)
delete_sidebar_cache()
#xh:GET请求处理执行登出操作
logout(request) # xh:Django内置登出函数清理会话
delete_sidebar_cache() # xh:清理侧边栏缓存
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginView(FormView):
form_class = LoginForm
template_name = 'account/login.html'
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)
#xh:用户登录视图,处理用户认证和登录逻辑
form_class = LoginForm # xh:登录表单
template_name = 'account/login.html' # xh:登录模板
success_url = '/' # xh:登录成功默认重定向URL
redirect_field_name = REDIRECT_FIELD_NAME # xh:重定向字段名(默认:'next'
login_ttl = 2626560 # xh:会话过期时间:一个月(秒)
# xh:方法装饰器:增强安全性
@method_decorator(sensitive_post_parameters('password')) # xh:敏感参数保护
@method_decorator(csrf_protect) #xh:CSRF保护
@method_decorator(never_cache) # xh:禁止缓存
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
#xh:添加上下文数据重定向URL
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
redirect_to = '/'
redirect_to = '/' # xh:默认重定向到首页
kwargs['redirect_to'] = redirect_to
return super(LoginView, self).get_context_data(**kwargs)
def form_valid(self, form):
#xh:表单验证通过后的登录处理
# xh:使用Django内置的认证表单进行验证
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
# xh:清理侧边栏缓存
delete_sidebar_cache()
logger.info(self.redirect_field_name)
# xh:执行用户登录(创建会话)
auth.login(self.request, form.get_user())
# xh:处理"记住我"功能
if self.request.POST.get("remember"):
# xh:设置会话过期时间为一个月
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
})
# xh:认证失败,重新显示表单
return self.render_to_response({'form': form})
def get_success_url(self):
#xh:获取登录成功后的重定向URL,进行安全验证,防止开放重定向攻击
redirect_to = self.request.POST.get(self.redirect_field_name)
# xh:验证重定向URL的安全性
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[
self.request.get_host()]):
redirect_to = self.success_url
url=redirect_to,
allowed_hosts=[self.request.get_host()]):
redirect_to = self.success_url # xh:不安全则使用默认URL
return redirect_to
def account_result(request):
type = request.GET.get('type')
id = request.GET.get('id')
#xh:账户操作结果页面,显示注册结果或处理邮箱验证
type = request.GET.get('type') # xh:操作类型register 或 validation
id = request.GET.get('id') # xh:用户ID
# xh:获取用户对象不存在则返回404
user = get_object_or_404(get_user_model(), id=id)
logger.info(type)
# xh:如果用户已激活,直接重定向到首页
if user.is_active:
return HttpResponseRedirect('/')
# xh:处理注册结果或邮箱验证
if type and type in ['register', 'validation']:
if type == 'register':
content = '''
恭喜您注册成功一封验证邮件已经发送到您的邮箱请验证您的邮箱后登录本站
'''
# xh:注册成功页面
content = '恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。'
title = '注册成功'
else:
# xh:邮箱验证处理
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign')
# xh:验证签名,防止篡改
if sign != c_sign:
return HttpResponseForbidden()
return HttpResponseForbidden() # 签名不匹配,拒绝访问
# xh:激活用户账户
user.is_active = True
user.save()
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
'''
content = '恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。'
title = '验证成功'
# xh:渲染结果页面
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
# xh:无效类型,重定向到首页
return HttpResponseRedirect('/')
class ForgetPasswordView(FormView):
#xh:忘记密码视图,处理密码重置请求
form_class = ForgetPasswordForm
template_name = 'account/forget_password.html'
def form_valid(self, form):
#xh:表单验证通过后的密码重置处理
if form.is_valid():
# xh:根据邮箱查找用户
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
# xh:对新密码进行哈希处理并保存
blog_user.password = make_password(form.cleaned_data["new_password2"])
blog_user.save()
# xh:重定向到登录页面
return HttpResponseRedirect('/login/')
else:
# xh:表单无效,重新显示
return self.render_to_response({'form': form})
class ForgetPasswordEmailCode(View):
#xh:忘记密码验证码发送视图,处理密码重置验证码的发送
def post(self, request: HttpRequest):
#xh:处理POST请求发送密码重置验证码
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱")
to_email = form.cleaned_data["email"]
return HttpResponse("错误的邮箱") # xh:表单验证失败
to_email = form.cleaned_data["email"] # xh:获取邮箱地址
code = generate_code()
utils.send_verify_email(to_email, code)
utils.set_code(to_email, code)
# xh:生成并发送验证码
code = generate_code() # xh:生成6位随机验证码
utils.send_verify_email(to_email, code) # xh:发送验证邮件
utils.set_code(to_email, code) # xh:保存验证码到缓存/数据库
return HttpResponse("ok")
return HttpResponse("ok") # xh:返回成功响应
Loading…
Cancel
Save