Merge branch 'develop'

master
ymq 4 months ago
commit 272d8dd3b2

@ -0,0 +1,7 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}

Binary file not shown.

@ -1,52 +1,224 @@
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
<<<<<<< HEAD
#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 _ # 国际化翻译函数
=======
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 _
>>>>>>> LXY_branch
<<<<<<< HEAD
# 本地应用导入
# Register your models here.
<<<<<<< HEAD
from .models import BlogUser # 导入自定义用户模型
=======
# 导入自定义用户模型
from .models import BlogUser
>>>>>>> JYN_branch
class BlogUserCreationForm(forms.ModelForm):
"""
<<<<<<< HEAD
自定义用户创建表单(用于管理后台添加新用户)
继承自 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',)# 创建用户时显示的字段这里只显示email字段
=======
自定义用户创建表单用于在管理员界面添加新用户
继承自ModelForm提供密码验证功能
"""
# 密码字段使用PasswordInput小部件确保输入不可见
=======
from .models import BlogUser# 导入当前应用下的BlogUser模型自定义用户模型
class BlogUserCreationForm(forms.ModelForm): # 定义两个密码字段使用PasswordInput小部件隐藏输入
>>>>>>> LXY_branch
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
# 确认密码字段,用于验证两次输入的密码是否一致
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
<<<<<<< HEAD
# 指定关联的模型
model = BlogUser
# 表单中包含的字段,这里只显示邮箱
fields = ('email',)
>>>>>>> JYN_branch
def clean_password2(self):
"""
验证两次输入的密码是否一致
<<<<<<< HEAD
Django 表单验证方法方法名必须以 clean_ 开头
"""
=======
model = BlogUser# 关联的模型是BlogUser
fields = ('email',) # 表单中显示的字段(仅邮箱,密码单独定义)
def clean_password2(self):# 验证两个密码是否一致
>>>>>>> LXY_branch
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")# 获取第一次输入的密码
password2 = self.cleaned_data.get("password2")# 获取第二次输入的密码
# 如果两次密码不一致,抛出验证错误
=======
这是Django表单验证机制的一部分方法名以clean_开头
"""
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
# 检查密码是否存在且不一致
>>>>>>> JYN_branch
if password1 and password2 and password1 != password2:
raise forms.ValidationError(_("passwords do not match"))
return password2
<<<<<<< HEAD
raise forms.ValidationError(_("passwords do not match"))# 错误信息(支持国际化)
return password2# 返回验证后的值
def save(self, commit=True):
"""
<<<<<<< HEAD
重写保存方法在保存用户前处理密码哈希
"""
# 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# 返回用户对象
=======
重写保存方法确保密码以哈希形式存储
而不是明文存储
"""
# 先调用父类方法获取用户对象,但不立即保存到数据库
user = super().save(commit=False)
# 使用set_password方法对密码进行哈希处理
user.set_password(self.cleaned_data["password1"])
if commit:
# 标记用户来源为管理员站点
user.source = 'adminsite'
user.save()
=======
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()# 提交到数据库
>>>>>>> LXY_branch
return user
>>>>>>> JYN_branch
class BlogUserChangeForm(UserChangeForm):
"""
<<<<<<< HEAD
自定义用户信息修改表单用于管理后台编辑用户
继承自 Django 自带的 UserChangeForm
"""
class Meta:
<<<<<<< HEAD
model = BlogUser # 关联的模型类
fields = '__all__'# 显示所有字段
field_classes = {'username': UsernameField}# 指定用户名使用专用字段类型
def __init__(self, *args, **kwargs):
"""
表单初始化方法
可以在这里对表单字段进行自定义设置
"""
super().__init__(*args, **kwargs) # 调用父类初始化方法
# 可以在这里添加自定义逻辑,如修改字段属性等
class BlogUserAdmin(UserAdmin):
"""
自定义用户管理后台配置
继承自 Django 自带的 UserAdmin
"""
# 指定使用的表单类
form = BlogUserChangeForm # 编辑用户时使用的表单
add_form = BlogUserCreationForm# 添加用户时使用的表单
# 管理后台列表页显示配置
list_display = (
'id', # 用户ID
'nickname', # 用户昵称
'username',# 用户名
'email', # 电子邮箱
'last_login', # 最后登录时间
'date_joined', # 注册时间
'source')# 用户来源标记
# 设置哪些字段可以点击跳转到编辑页
list_display_links = ('id', 'username')
# 默认排序规则按ID降序排列
ordering = ('-id',)
=======
自定义用户修改表单用于在管理员界面编辑用户信息
继承自Django内置的UserChangeForm
"""
class Meta:
# 指定关联的模型
model = BlogUser
# 显示所有字段
fields = '__all__'
# 指定用户名字段的处理类
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
"""初始化方法,调用父类的初始化方法"""
super().__init__(*args, **kwargs)
class BlogUserAdmin(UserAdmin):
"""
自定义用户管理员类用于在Django管理后台配置用户模型的显示和操作
继承自Django内置的UserAdmin
"""
# 指定编辑用户时使用的表单
form = BlogUserChangeForm
# 指定添加用户时使用的表单
add_form = BlogUserCreationForm
# 列表页面显示的字段
=======
model = BlogUser # 关联的模型是BlogUser
fields = '__all__'# 显示模型的所有字段
field_classes = {'username': UsernameField}# 为用户名字段指定类保持Django原生逻辑
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)# 调用父类的初始化方法
class BlogUserAdmin(UserAdmin):
form = BlogUserChangeForm# 指定修改用户时使用的表单
add_form = BlogUserCreationForm# 指定创建用户时使用的表单
>>>>>>> LXY_branch
list_display = (
'id',
'nickname',
@ -54,6 +226,16 @@ class BlogUserAdmin(UserAdmin):
'email',
'last_login',
'date_joined',
'source')
<<<<<<< HEAD
'source'
)
# 列表页面中可点击跳转的字段
list_display_links = ('id', 'username')
# 排序方式按id降序排列最新的用户在前
ordering = ('-id',)
>>>>>>> JYN_branch
=======
'source')
list_display_links = ('id', 'username')# 列表页中可点击跳转的字段
ordering = ('-id',)# 列表页的排序方式按ID倒序
>>>>>>> LXY_branch

@ -1,5 +1,30 @@
<<<<<<< HEAD
from django.apps import AppConfig
class AccountsConfig(AppConfig):
"""
<<<<<<< HEAD
Accounts 应用的配置类
功能
1. 定义应用名称 Django 内部识别
2. 可在此处覆盖 ready() 方法以注册信号等
"""
name = 'accounts'# 必须与项目中的应用目录名完全一致
=======
Django应用配置类用于定义'accounts'应用的元数据
每个Django应用都需要一个配置类用于设置应用的各种属性和行为
通常放在应用目录下的apps.py文件中
"""
# 应用的名称,必须与应用目录名一致
# 这个名称会被Django用来识别和管理应用
name = 'accounts'
>>>>>>> JYN_branch
=======
from django.apps import AppConfig#导入 Django 框架中用于应用配置的 AppConfig 类,这是 Django 应用配置的核心类
class AccountsConfig(AppConfig):#定义 AccountsConfig 类,继承自 AppConfig用于对 accounts 应用进行自定义配置
name = 'accounts'#指定该应用的名称为 accountsDjango 会通过这个名称来识别和管理该应用
>>>>>>> LXY_branch

@ -1,26 +1,95 @@
<<<<<<< HEAD
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 的验证异常类,用于在表单验证时抛出自定义错误。
from django.forms import widgets #导入 Django 表单的小部件模块,用于自定义表单字段的渲染样式(如输入框类型、样式类等)。
from django.utils.translation import gettext_lazy as _ #导入 Django 的延迟翻译函数,用于表单字段标签、错误提示的国际化翻译。
from . import utils #导入当前应用下的 utils 模块(假设包含工具类或函数,此处代码未展示具体使用)。
from .models import BlogUser #导入当前应用下定义的 BlogUser 模型(自定义用户模型)。
class LoginForm(AuthenticationForm): #继承 Django 内置的 AuthenticationForm自定义登录表单的样式和逻辑。
def __init__(self, *args, **kwargs): #重写构造方法,用于自定义表单字段的小部件属性。
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
=======
from django import forms
from django.contrib.auth import get_user_model, password_validation
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
from . import utils # 导入自定义工具模块,可能用于验证码验证等功能
from .models import BlogUser # 导入自定义用户模型
class LoginForm(AuthenticationForm):
"""
自定义登录表单继承自Django内置的AuthenticationForm
用于处理用户登录验证主要扩展了表单字段的样式
"""
def __init__(self, *args, **kwargs):
# 调用父类构造方法初始化表单
super(LoginForm, self).__init__(*args, **kwargs)
# 自定义用户名输入框添加占位符和CSS类
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
# 自定义密码输入框添加占位符和CSS类
>>>>>>> 8b27cdad9a9ccc84febce3bcf1d211ed109f96f2
self.fields['password'].widget = widgets.PasswordInput(
<<<<<<< HEAD
attrs={'placeholder': "password", "class": "form-control"}) #为密码字段设置密码输入小部件,同样定义占位符和样式类。
<<<<<<< HEAD
=======
attrs={'placeholder': "password", "class": "form-control"})
#自定义登录表单在__init__方法中设置username文本输入占位符、form-control样式和password密码输入占位符、form-control样式字段的前端显示样式。
>>>>>>> LXY_branch
<<<<<<< HEAD
class RegisterForm(UserCreationForm): #继承 Django 内置的 UserCreationForm自定义注册表单的字段、样式和验证逻辑。
=======
<<<<<<< HEAD
# 注册表单继承自Django内置的UserCreationForm
class RegisterForm(UserCreationForm):
>>>>>>> 5d714be542986b7f935eb0ec4df9b0918e75eeca
=======
class RegisterForm(UserCreationForm):
"""
自定义注册表单继承自Django内置的UserCreationForm
用于处理用户注册逻辑包含用户名邮箱和密码验证
"""
>>>>>>> 8b27cdad9a9ccc84febce3bcf1d211ed109f96f2
>>>>>>> JYN_branch
def __init__(self, *args, **kwargs):
# 调用父类构造方法初始化表单
super(RegisterForm, self).__init__(*args, **kwargs)
<<<<<<< HEAD
# 自定义用户名、邮箱和密码字段的HTML属性
=======
# 自定义各字段的输入控件,添加样式和占位符
>>>>>>> JYN_branch
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
self.fields['email'].widget = widgets.EmailInput(
@ -29,19 +98,41 @@ class RegisterForm(UserCreationForm):
attrs={'placeholder': "password", "class": "form-control"})
self.fields['password2'].widget = widgets.PasswordInput(
attrs={'placeholder': "repeat password", "class": "form-control"})
<<<<<<< HEAD
# 验证邮箱唯一性
=======
#__init__方法中设置username文本输入、email邮箱输入、password1和password2密码输入字段的占位符与form-control样式
>>>>>>> LXY_branch
def clean_email(self):
"""
邮箱验证方法检查邮箱是否已被注册
表单验证机制中以clean_为前缀的方法会自动被调用
"""
email = self.cleaned_data['email']
# 检查该邮箱是否已存在于数据库中
if get_user_model().objects.filter(email=email).exists():
raise ValidationError(_("email already exists"))
raise ValidationError(_("email already exists")) # 抛出验证错误
return email
<<<<<<< HEAD
# 指定关联的用户模型和表单字段
class Meta:
model = get_user_model() # 使用项目配置的用户模型可能是自定义的BlogUser
fields = ("username", "email") # 注册表单中显示的字段
=======
#clean_email方法验证邮箱是否已被注册若存在则抛出“邮箱已存在”的验证错误
class Meta:
model = get_user_model()
fields = ("username", "email")
#Meta类指定关联模型为自定义用户模型表单字段包含username和email
>>>>>>> LXY_branch
# 忘记密码表单(验证邮箱和验证码)
class ForgetPasswordForm(forms.Form):
"""
忘记密码表单用于用户重置密码的流程
包含新密码确认密码邮箱和验证码字段
"""
# 新密码字段
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
@ -51,7 +142,12 @@ class ForgetPasswordForm(forms.Form):
}
),
)
<<<<<<< HEAD
# 新密码字段2用于确认
=======
# 确认新密码字段
>>>>>>> JYN_branch
new_password2 = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
@ -61,7 +157,12 @@ class ForgetPasswordForm(forms.Form):
}
),
)
<<<<<<< HEAD
# 邮箱字段
=======
# 邮箱字段(用于验证用户身份)
>>>>>>> JYN_branch
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
@ -71,7 +172,12 @@ class ForgetPasswordForm(forms.Form):
}
),
)
<<<<<<< HEAD
# 验证码字段
=======
# 验证码字段(用于身份验证)
>>>>>>> JYN_branch
code = forms.CharField(
label=_('Code'),
widget=forms.TextInput(
@ -81,37 +187,80 @@ class ForgetPasswordForm(forms.Form):
}
),
)
<<<<<<< HEAD
# 验证两次输入的密码是否一致,并检查密码强度
=======
#定义new_password1新密码密码输入、new_password2确认密码密码输入、email邮箱文本输入、code验证码文本输入字段均设置form-control样式和占位符。
>>>>>>> LXY_branch
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)
<<<<<<< HEAD
password_validation.validate_password(password2)# 使用Django的密码验证器
=======
# 使用Django内置的密码验证器验证密码强度
password_validation.validate_password(password2)
>>>>>>> JYN_branch
return password2
<<<<<<< HEAD
# 验证邮箱是否已注册
=======
# clean_new_password2方法验证两次新密码是否一致并对密码进行有效性校验
>>>>>>> LXY_branch
def clean_email(self):
"""验证邮箱是否已注册"""
user_email = self.cleaned_data.get("email")
if not BlogUser.objects.filter(
email=user_email
).exists():
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
# 检查该邮箱是否存在于系统中
if not BlogUser.objects.filter(email=user_email).exists():
# 提示邮箱不存在(实际应用中可能需要模糊提示以避免信息泄露)
raise ValidationError(_("email does not exist"))
return user_email
<<<<<<< HEAD
# 验证用户输入的验证码是否正确
=======
# clean_email方法验证邮箱是否已注册基于BlogUser模型未注册则抛出“邮箱不存在”的验证错误
>>>>>>> LXY_branch
def clean_code(self):
"""验证验证码是否有效"""
code = self.cleaned_data.get("code")
<<<<<<< HEAD
error = utils.verify(# 调用工具函数验证验证码
=======
# 调用工具函数验证邮箱和验证码是否匹配
error = utils.verify(
>>>>>>> JYN_branch
email=self.cleaned_data.get("email"),
code=code,
)
if error:
raise ValidationError(error)
raise ValidationError(error) # 验证码无效时抛出错误
return code
<<<<<<< HEAD
# 忘记密码功能中的验证码发送表单(仅需邮箱字段)
=======
#clean_code方法调用工具方法utils.verify验证验证码有效性无效则抛出错误
>>>>>>> LXY_branch
class ForgetPasswordCodeForm(forms.Form):
"""
发送密码重置验证码的表单
仅包含邮箱字段用于提交需要重置密码的邮箱
"""
email = forms.EmailField(
label=_('Email'),
<<<<<<< HEAD
)
=======
)
#仅包含email字段邮箱输入用于忘记密码流程中验证邮箱的步骤
>>>>>>> LXY_branch

@ -3,33 +3,112 @@ 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
from djangoblog.utils import get_current_site # 导入获取当前站点信息的工具函数
# Create your models here.
<<<<<<< HEAD
# 自定义用户模型继承Django内置的AbstractUser
class BlogUser(AbstractUser):
<<<<<<< HEAD
# 用户昵称(可选)
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)
# 账号创建来源(如:网站注册/第三方登录等,可选)
=======
"""
自定义用户模型继承自Django内置的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)
# 账号创建来源(如管理员添加、前台注册等),用于追踪用户注册渠道
>>>>>>> JYN_branch
source = models.CharField(_('create source'), max_length=100, blank=True)
# 获取用户详情页的绝对URL用于模板中的{% url %}反向解析)
=======
class BlogUser(AbstractUser):#自定义用户模型BlogUser继承自Django内置的AbstractUser可扩展的用户抽象类
nickname = models.CharField(_('nick name'), max_length=100, blank=True)#定义nickname字段字符类型支持国际化翻译最大长度100可为空。
creation_time = models.DateTimeField(_('creation time'), default=now)#定义creation_time字段日期时间类型默认值为当前时间now方法
last_modify_time = models.DateTimeField(_('last modify time'), default=now)#定义last_modify_time字段日期时间类型默认值为当前时间。
source = models.CharField(_('create source'), max_length=100, blank=True)#定义source字段字符类型记录用户创建来源最大长度100可为空。
>>>>>>> LXY_branch
def get_absolute_url(self):
"""
返回用户详情页的URL
Django推荐为模型定义此方法用于获取对象的标准URL
"""
return reverse(
<<<<<<< HEAD
'blog:author_detail', kwargs={
<<<<<<< HEAD
'author_name': self.username})
# 定义对象的字符串表示Admin后台和shell中显示
=======
'blog:author_detail', # 对应的URL名称
kwargs={'author_name': self.username} # 传递的参数
)
=======
'author_name': self.username})#定义获取用户详情页绝对URL的方法通过reverse反向解析路由blog:author_detail传递username参数。
>>>>>>> LXY_branch
>>>>>>> JYN_branch
def __str__(self):
<<<<<<< HEAD
"""模型的字符串表示,这里返回用户的邮箱"""
return self.email
# 获取用户详情页的完整URL包含域名用于分享链接
=======
return self.email#定义对象的字符串表示方法返回用户的email
>>>>>>> LXY_branch
def get_full_url(self):
site = get_current_site().domain
<<<<<<< HEAD
site = get_current_site().domain# 获取当前站点域名
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
<<<<<<< HEAD
=======
"""获取用户详情页的完整URL包含域名"""
# 获取当前站点的域名
site = get_current_site().domain
# 拼接完整URL协议+域名+路径)
url = "https://{site}{path}".format(
site=site,
path=self.get_absolute_url() # 调用get_absolute_url获取相对路径
)
>>>>>>> JYN_branch
return url
# 元数据配置(模型级别的选项)
class Meta:
<<<<<<< HEAD
ordering = ['-id'] # 默认按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' # 指定使用id字段获取最新对象用于Model.objects.latest()
>>>>>>> JYN_branch
=======
return url#定义获取带域名的完整URL方法结合当前站点域名和get_absolute_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'#指定按id获取最新记录
>>>>>>> LXY_branch

@ -3,182 +3,404 @@ 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
from accounts.models import BlogUser # 导入自定义用户模型
from blog.models import Article, Category # 导入博客相关模型
from djangoblog.utils import * # 导入项目工具函数
from . import utils # 导入当前应用的工具函数
# Create your tests here.
# 创建测试类继承Django的TestCase
class AccountTest(TestCase):
<<<<<<< HEAD
# 测试初始化方法(每个测试方法运行前都会执行)
def setUp(self):
# 初始化测试客户端(模拟浏览器请求)
self.client = Client()
# 初始化请求工厂(用于生成请求对象)
self.factory = RequestFactory()
# 创建一个普通测试用户
=======
"""
账号相关功能的测试类继承自Django的TestCase
包含用户登录注册密码重置等功能的测试用例
"""
def setUp(self):
"""
测试前的初始化方法会在每个测试方法执行前运行
用于创建测试所需的基础数据
"""
self.client = Client() # 创建测试客户端,用于模拟用户请求
self.factory = RequestFactory() # 创建请求工厂,用于构建请求对象
# 创建一个测试用户
>>>>>>> JYN_branch
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
)
<<<<<<< HEAD
# 测试用的随机字符串
self.new_test = "xxx123--="
# 测试用户账号验证功能
def test_validate_account(self):
# 获取当前站点域名
site = get_current_site().domain
# 创建一个超级用户(用于测试管理员权限)
=======
self.new_test = "xxx123--=" # 测试用的新密码
def test_validate_account(self):
"""测试用户账号验证相关功能,包括登录和管理员权限"""
site = get_current_site().domain # 获取当前站点域名
# 创建一个超级用户
>>>>>>> JYN_branch
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="qwer!@#$ggg")
<<<<<<< HEAD
# 从数据库获取刚创建的超级用户(验证是否创建成功)
testuser = BlogUser.objects.get(username='liangliangyy1')
=======
# 获取创建的用户
testuser = BlogUser.objects.get(username='liangliangyy1')
>>>>>>> JYN_branch
# 测试登录功能
loginresult = self.client.login(
username='liangliangyy1',
password='qwer!@#$ggg')
self.assertEqual(loginresult, True)
<<<<<<< HEAD
self.assertEqual(loginresult, True)# 验证登录成功
# 测试访问管理员后台
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200)# 验证返回200状态码
=======
self.assertEqual(loginresult, True) # 断言登录成功
# 测试访问管理员页面
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200) # 断言页面访问成功
>>>>>>> JYN_branch
# 创建测试分类
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
<<<<<<< HEAD
=======
>>>>>>> JYN_branch
# 创建测试文章
article = Article()
article.title = "nicetitleaaa"
article.body = "nicecontentaaa"
<<<<<<< HEAD
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) # 验证返回200状态码
=======
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.type = 'a' # 假设'a'表示文章类型
article.status = 'p' # 假设'p'表示已发布
article.save()
# 测试访问文章管理页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
<<<<<<< HEAD
self.assertEqual(response.status_code, 200) # 断言页面访问成功
>>>>>>> JYN_branch
=======
self.assertEqual(response.status_code, 200)#测试管理员账号登录后台功能:创建超级用户,验证登录状态和后台页面访问状态
>>>>>>> LXY_branch
# 测试用户注册功能
def test_validate_register(self):
<<<<<<< HEAD
# 验证测试邮箱初始不存在
=======
"""测试用户注册功能,包括注册流程、邮箱验证和权限控制"""
# 初始状态下,该邮箱应不存在
>>>>>>> JYN_branch
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
<<<<<<< HEAD
# 模拟注册请求
=======
# 模拟用户注册提交
>>>>>>> JYN_branch
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',
})
<<<<<<< HEAD
# 验证用户已创建(通过邮箱查询)
=======
# 注册后,该邮箱应存在
>>>>>>> JYN_branch
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
<<<<<<< HEAD
# 获取刚注册的用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 生成验证签名(用于邮箱验证等场景)
=======
# 获取刚注册的用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 生成验证链接(模拟邮箱验证流程)
>>>>>>> JYN_branch
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)
<<<<<<< HEAD
# 测试访问验证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')
# 获取指定邮箱的用户并设置为超级用户和工作人员
=======
# 访问验证链接
response = self.client.get(url)
self.assertEqual(response.status_code, 200) # 断言验证页面访问成功
# 登录新注册用户
self.client.login(username='user1233', password='password123!q@wE#R$T')
# 提升用户权限
>>>>>>> JYN_branch
user = BlogUser.objects.filter(email='user123@user.com')[0]
user.is_superuser = True
user.is_staff = True
user.save()
<<<<<<< HEAD
# 删除侧边栏缓存
delete_sidebar_cache()
# 创建分类
=======
# 清除缓存
delete_sidebar_cache()
# 创建测试分类
>>>>>>> JYN_branch
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
<<<<<<< HEAD
# 创建文章
=======
# 创建测试文章
>>>>>>> JYN_branch
article = Article()
article.category = category
article.title = "nicetitle333"
article.body = "nicecontentttt"
article.author = user
article.type = 'a'
article.status = 'p'
article.save()
<<<<<<< HEAD
# 测试已登录用户访问文章管理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.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])
self.assertIn(response.status_code, [301, 302, 200]) # 登出通常是重定向
# 登出后访问管理页面(应被拒绝或重定向)
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
# 使用错误密码登录
>>>>>>> JYN_branch
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
'password': 'password123'# 注意这里密码与登录时使用的不同
})
self.assertIn(response.status_code, [301, 302, 200])
<<<<<<< HEAD
# 测试使用错误密码登录后访问文章管理URL应重定向
=======
# 错误登录后访问管理页面
>>>>>>> JYN_branch
response = self.client.get(article.get_admin_url())
<<<<<<< HEAD
self.assertIn(response.status_code, [301, 302, 200])
# 测试邮箱验证码验证
=======
self.assertIn(response.status_code, [301, 302, 200])#测试用户注册流程:验证注册前后用户数量变化,邮箱验证链接的有效性,以及注册后用户权限、文章发布等功能
>>>>>>> LXY_branch
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)
<<<<<<< HEAD
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)
<<<<<<< HEAD
self.assertEqual(type(err), str)# 应返回错误信息字符串
# 测试忘记密码发送验证码功能 - 成功情况
=======
code = generate_code() # 生成验证码
utils.set_code(to_email, code) # 存储验证码
utils.send_verify_email(to_email, code) # 发送验证邮件
=======
self.assertEqual(type(err), str)#测试邮箱验证码功能:验证有效邮箱和无效邮箱的验证码校验结果
>>>>>>> LXY_branch
# 验证正确的邮箱和验证码
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) # 应返回错误信息
>>>>>>> JYN_branch
def test_forget_password_email_code_success(self):
"""测试发送密码重置验证码成功的情况"""
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
<<<<<<< HEAD
data=dict(email="admin@admin.com") # 使用正确邮箱格式
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
<<<<<<< HEAD
self.assertEqual(resp.content.decode("utf-8"), "ok")# 验证返回成功消息
# 测试忘记密码发送验证码功能 - 失败情况
def test_forget_password_email_code_fail(self):
# 测试不提供邮箱
=======
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")#测试忘记密码的邮箱验证码发送:分别验证成功和失败场景(如邮箱错误)的接口响应
>>>>>>> LXY_branch
def test_forget_password_email_code_fail(self):
"""测试发送密码重置验证码失败的情况"""
# 不提供邮箱
>>>>>>> JYN_branch
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
)
<<<<<<< HEAD
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 测试提供错误格式邮箱
=======
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") # 断言返回错误信息
# 提供无效格式的邮箱
>>>>>>> JYN_branch
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com")
)
<<<<<<< HEAD
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)# 为测试用户设置验证码
# 准备重置密码数据
=======
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) # 存储验证码
# 准备重置密码的数据
>>>>>>> JYN_branch
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, # 验证码
)
<<<<<<< HEAD
# 提交重置密码请求
=======
# 提交密码重置请求
>>>>>>> JYN_branch
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 302)
<<<<<<< HEAD
self.assertEqual(resp.status_code, 302) # 应重定向
=======
self.assertEqual(resp.status_code, 302) # 成功重置后通常重定向
>>>>>>> JYN_branch
# 验证用户密码是否修改成功
# 验证密码是否已更新
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
).first() # type: BlogUser
self.assertNotEqual(blog_user, None)
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",
<<<<<<< HEAD
email="123@123.com",# 不存在的邮箱
=======
email="123@123.com", # 不存在的邮箱
>>>>>>> JYN_branch
code="123456",
)
resp = self.client.post(
@ -186,22 +408,46 @@ class AccountTest(TestCase):
data=data
)
self.assertEqual(resp.status_code, 200)
<<<<<<< HEAD
self.assertEqual(resp.status_code, 200) # 应返回错误页面而非重定向
=======
self.assertEqual(resp.status_code, 200) # 应返回页面但不重置密码
>>>>>>> JYN_branch
# 测试忘记密码重置功能 - 验证码错误情况
def test_forget_password_email_code_error(self):
<<<<<<< HEAD
code = generate_code()
utils.set_code(self.blog_user.email, code)
utils.set_code(self.blog_user.email, code)# 设置正确验证码
# 使用错误验证码提交
=======
"""测试使用错误的验证码重置密码的情况"""
code = generate_code() # 生成正确验证码
utils.set_code(self.blog_user.email, code) # 存储验证码
# 使用错误的验证码
>>>>>>> JYN_branch
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code="111111",
<<<<<<< HEAD
code="111111",# 错误验证码
=======
code="111111", # 错误的验证码
>>>>>>> JYN_branch
)
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 200)
<<<<<<< HEAD
<<<<<<< HEAD
self.assertEqual(resp.status_code, 200)# 应返回错误页面而非重定向
=======
self.assertEqual(resp.status_code, 200)#测试忘记密码流程:成功场景:验证密码修改后是否生效;失败场景:验证不存在用户、验证码错误时的接口响应
>>>>>>> LXY_branch
=======
self.assertEqual(resp.status_code, 200) # 应返回页面但不重置密码
>>>>>>> JYN_branch

@ -1,28 +1,113 @@
<<<<<<< HEAD
# 导入 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"
# 定义 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 名称
]
=======
from django.urls import path
from django.urls import re_path
from django.urls import re_path # 用于正则表达式匹配URL
from . import views
from .forms import LoginForm
from . import views # 导入当前应用的视图函数/类
from .forms import LoginForm # 导入自定义的登录表单
# 定义应用命名空间避免URL名称冲突
app_name = "accounts"
<<<<<<< HEAD
# URL路由配置列表映射URL路径到对应的视图
urlpatterns = [
# 登录页面路由使用正则匹配以login/结尾的路径
re_path(r'^login/$',
# 调用LoginView类视图指定登录成功后重定向到首页/
views.LoginView.as_view(success_url='/'),
name='login', # URL的名称用于反向解析
# 向视图传递额外参数指定登录表单为自定义的LoginForm
kwargs={'authentication_form': LoginForm}),
# 注册页面路由匹配以register/结尾的路径
re_path(r'^register/$',
# 调用RegisterView类视图注册成功后重定向到首页
views.RegisterView.as_view(success_url="/"),
name='register'), # URL名称用于反向解析
# 登出功能路由匹配以logout/结尾的路径
re_path(r'^logout/$',
# 调用LogoutView类视图Django内置或自定义
views.LogoutView.as_view(),
name='logout'), # URL名称
# 账号操作结果页面路由精确匹配account/result.html路径
path(r'account/result.html',
# 调用函数视图account_result
views.account_result,
name='result'), # URL名称用于展示注册/验证等结果
# 忘记密码页面路由匹配以forget_password/结尾的路径
re_path(r'^forget_password/$',
# 调用ForgetPasswordView类视图
views.ForgetPasswordView.as_view(),
name='forget_password'), # URL名称
# 发送密码重置验证码页面路由匹配以forget_password_code/结尾的路径
re_path(r'^forget_password_code/$',
# 调用ForgetPasswordEmailCode类视图处理发送验证码逻辑
views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'), # URL名称
]
>>>>>>> JYN_branch
=======
urlpatterns = [re_path(r'^login/$',
views.LoginView.as_view(success_url='/'),
name='login',
kwargs={'authentication_form': LoginForm}),
kwargs={'authentication_form': LoginForm}),#登录路由对应LoginView用LoginForm表单成功重定向/
re_path(r'^register/$',
views.RegisterView.as_view(success_url="/"),
name='register'),
name='register'),#注册路由对应RegisterView成功重定向/。
re_path(r'^logout/$',
views.LogoutView.as_view(),
name='logout'),
name='logout'),#登出路由对应LogoutView。
path(r'account/result.html',
views.account_result,
name='result'),
name='result'),#结果页路由对应account_result视图
re_path(r'^forget_password/$',
views.ForgetPasswordView.as_view(),
name='forget_password'),
name='forget_password'),#忘记密码路由对应ForgetPasswordView
re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'),
name='forget_password_code'),#忘记密码验证码路由对应ForgetPasswordEmailCode
]
>>>>>>> LXY_branch

@ -1,26 +1,112 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
<<<<<<< HEAD
# 导入 Django 认证系统所需的模块
from django.contrib.auth import get_user_model# 动态获取当前项目的 User 模型
from django.contrib.auth.backends import ModelBackend# Django 默认的认证后端基类
=======
from django.contrib.auth import get_user_model # 获取项目配置的用户模型(支持自定义模型)
from django.contrib.auth.backends import ModelBackend # 导入Django内置的模型认证后端
>>>>>>> JYN_branch
class EmailOrUsernameModelBackend(ModelBackend):
class EmailOrUsernameModelBackend(ModelBackend):#自定义Django认证后端支持用户名或邮箱两种方式登录。
"""
<<<<<<< HEAD
允许使用用户名或邮箱登录
承自 Django ModelBackend重写 authenticate get_user 方法
=======
自定义认证后端继承自Django的ModelBackend
扩展功能允许用户使用用户名邮箱两种方式登录而非仅支持用户名
>>>>>>> JYN_branch
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""
<<<<<<< HEAD
重写认证方法支持用户名或邮箱登录
参数:
request: HttpRequest 对象可能包含额外的认证上下文
username: 用户输入的用户名或邮箱前端传递的字段名固定为 username
password: 用户输入的密码
**kwargs: 其他可能的参数如通过信号传递的额外参数
返回:
如果认证成功返回 User 对象否则返回 None
"""
# 判断输入是邮箱还是用户名(通过 '@' 符号区分)
if '@' in username:
# 如果是邮箱,设置查询条件为 email 字段
kwargs = {'email': username}
else:
# 否则按用户名查询
=======
认证核心方法验证用户输入的凭证用户名/邮箱 + 密码是否有效
参数说明
- request当前请求对象
- username前端传入的用户名参数实际可能是用户名或邮箱
- password前端传入的密码明文
返回验证成功返回用户对象失败返回None
"""
# 判断输入的「username」是否包含@符号,以此区分邮箱和用户名
if '@' in username:
# 若包含@,则按邮箱字段查询用户
kwargs = {'email': username}
else:
# 若不包含@,则按用户名字段查询用户
>>>>>>> JYN_branch
kwargs = {'username': username}
try:
<<<<<<< HEAD
# 尝试从数据库获取用户(使用当前项目的自定义 User 模型)
user = get_user_model().objects.get(**kwargs)
# 验证密码是否正确(使用 Django 的密码哈希校验)
if user.check_password(password):
return user# 认证成功返回用户对象
except get_user_model().DoesNotExist:
# 用户不存在时返回 NoneDjango 会继续尝试其他认证后端)
=======
# 根据上述条件从数据库查询唯一用户
user = get_user_model().objects.get(**kwargs)
# 验证密码check_password会自动将明文密码与数据库中存储的哈希密码比对
if user.check_password(password):
return user
return user # 密码正确,返回用户对象(认证成功)
except get_user_model().DoesNotExist:
# 若查询不到用户(用户名/邮箱不存在返回None认证失败
>>>>>>> JYN_branch
return None
#核心认证逻辑:判断输入是否为邮箱(含@分别用邮箱或用户名查询用户验证密码后返回用户对象若用户不存在则返回None。
def get_user(self, username):
"""
<<<<<<< HEAD
根据用户 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
<<<<<<< HEAD
=======
根据用户ID获取用户对象Django认证系统必须实现的方法
作用认证成功后系统通过此方法获取用户完整信息
参数username实际为用户IDDjango默认用ID作为唯一标识
返回存在则返回用户对象不存在返回None
"""
try:
# 通过主键ID查询用户
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
# 若用户不存在返回None
return None
>>>>>>> JYN_branch
=======
#根据用户ID主键查询用户不存在则返回None用于Django认证系统的用户查询环节
>>>>>>> LXY_branch

@ -1,49 +1,137 @@
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 _
<<<<<<< HEAD
# 导入 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# 假设是项目封装的邮件发送函数
from djangoblog.utils import send_email
# 验证码有效期5分钟
=======
from django.core.cache import cache # 导入Django缓存模块用于存储验证码
from django.utils.translation import gettext # 用于获取即时翻译文本
from django.utils.translation import gettext_lazy as _ # 用于延迟翻译文本(支持国际化)
<<<<<<< HEAD
from djangoblog.utils import send_email # 导入项目自定义的发送邮件工具函数
# 验证码有效期5分钟全局变量统一控制时效
>>>>>>> JYN_branch
_code_ttl = timedelta(minutes=5)
=======
_code_ttl = timedelta(minutes=5)#验证码有效期设置为5分钟。
>>>>>>> LXY_branch
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""发送重设密码验证码
"""
<<<<<<< HEAD
# 构造邮件正文(使用国际化字符串,并插入动态验证码)
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
# 调用项目封装的邮件发送函数
=======
发送邮箱验证码邮件主要用于密码重置场景
Args:
to_mail: 接受邮箱
subject: 邮件主题
code: 验证码
to_mail: 收验证码的目标邮箱地址
code: 生成的随机验证码
subject: 邮件主题默认值为Verify Email支持国际化
"""
# 构造邮件HTML内容包含验证码和有效期提示使用%(code)s占位符注入验证码
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
# 调用发送邮件函数,参数依次为:收件人列表、邮件主题、邮件内容
>>>>>>> JYN_branch
send_email([to_mail], subject, html_content)
def verify(email: str, code: str) -> typing.Optional[str]:
"""验证code是否有效
"""
<<<<<<< HEAD
# 从缓存获取该邮箱对应的正确验证码
cache_code = get_code(email)
# 比对用户输入的验证码和缓存中的验证码
if cache_code != code:
return gettext("Verification code error") # 返回翻译后的错误信息
# 隐含逻辑:验证成功时返回 None调用方需检查返回值是否为 None
=======
验证用户输入的验证码是否有效
Args:
email: 请求邮箱
code: 验证码
email: 用户提交的邮箱用于匹配缓存中的验证码
code: 用户输入的验证码
Return:
如果有错误就返回错误str
Node:
这里的错误处理不太合理应该采用raise抛出
否测调用方也需要对error进行处理
验证失败时返回错误提示字符串验证成功时返回None
Note:
代码注释中指出当前错误处理逻辑不合理应使用raise抛出异常而非返回错误字符串
若返回错误字符串调用方需额外判断返回值是否为错误增加了代码耦合度
"""
# 从缓存中获取该邮箱对应的验证码
cache_code = get_code(email)
# 对比用户输入的验证码与缓存中的验证码
if cache_code != code:
# 验证码不匹配时,返回国际化的错误提示
return gettext("Verification code error")
>>>>>>> JYN_branch
def set_code(email: str, code: str):
"""
<<<<<<< HEAD
将验证码存入缓存
Args:
email (str): 用户邮箱作为缓存键
code (str): 要存储的验证码
Note:
验证码有效期由全局变量 _code_ttl 控制5分钟
"""
# 使用 Django 缓存设置键值对,并指定过期时间(转换为秒)
"""设置code"""
=======
将验证码存入缓存以邮箱为key设置有效期
Args:
email: 作为缓存key的邮箱地址确保一个邮箱对应一个验证码
code: 需要存入缓存的验证码
"""
# 调用Django缓存的set方法key=邮箱value=验证码timeout=有效期(秒)
>>>>>>> JYN_branch
cache.set(email, code, _code_ttl.seconds)
<<<<<<< HEAD
def get_code(email: str) -> typing.Optional[str]:
"""
<<<<<<< HEAD
从缓存获取验证码
Args:
email (str): 用户邮箱缓存键
Returns:
typing.Optional[str]:
- 存在则返回验证码字符串
- 不存在或过期返回 None
"""
# 直接调用 Django 缓存的 get 方法
=======
def get_code(email: str) -> typing.Optional[str]:#从缓存中获取指定邮箱对应的验证码
>>>>>>> LXY_branch
"""获取code"""
return cache.get(email)
=======
从缓存中获取指定邮箱对应的验证码
Args:
email: 用于查询的邮箱地址缓存key
Return:
缓存中存在该邮箱对应的验证码时返回字符串不存在时返回None
"""
# 调用Django缓存的get方法根据邮箱key获取验证码
return cache.get(email)
>>>>>>> JYN_branch

@ -1,59 +1,164 @@
# 导入日志模块,用于记录运行时的信息和错误
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
<<<<<<< HEAD
# 认证相关常量(如重定向字段名)
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__)
=======
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 # Django内置登录表单
from django.contrib.auth.hashers import make_password # 密码哈希处理函数
from django.http import HttpResponseRedirect, HttpResponseForbidden # HTTP响应类
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404, render # 快捷函数
from django.urls import reverse # URL反向解析
from django.utils.decorators import method_decorator # 类视图装饰器工具
from django.utils.http import url_has_allowed_host_and_scheme # 验证重定向URL安全性
from django.views import View # 基础视图类
from django.views.decorators.cache import never_cache # 禁止缓存装饰器
from django.views.decorators.csrf import csrf_protect # CSRF保护装饰器
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__) # 初始化日志记录器
>>>>>>> JYN_branch
# Create your views here.
# 注册视图类(继承自 FormView处理表单提交
class RegisterView(FormView):
<<<<<<< HEAD
# 指定使用的表单类
form_class = RegisterForm
<<<<<<< HEAD
template_name = 'account/registration_form.html'
# 使用装饰器确保视图禁用缓存never_cache并启用 CSRF 防护
=======
template_name = 'account/registration_form.html'#处理用户注册逻辑指定表单类RegisterForm和模板account/registration_form.html。
>>>>>>> LXY_branch
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 调用父类方法处理请求
=======
"""
用户注册类视图继承自FormView处理表单提交的通用视图
负责用户注册表单展示数据验证发送验证邮件及注册结果跳转
"""
form_class = RegisterForm # 指定使用的注册表单
template_name = 'account/registration_form.html' # 注册页面模板路径
@method_decorator(csrf_protect) # 为视图添加CSRF保护
def dispatch(self, *args, **kwargs):
"""重写分发方法,添加装饰器后调用父类逻辑"""
>>>>>>> JYN_branch
return super(RegisterView, self).dispatch(*args, **kwargs)
# 表单验证通过后的处理逻辑
def form_valid(self, form):
<<<<<<< HEAD
# 再次检查表单有效性(冗余,因为 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)
# 构造邮件内容(包含验证链接)
=======
"""表单验证通过后执行的逻辑(注册核心流程)"""
if form.is_valid():
# 1. 暂存用户数据不立即保存is_active设为False需邮箱验证后激活
user = form.save(False)
user.is_active = False # 初始状态:未激活(需邮箱验证)
user.source = 'Register' # 标记注册来源为“前台注册”
user.save(True) # 保存用户到数据库
# 2. 生成邮箱验证链接(包含签名,防止篡改)
site = get_current_site().domain # 获取当前站点域名
# 双重SHA256加密用SECRET_KEY+用户ID生成签名确保链接安全性
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境下替换域名(适配本地测试)
if settings.DEBUG:
site = '127.0.0.1:8000'
# 反向解析结果页URL拼接完整验证链接
path = reverse('account:result')
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
# 3. 构造验证邮件内容并发送
>>>>>>> JYN_branch
content = """
<p>请点击下面链接验证您的邮箱</p>
@ -64,141 +169,421 @@ class RegisterView(FormView):
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
# 发送验证邮件
send_email(
<<<<<<< HEAD
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
<<<<<<< HEAD
})
=======
emailto=[user.email], # 收件人邮箱(新注册用户的邮箱)
title='验证您的电子邮箱', # 邮件标题
content=content # 邮件HTML内容
)
=======
})#form_valid方法中保存用户并设置为非活跃状态生成邮箱验证链接并发送验证邮件最后重定向到结果页。
>>>>>>> LXY_branch
# 4. 跳转到注册结果页(提示用户查收验证邮件)
url = reverse('accounts:result') + f'?type=register&id={str(user.id)}'
return HttpResponseRedirect(url)
else:
# 表单验证失败,重新渲染表单并显示错误
return self.render_to_response({'form': form})
>>>>>>> JYN_branch
# 登出视图继承自RedirectView重定向到登录页面
class LogoutView(RedirectView):
<<<<<<< HEAD
<<<<<<< HEAD
# 登出后重定向的URL
url = '/login/'
# 使用never_cache装饰器确保视图不会被缓存
=======
url = '/login/'#处理用户登出,登出后重定向到/login/
>>>>>>> LXY_branch
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
# 调用父类的dispatch方法处理请求
=======
"""
用户登出类视图继承自RedirectView处理重定向的通用视图
负责清除用户会话缓存并重定向到登录页
"""
url = '/login/' # 登出后默认重定向地址(登录页)
@method_decorator(never_cache) # 禁止缓存登出页面,避免浏览器缓存导致的问题
def dispatch(self, request, *args, **kwargs):
"""重写分发方法,添加装饰器后调用父类逻辑"""
>>>>>>> JYN_branch
return super(LogoutView, self).dispatch(request, *args, **kwargs)
# 处理GET请求
def get(self, request, *args, **kwargs):
<<<<<<< HEAD
# 执行登出操作
logout(request)
# 删除侧边栏缓存
delete_sidebar_cache()
<<<<<<< HEAD
# 调用父类的get方法完成重定向
return super(LogoutView, self).get(request, *args, **kwargs)
=======
"""处理GET请求登出核心逻辑"""
logout(request) # 清除用户会话,实现登出
delete_sidebar_cache() # 删除侧边栏缓存(可能存储了用户相关信息)
return super(LogoutView, self).get(request, *args, **kwargs) # 执行重定向
>>>>>>> JYN_branch
=======
return super(LogoutView, self).get(request, *args, **kwargs)#get方法中调用logout登出用户删除侧边栏缓存后完成重定向
>>>>>>> LXY_branch
# 登录视图继承自FormView
class LoginView(FormView):
<<<<<<< HEAD
# 使用的表单类
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'))
# 使用多个装饰器装饰dispatch方法
@method_decorator(sensitive_post_parameters('password'))# 标记密码参数为敏感信息
@method_decorator(csrf_protect) # 启用CSRF保护
@method_decorator(never_cache)# 禁止缓存
def dispatch(self, request, *args, **kwargs):
# 调用父类的dispatch方法处理请求
=======
"""
用户登录类视图继承自FormView
负责登录表单展示数据验证用户认证记住登录状态及重定向
"""
form_class = LoginForm # 指定使用的自定义登录表单
template_name = 'account/login.html' # 登录页面模板路径
success_url = '/' # 登录成功默认重定向地址(首页)
redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名默认next
login_ttl = 2626560 # “记住登录”状态的有效期约等于1个月
# 为视图添加多重装饰器敏感参数保护、CSRF保护、禁止缓存
@method_decorator(sensitive_post_parameters('password')) # 保护密码参数,避免日志泄露
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
>>>>>>> JYN_branch
return super(LoginView, self).dispatch(request, *args, **kwargs)
# 获取模板上下文数据
def get_context_data(self, **kwargs):
<<<<<<< HEAD
# 从GET参数中获取重定向URL
=======
"""添加额外上下文数据(重定向地址)到模板"""
# 获取URL中的重定向参数如登录前访问的受保护页面
>>>>>>> JYN_branch
redirect_to = self.request.GET.get(self.redirect_field_name)
# 如果不存在则设置为根路径
if redirect_to is None:
<<<<<<< HEAD
redirect_to = '/'
# 将重定向URL添加到上下文
kwargs['redirect_to'] = redirect_to
# 调用父类方法获取其他上下文数据
<<<<<<< HEAD
return super(LoginView, self).get_context_data(**kwargs)
# 表单验证通过后的处理
def form_valid(self, form):
# 重新创建认证表单这里可能有逻辑问题因为form已经传入
=======
redirect_to = '/' # 默认重定向到首页
kwargs['redirect_to'] = redirect_to # 将重定向地址传入模板上下文
return super(LoginView, self).get_context_data(** kwargs)
=======
return super(LoginView, self).get_context_data(**kwargs)#处理用户登录逻辑指定表单类LoginForm、模板account / login.html和成功后重定向地址 /
>>>>>>> LXY_branch
def form_valid(self, form):
"""表单验证通过后执行的逻辑(登录核心流程)"""
# 用Django内置AuthenticationForm重新验证确保认证逻辑符合默认规范
>>>>>>> JYN_branch
form = AuthenticationForm(data=self.request.POST, request=self.request)
# 再次验证表单
if form.is_valid():
<<<<<<< HEAD
# 删除侧边栏缓存
delete_sidebar_cache()
# 记录日志
logger.info(self.redirect_field_name)
# 登录用户
auth.login(self.request, form.get_user())
# 如果用户选择了"记住我"
=======
delete_sidebar_cache() # 删除侧边栏缓存(更新用户登录状态)
logger.info(self.redirect_field_name) # 日志记录重定向字段名
# 执行登录:将用户信息存入会话
auth.login(self.request, form.get_user())
# 处理“记住我”功能若勾选设置会话有效期为1个月
>>>>>>> JYN_branch
if self.request.POST.get("remember"):
# 设置较长的会话过期时间
self.request.session.set_expiry(self.login_ttl)
<<<<<<< HEAD
# 调用父类方法处理成功跳转
=======
# 调用父类form_valid执行重定向
>>>>>>> JYN_branch
return super(LoginView, self).form_valid(form)
# return HttpResponseRedirect('/')
else:
<<<<<<< HEAD
# 表单无效,重新渲染表单并显示错误
return self.render_to_response({
'form': form
<<<<<<< HEAD
})
# 获取成功后的跳转URL
=======
})#form_valid方法中验证表单登录用户并根据“记住我”选项设置会话过期时间
>>>>>>> LXY_branch
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
<<<<<<< HEAD
=======
# 表单验证失败(如密码错误),重新渲染表单并显示错误
return self.render_to_response({'form': form})
def get_success_url(self):
"""自定义登录成功后的重定向地址优先使用URL中的next参数"""
# 获取POST请求中的重定向地址用户登录前尝试访问的页面
redirect_to = self.request.POST.get(self.redirect_field_name)
# 验证重定向地址的安全性:避免跳转到外部恶意网站
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[self.request.get_host()]):
redirect_to = self.success_url # 不安全则使用默认重定向地址
>>>>>>> JYN_branch
return redirect_to
=======
return redirect_to#get_success_url方法处理登录后的重定向地址确保其安全性
>>>>>>> LXY_branch
# 账户操作结果页面(如注册成功、邮箱验证等)
def account_result(request):
<<<<<<< HEAD
# 从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('/')
# 检查类型参数是否有效
=======
"""
账号操作结果视图函数函数视图
处理注册成功提示邮箱验证逻辑并展示结果页面
"""
# 从URL参数中获取操作类型register/validation和用户ID
type = request.GET.get('type')
id = request.GET.get('id')
# 获取对应的用户若不存在则返回404
user = get_object_or_404(get_user_model(), id=id)
<<<<<<< HEAD
logger.info(type) # 日志记录操作类型
# 若用户已激活,直接重定向到首页(避免重复验证)
=======
logger.info(type)#处理注册和邮箱验证的结果逻辑根据type参数区分场景
>>>>>>> LXY_branch
if user.is_active:
return HttpResponseRedirect('/')
# 处理合法的操作类型(注册成功提示/邮箱验证)
>>>>>>> JYN_branch
if type and type in ['register', 'validation']:
if type == 'register':
# 注册成功:提示用户查收验证邮件
content = '''
恭喜您注册成功一封验证邮件已经发送到您的邮箱请验证您的邮箱后登录本站
'''
title = '注册成功'
else:
<<<<<<< HEAD
# 生成验证签名
=======
# 邮箱验证:验证签名是否正确,正确则激活用户
# 重新计算签名与URL中的签名对比防止链接篡改
>>>>>>> JYN_branch
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 获取请求中的签名
sign = request.GET.get('sign')
# 验证签名是否匹配
if sign != c_sign:
<<<<<<< HEAD
return HttpResponseForbidden()
# 激活用户账户
user.is_active = True
user.save()
# 验证成功提示内容
=======
return HttpResponseForbidden() # 签名不匹配返回403禁止访问
# 激活用户将is_active设为True
user.is_active = True
user.save()
# 验证成功:提示用户可登录
>>>>>>> JYN_branch
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
'''
title = '验证成功'
<<<<<<< HEAD
# 渲染结果页面
=======
# 渲染结果页面,传递标题和内容
>>>>>>> JYN_branch
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
<<<<<<< HEAD
# 无效类型重定向到首页
=======
# 操作类型不合法,重定向到首页
>>>>>>> JYN_branch
return HttpResponseRedirect('/')
# 忘记密码视图继承自FormView
class ForgetPasswordView(FormView):
<<<<<<< HEAD
# 使用的表单类
form_class = ForgetPasswordForm
<<<<<<< HEAD
# 模板文件路径
template_name = 'account/forget_password.html'
=======
"""
忘记密码类视图继承自FormView
负责密码重置表单展示数据验证及更新用户密码
"""
form_class = ForgetPasswordForm # 指定使用的密码重置表单
template_name = 'account/forget_password.html' # 密码重置页面模板路径
>>>>>>> JYN_branch
=======
template_name = 'account/forget_password.html'#处理忘记密码逻辑指定表单类ForgetPasswordForm和模板account/forget_password.html
>>>>>>> LXY_branch
# 表单验证通过后的处理
def form_valid(self, form):
"""表单验证通过后执行的逻辑(密码重置核心流程)"""
if form.is_valid():
<<<<<<< HEAD
# 根据邮箱获取用户对象
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
# 设置新密码(加密)
=======
# 1. 获取表单中的邮箱,查询对应的用户
blog_user = BlogUser.objects.filter(
email=form.cleaned_data.get("email")
).get()
# 2. 对新密码进行哈希处理,并更新用户密码
>>>>>>> JYN_branch
blog_user.password = make_password(form.cleaned_data["new_password2"])
# 保存用户对象
blog_user.save()
<<<<<<< HEAD
# 重定向到登录页面
return HttpResponseRedirect('/login/')
else:
<<<<<<< HEAD
# 表单无效,重新渲染表单并显示错误
=======
# 3. 密码重置成功,重定向到登录页
return HttpResponseRedirect('/login/')
else:
# 表单验证失败(如验证码错误、密码不一致),重新渲染表单
>>>>>>> JYN_branch
return self.render_to_response({'form': form})
# 忘记密码验证码发送视图继承自View
class ForgetPasswordEmailCode(View):
<<<<<<< HEAD
# 处理POST请求
=======
return self.render_to_response({'form': form})#form_valid方法中验证表单后重置用户密码并重定向到登录页
class ForgetPasswordEmailCode(View):# 处理忘记密码的邮箱验证码发送逻辑
>>>>>>> LXY_branch
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)
<<<<<<< HEAD
# 返回成功响应
return HttpResponse("ok")
=======
"""
发送密码重置验证码类视图继承自基础View
负责验证邮箱合法性生成验证码发送验证邮件并返回结果
"""
def post(self, request: HttpRequest):
"""处理POST请求发送验证码核心逻辑"""
# 1. 验证邮箱表单数据
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱") # 邮箱格式不合法,返回错误提示
# 2. 生成验证码并发送邮件
to_email = form.cleaned_data["email"] # 获取合法的邮箱地址
code = generate_code() # 生成随机验证码
utils.send_verify_email(to_email, code) # 发送验证码邮件
utils.set_code(to_email, code) # 将验证码存入缓存(设置有效期)
# 3. 操作成功返回“ok”提示
return HttpResponse("ok")
>>>>>>> JYN_branch
=======
return HttpResponse("ok")# post方法中验证邮箱表单生成并发送验证码将验证码存入缓存后返回成功标识
>>>>>>> LXY_branch

@ -1,4 +1,4 @@
import logging
import logging #导入 Python 标准库的 logging 模块,用于日志记录,方便追踪程序运行过程中的关键信息。
from django import forms
from haystack.forms import SearchForm

@ -1,47 +1,87 @@
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
# 导入Django Admin核心模块和辅助工具
from django.contrib import admin # Django Admin管理后台核心模块
from django.urls import reverse # 用于生成Django内部URL反转URL
from django.utils.html import format_html # 用于生成安全的HTML代码防止XSS攻击
from django.utils.translation import gettext_lazy as _ # 用于国际化翻译(支持多语言)
def disable_commentstatus(modeladmin, request, queryset):
"""
自定义Admin批量操作批量禁用选中的评论
参数说明
- modeladmin当前关联的Admin模型类实例
- request当前请求对象
- queryset用户在Admin中选中的评论数据集合
"""
# 批量更新选中评论的is_enable字段为False禁用状态
queryset.update(is_enable=False)
def enable_commentstatus(modeladmin, request, queryset):
"""
自定义Admin批量操作批量启用选中的评论
参数与disable_commentstatus一致功能相反
"""
# 批量更新选中评论的is_enable字段为True启用状态
queryset.update(is_enable=True)
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
# 为批量操作函数设置在Admin界面显示的名称支持国际化
disable_commentstatus.short_description = _('Disable comments') # 显示为“禁用评论”
enable_commentstatus.short_description = _('Enable comments') # 显示为“启用评论”
class CommentAdmin(admin.ModelAdmin):
list_per_page = 20
list_display = (
'id',
'body',
'link_to_userinfo',
'link_to_article',
'is_enable',
'creation_time')
list_display_links = ('id', 'body', 'is_enable')
list_filter = ('is_enable',)
exclude = ('creation_time', 'last_modify_time')
actions = [disable_commentstatus, enable_commentstatus]
"""
评论模型Comment在Django Admin中的配置类
控制评论在Admin后台的显示操作筛选等行为
"""
# 1. 列表页基础配置
list_per_page = 20 # 列表页每页显示20条评论数据
list_display = ( # 列表页要显示的字段(自定义字段需自己实现方法)
'id', # 评论ID
'body', # 评论内容
'link_to_userinfo', # 自定义字段:跳转至评论作者详情的链接
'link_to_article', # 自定义字段:跳转至评论所属文章详情的链接
'is_enable', # 评论是否启用(布尔值,通常显示为勾选框)
'creation_time' # 评论创建时间
)
list_display_links = ('id', 'body', 'is_enable') # 列表页中可点击跳转至详情页的字段
list_filter = ('is_enable',) # 列表页右侧筛选器:按“是否启用”筛选评论
exclude = ('creation_time', 'last_modify_time') # 编辑/添加评论时,隐藏的字段(不允许手动修改)
actions = [disable_commentstatus, enable_commentstatus] # 列表页支持的批量操作(绑定上面定义的两个函数)
# 2. 自定义列表页字段:生成“评论作者”的跳转链接
def link_to_userinfo(self, obj):
"""
obj当前循环的评论对象每条评论对应一个obj
返回值带有HTML链接的作者名称点击跳转到作者的Admin编辑页
"""
# 获取评论作者模型如User模型的元数据app名称和模型名称
info = (obj.author._meta.app_label, obj.author._meta.model_name)
# 反转生成作者Admin编辑页的URL格式为“admin:app名_模型名_change”参数为作者ID
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
# 生成安全的HTML链接优先显示作者昵称没有昵称则显示邮箱
# 注原代码中HTML标签内href属性缺失值应改为href="%s"),此处按正确逻辑补充
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
u'%s' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email)
)
# 3. 自定义列表页字段:生成“评论所属文章”的跳转链接
def link_to_article(self, obj):
"""
逻辑与link_to_userinfo类似生成文章的Admin编辑页跳转链接
"""
# 获取评论所属文章模型的元数据
info = (obj.article._meta.app_label, obj.article._meta.model_name)
# 反转生成文章Admin编辑页的URL参数为文章ID
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
# 生成HTML链接显示文章标题点击跳转到文章编辑页
return format_html(
u'<a href="%s">%s</a>' % (link, obj.article.title))
u'%s' % (link, obj.article.title)
)
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')
# 4. 为自定义字段设置在Admin界面显示的名称支持国际化
link_to_userinfo.short_description = _('User') # 自定义字段“link_to_userinfo”显示为“用户”
link_to_article.short_description = _('Article') # 自定义字段“link_to_article”显示为“文章”

@ -1,5 +1,18 @@
# 导入Django的App配置基类所有应用的配置类都需继承此类
from django.apps import AppConfig
class CommentsConfig(AppConfig):
"""
comments应用的配置类
作用定义应用的核心标识初始化行为等是Django识别和管理该应用的入口
"""
# 应用的唯一名称必须与应用目录名一致Django通过该值定位应用
name = 'comments'
<<<<<<< HEAD
# 可选扩展配置(当前代码未实现,可根据需求添加):
# - verbose_name应用的人性化名称如 verbose_name = "评论管理"用于Admin后台显示
# - default_auto_field指定模型默认的主键类型如 default_auto_field = "django.db.models.BigAutoField"
=======
>>>>>>> ZYY_branch

@ -1,13 +1,25 @@
from django import forms
from django.forms import ModelForm
# 导入Django表单核心模块
from django import forms # Django表单基础模块提供表单字段、验证等功能
from django.forms import ModelForm # 模型表单类,可快速将模型转换为表单(减少重复代码)
# 导入当前应用下的Comment模型评论模型表单需与该模型关联
from .models import Comment
class CommentForm(ModelForm):
"""
评论模型对应的模型表单类继承ModelForm
核心作用生成前端评论提交表单并关联Comment模型处理数据存储
"""
# 1. 自定义额外字段父评论ID用于实现评论回复功能
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
widget=forms.HiddenInput, # 表单控件:隐藏输入框(前端不显示,仅用于传递数据)
required=False # 是否必填False表示允许为空普通评论无父评论回复评论时才传值
)
# 2. Meta类模型表单的核心配置关联模型、指定字段等
class Meta:
model = Comment
fields = ['body']
model = Comment # 关联的模型当前表单与Comment模型绑定
fields = ['body'] # 表单需显示/处理的模型字段仅包含评论内容body字段
# 注Comment模型中其他字段如author、article、creation_time等
# 通常由后端自动填充如从登录态获取author无需前端用户输入

@ -1,38 +1,91 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# Django自动生成的数据库迁移文件用于创建Comment评论模型对应的数据库表
# 迁移文件作用:记录模型结构变化,通过`python manage.py migrate`同步到数据库
# 导入Django迁移所需模块配置、迁移基类、字段类型、关联逻辑、时间工具
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django.db.models.deletion # 用于定义外键删除策略如CASCADE
import django.utils.timezone # 用于时间字段的默认值
class Migration(migrations.Migration):
"""
Comment模型的初始迁移类负责在数据库中创建`comments_comment`
所有迁移类都必须继承migrations.Migration
"""
# 标识该迁移是模型的「初始迁移」第一次为Comment模型创建表
initial = True
# 迁移依赖:执行当前迁移前,必须先执行依赖的迁移
dependencies = [
('blog', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('blog', '0001_initial'), # 依赖blog应用的0001_initial迁移因Comment关联blog的Article模型
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型的可交换迁移(适配自定义用户模型)
]
# 迁移操作当前迁移要执行的具体数据库操作此处为「创建Comment表」
operations = [
# 1. 创建Comment模型对应的数据库表
migrations.CreateModel(
name='Comment',
name='Comment', # 模型名称与代码中定义的Comment类一致
# 2. 定义表的字段(对应模型中的字段,映射到数据库表的列)
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('body', models.TextField(max_length=300, 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='修改时间')),
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')),
# 主键字段BigAutoField自增bigint类型Django默认主键无需在模型中手动定义
('id', models.BigAutoField(
auto_created=True, # 自动创建
primary_key=True, # 设为主键
serialize=False, # 不序列化(主键无需序列化)
verbose_name='ID' # 字段显示名Admin后台中显示
)),
# 评论正文字段TextField长文本类型对应模型中的body字段
('body', models.TextField(
max_length=300, # 最大长度300字符
verbose_name='正文' # 显示名
)),
# 创建时间字段DateTimeField日期时间类型对应模型中的created_time
('created_time', models.DateTimeField(
default=django.utils.timezone.now, # 默认值为当前时区时间
verbose_name='创建时间'
)),
# 修改时间字段DateTimeField对应模型中的last_mod_time
('last_mod_time', models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='修改时间'
)),
# 是否显示字段BooleanField布尔类型对应模型中的is_enable
('is_enable', models.BooleanField(
default=True, # 默认值为True创建后默认显示
verbose_name='是否显示'
)),
# 外键关联文章blog应用的Article模型对应模型中的article字段
('article', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, # 级联删除:文章删,评论删
to='blog.article', # 关联目标blog应用的Article模型
verbose_name='文章'
)),
# 外键关联用户项目配置的用户模型对应模型中的author字段
('author', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, # 级联删除:用户删,评论删
to=settings.AUTH_USER_MODEL, # 关联目标:自定义用户模型(灵活适配)
verbose_name='作者'
)),
# 外键关联父评论自关联Comment模型自身对应模型中的parent_comment字段
('parent_comment', models.ForeignKey(
blank=True, # 表单中允许为空(普通评论无父评论)
null=True, # 数据库中允许为空与blank=True配合
on_delete=django.db.models.deletion.CASCADE, # 级联删除:父评论删,子评论删
to='comments.comment', # 关联目标comments应用的Comment模型自关联
verbose_name='上级评论'
)),
],
# 3. 模型的额外配置(映射到数据库表的属性和默认行为)
options={
'verbose_name': '评论',
'verbose_name_plural': '评论',
'ordering': ['-id'],
'get_latest_by': 'id',
'verbose_name': '评论', # 模型单数显示名Admin中显示
'verbose_name_plural': '评论', # 模型复数显示名与单数一致避免“评论s”
'ordering': ['-id'], # 表数据默认排序按id倒序最新评论在前
'get_latest_by': 'id', # 用Model.objects.latest()时按id取最新数据
# 注Django会自动根据模型名生成表名app名_模型名 → comments_comment
},
),
]
]

@ -5,14 +5,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comments', '0001_initial'),
]
dependencies = [
('comments', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='是否显示'),
),
operations = [
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='是否显示'),
),
]

@ -1,39 +1,64 @@
from django.conf import settings
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
# 导入Django核心模块配置、数据库模型、时间工具、国际化
from django.conf import settings # 导入项目配置(用于获取自定义用户模型)
from django.db import models # Django数据库模型基类所有模型需继承models.Model
from django.utils.timezone import now # 获取当前时区时间,用于时间字段默认值
from django.utils.translation import gettext_lazy as _ # 国际化翻译,支持多语言显示
# 导入关联模型从blog应用导入Article模型评论需关联到具体文章
from blog.models import Article
# Create your models here.
class Comment(models.Model):
"""
评论模型存储用户对文章的评论数据支持评论回复父子评论
与User用户Article文章为多对一关系与自身为自关联实现回复
"""
# 1. 评论正文长文本字段限制最大300字符
body = models.TextField('正文', max_length=300)
creation_time = models.DateTimeField(_('creation time'), default=now)
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 2. 时间字段:创建时间和最后修改时间,默认值为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now) # 评论创建时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now) # 评论最后修改时间
# 3. 关联用户:多对一(多个评论属于一个用户)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
on_delete=models.CASCADE)
settings.AUTH_USER_MODEL, # 关联项目配置的用户模型而非固定User更灵活
verbose_name=_('author'), # 字段在Admin后台显示的名称支持国际化
on_delete=models.CASCADE # 级联删除:若用户被删除,其所有评论也会被删除
)
# 4. 关联文章:多对一(多个评论属于一篇文章)
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE)
Article, # 关联blog应用的Article模型
verbose_name=_('article'), # Admin显示名
on_delete=models.CASCADE # 级联删除:文章删除,关联评论也删除
)
# 5. 父评论:自关联(实现评论回复,多个子评论对应一个父评论)
parent_comment = models.ForeignKey(
'self',
verbose_name=_('parent comment'),
blank=True,
null=True,
on_delete=models.CASCADE)
is_enable = models.BooleanField(_('enable'),
default=False, blank=False, null=False)
'self', # 关联自身模型(表示父评论)
verbose_name=_('parent comment'), # Admin显示名
blank=True, # 表单中允许为空(普通评论无父评论,回复评论才有)
null=True, # 数据库中允许为空与blank=True配合使用
on_delete=models.CASCADE # 级联删除:父评论删除,子评论也删除
)
# 6. 启用状态:布尔值,控制评论是否在前端显示
is_enable = models.BooleanField(
_('enable'),
default=False, # 默认禁用(需管理员审核后启用,防止垃圾评论)
blank=False, # 表单中不允许为空
null=False # 数据库中不允许为空
)
# 模型元数据:控制模型的整体行为(排序、显示名等)
class Meta:
ordering = ['-id']
verbose_name = _('comment')
verbose_name_plural = verbose_name
get_latest_by = 'id'
ordering = ['-id'] # 数据查询时按ID倒序排列最新评论在前
verbose_name = _('comment') # 模型单数显示名Admin中“评论”
verbose_name_plural = verbose_name # 模型复数显示名与单数一致避免“评论s”
get_latest_by = 'id' # 使用Model.objects.latest()时按id字段取最新数据
# 模型实例的字符串表示打印评论对象时显示正文便于调试和Admin显示
def __str__(self):
return self.body
return self.body

@ -1,109 +1,103 @@
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
# 导入Django测试核心模块、URL工具及项目内模型/工具
from django.test import Client, RequestFactory, TransactionTestCase # Django测试类Client模拟HTTP请求RequestFactory构造请求对象TransactionTestCase支持事务回滚
from django.urls import reverse # 生成URL通过URL名称反向解析避免硬编码
# 导入项目内关联模型:用户、分类、文章、评论模型
from accounts.models import BlogUser
from blog.models import Category, Article
from comments.models import Comment
# 导入评论相关自定义模板标签和通用工具函数
from comments.templatetags.comments_tags import *
from djangoblog.utils import get_max_articleid_commentid
# Create your tests here.
class CommentsTest(TransactionTestCase):
"""
评论功能测试类继承TransactionTestCase用于测试评论的提交状态更新等核心逻辑
支持数据库事务回滚确保测试用例间数据隔离
"""
def setUp(self):
"""
测试前置初始化方法每个测试用例执行前自动调用
作用创建测试所需的基础数据客户端用户系统配置等
"""
# 1. 初始化测试工具Client模拟浏览器请求RequestFactory构造原始请求对象
self.client = Client()
self.factory = RequestFactory()
from blog.models import BlogSettings
value = BlogSettings()
value.comment_need_review = True
value.save()
# 2. 初始化博客系统配置:设置“评论需审核”(模拟真实场景中评论需管理员审核才能显示)
from blog.models import BlogSettings # 局部导入避免循环引用
value = BlogSettings() # 创建配置对象
value.comment_need_review = True # 开启评论审核开关
value.save() # 保存到测试数据库
# 3. 创建测试超级用户:用于模拟登录状态下提交评论
self.user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="liangliangyy1")
email="liangliangyy1@gmail.com", # 测试邮箱
username="liangliangyy1", # 测试用户名
password="liangliangyy1" # 测试密码明文Django会自动加密存储
)
def update_article_comment_status(self, article):
"""
辅助方法批量更新某篇文章下所有评论的启用状态设为启用
模拟管理员审核通过评论的操作用于测试审核后评论的显示逻辑
参数
- article目标文章对象需更新其下所有评论
"""
# 获取该文章下所有评论
comments = article.comment_set.all()
# 遍历评论将“是否启用”字段设为True并保存
for comment in comments:
comment.is_enable = True
comment.save()
def test_validate_comment(self):
"""
核心测试用例验证评论提交流程登录创建文章提交评论验证状态
覆盖场景登录用户提交评论评论未审核时不显示审核后正常显示
"""
# 1. 模拟用户登录:使用之前创建的测试超级用户登录
self.client.login(username='liangliangyy1', password='liangliangyy1')
# 2. 创建测试分类:文章需关联分类,先创建分类数据
category = Category()
category.name = "categoryccc"
category.save()
category.name = "categoryccc" # 分类名称
category.save() # 保存到测试数据库
# 3. 创建测试文章:评论需关联文章,创建一篇已发布的文章
article = Article()
article.title = "nicetitleccc"
article.body = "nicecontentccc"
article.author = self.user
article.category = category
article.type = 'a'
article.status = 'p'
article.save()
article.title = "nicetitleccc" # 文章标题
article.body = "nicecontentccc" # 文章内容
article.author = self.user # 关联作者(测试用户)
article.category = category # 关联分类(刚创建的测试分类)
article.type = 'a' # 文章类型(假设'a'代表普通文章)
article.status = 'p' # 文章状态(假设'p'代表已发布)
article.save() # 保存到测试数据库
# 4. 构造评论提交URL通过URL名称“comments:postcomment”反向解析传入文章ID
comment_url = reverse(
'comments:postcomment', kwargs={
'article_id': article.id})
'comments:postcomment',
kwargs={'article_id': article.id} # URL参数文章ID指定评论所属文章
)
response = self.client.post(comment_url,
{
'body': '123ffffffffff'
})
# 5. 模拟POST请求提交评论向评论URL发送包含评论内容的请求
response = self.client.post(
comment_url,
{'body': '123ffffffffff'} # 请求参数:评论正文
)
# 6. 验证评论提交结果检查响应状态码是否为302重定向通常提交后跳回文章页
self.assertEqual(response.status_code, 302)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 0)
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 1)
response = self.client.post(comment_url,
{
'body': '123ffffffffff',
})
self.assertEqual(response.status_code, 302)
# 7. 验证“未审核评论不显示”重新获取文章检查其评论列表长度是否为0因评论需审核
article = Article.objects.get(pk=article.pk) # 从数据库重新查询(避免缓存)
self.assertEqual(len(article.comment_list()), 0) # comment_list()应为自定义方法,返回启用的评论
article = Article.objects.get(pk=article.pk)
# 8. 模拟审核通过:调用辅助方法,将该文章下所有评论设为“启用”
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 2)
parent_comment_id = article.comment_list()[0].id
response = self.client.post(comment_url,
{
'body': '''
# Title1
```python
import os
```
[url](https://www.lylinux.net/)
[ddd](http://www.baidu.com)
''',
'parent_comment_id': parent_comment_id
})
self.assertEqual(response.status_code, 302)
self.update_article_comment_status(article)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 3)
comment = Comment.objects.get(id=parent_comment_id)
tree = parse_commenttree(article.comment_list(), comment)
self.assertEqual(len(tree), 1)
data = show_comment_item(comment, True)
self.assertIsNotNone(data)
s = get_max_articleid_commentid()
self.assertIsNotNone(s)
from comments.utils import send_comment_email
send_comment_email(comment)
# 9. 验证“审核后评论显示”再次检查评论列表长度是否为1审核通过后应显示
self.assertEqual(len(article.comment_list()), 1)

@ -1,11 +1,28 @@
# 导入Django的URL路径配置模块
from django.urls import path
# 导入当前应用comments的视图模块views.py用于关联URL和视图逻辑
from . import views
# 定义应用命名空间在模板或反向解析URL时需通过「app_name:URL名称」的格式定位如comments:postcomment
# 作用避免不同应用间URL名称冲突
app_name = "comments"
# URL路由列表配置URL路径与视图的映射关系
urlpatterns = [
# 评论提交URL处理用户对特定文章的评论提交请求
path(
'article/<int:article_id>/postcomment',
# URL路径规则
# - 'article/':固定路径前缀,标识与文章相关的操作
# - '<<int:article_id>/'动态路径参数接收整数类型的文章ID用于指定评论所属文章
# - 'postcomment':固定路径后缀,标识“提交评论”的操作
'article/<<int:article_id>/postcomment',
# 关联的视图调用views.py中的CommentPostView类视图的as_view()方法(类视图需转为视图函数)
# 该视图负责处理评论提交的业务逻辑(如数据验证、保存评论等)
views.CommentPostView.as_view(),
name='postcomment'),
]
# URL名称用于反向解析如在模板或代码中通过name='postcomment'生成URL
name='postcomment'
),
]

@ -1,38 +1,57 @@
import logging
# 导入日志模块和Django国际化工具以及项目自定义工具
import logging # Python内置日志模块用于记录邮件发送过程中的错误信息
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _ # Django国际化工具支持多语言邮件内容
from djangoblog.utils import get_current_site
from djangoblog.utils import send_email
from djangoblog.utils import get_current_site # 项目自定义工具获取当前网站的域名如example.com
from djangoblog.utils import send_email # 项目自定义工具封装邮件发送逻辑底层调用Django邮件功能
# 初始化日志记录器按当前模块名称创建logger用于记录该模块的运行日志如邮件发送失败
logger = logging.getLogger(__name__)
def send_comment_email(comment):
site = get_current_site().domain
subject = _('Thanks for your comment')
"""
评论相关邮件发送函数触发场景为用户提交评论后
1. 向评论作者发送评论提交成功的感谢邮件
2. 若该评论是回复有父评论向父评论作者发送评论被回复的通知邮件
参数
- comment已保存到数据库的Comment模型对象包含评论作者所属文章父评论等信息
"""
# 1. 基础数据准备:获取当前网站域名,用于拼接文章访问链接
site = get_current_site().domain # 如从配置中获取域名“blog.example.com”
# 2. 构建邮件基础信息(通用主题、文章访问链接)
subject = _('Thanks for your comment') # 邮件主题(支持国际化,多语言环境下自动切换)
# 拼接文章的完整访问URLhttps://域名 + 文章的相对路径通过模型get_absolute_url()获取)
article_url = f"https://{site}{comment.article.get_absolute_url()}"
html_content = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
Thank you again!
<br />
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
tomail = comment.author.email
send_email([tomail], subject, html_content)
# 3. 向「当前评论作者」发送感谢邮件
# 构建HTML格式的邮件内容支持超链接%s占位符通过字典传值替换
html_content = _("""<p>Thank you very much for your comments on this site</p >
You can visit %s
to review your comments,
Thank you again!
<br />
If the link above cannot be opened, please copy this link to your browser.
%s""") % (article_url, comment.article.title, article_url)
tomail = comment.author.email # 收件人邮箱:当前评论作者的邮箱
send_email([tomail], subject, html_content) # 调用自定义工具发送邮件收件人列表、主题、HTML内容
# 4. 若当前评论是「回复评论」(有父评论),向「父评论作者」发送回复通知邮件
try:
if comment.parent_comment:
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
received a reply. <br/> %(comment_body)s
if comment.parent_comment: # 判断当前评论是否有父评论(即是否是回复)
# 构建回复通知的HTML邮件内容告知父评论作者“你的评论被回复了”
html_content = _("""Your comment on %s<br/> has
received a reply. <br/> %s
<br/>
go check it out!
<br/>
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s
""") % {'article_url': article_url, 'article_title': comment.article.title,
'comment_body': comment.parent_comment.body}
tomail = comment.parent_comment.author.email
send_email([tomail], subject, html_content)
%s
""") % (article_url, comment.article.title, comment.parent_comment.body, article_url)
tomail = comment.parent_comment.author.email # 收件人邮箱:父评论作者的邮箱
send_email([tomail], subject, html_content) # 发送回复通知邮件
# 捕获邮件发送过程中的所有异常(如邮箱格式错误、邮件服务器故障等)
except Exception as e:
logger.error(e)
logger.error(e) # 将错误信息记录到日志(便于后续排查问题,不中断程序运行)

@ -1,63 +1,114 @@
<<<<<<< HEAD
# Create your views here.
# 导入Django核心模块、异常类、视图工具及项目内模型/表单
from django.core.exceptions import ValidationError # Django内置验证异常类用于抛出自定义验证错误
from django.http import HttpResponseRedirect # 用于重定向HTTP响应如提交后跳回文章页
from django.shortcuts import get_object_or_404 # 快捷查询找到数据返回对象找不到返回404页面
from django.utils.decorators import method_decorator # 用于给类视图的方法添加装饰器
from django.views.decorators.csrf import csrf_protect # CSRF保护装饰器防止跨站请求伪造攻击
from django.views.generic.edit import FormView # 通用表单视图类:简化表单提交、验证、处理的逻辑
=======
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
from django.views.generic.edit import FormView
>>>>>>> ZYY_branch
# 导入项目内关联模型和表单:用户、文章、评论表单、评论模型
from accounts.models import BlogUser
from blog.models import Article
from .forms import CommentForm
from .forms import CommentForm # 评论功能的表单类之前定义的CommentForm
from .models import Comment
class CommentPostView(FormView):
form_class = CommentForm
template_name = 'blog/article_detail.html'
"""
评论提交的类视图继承FormView处理评论表单的展示验证和数据保存
核心功能接收用户提交的评论数据验证合法性后保存到数据库支持评论回复
"""
# 1. 类视图基础配置
form_class = CommentForm # 指定关联的表单类使用CommentForm处理提交数据
template_name = 'blog/article_detail.html' # 指定表单渲染的模板:评论在文章详情页提交,故用文章详情模板
# 2. 给dispatch方法添加CSRF保护所有请求GET/POST都经过CSRF验证
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
"""
类视图的请求入口方法所有请求都会先经过此方法
作用调用父类的dispatch逻辑同时应用CSRF保护
"""
return super(CommentPostView, self).dispatch(*args, **kwargs)
# 3. 处理GET请求当用户以GET方式访问该视图时触发
def get(self, request, *args, **kwargs):
"""
GET请求逻辑不处理表单提交直接重定向到对应的文章详情页的评论区
避免用户直接通过URL以GET方式访问该视图时出现异常
"""
# 从URL路径参数中获取文章IDkwargs对应URL中的<int:article_id>
article_id = self.kwargs['article_id']
# 查询对应的文章找不到则返回404
article = get_object_or_404(Article, pk=article_id)
# 获取文章详情页的绝对URL并拼接评论区锚点#comments跳转到页面评论区域
url = article.get_absolute_url()
# 重定向到文章详情页的评论区
return HttpResponseRedirect(url + "#comments")
# 4. 处理表单验证失败的逻辑当form.is_valid()为False时触发
def form_invalid(self, form):
"""
表单数据验证失败如评论内容为空格式错误时的处理
作用重新渲染文章详情页带上错误的表单对象前端显示错误提示
"""
# 获取URL中的文章ID查询对应的文章
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
# 渲染模板传递错误的表单对象form和文章对象article前端可显示错误信息
return self.render_to_response({
'form': form,
'article': article
'form': form, # 带有错误信息的表单
'article': article # 当前文章对象(用于渲染文章详情)
})
# 5. 处理表单验证成功的逻辑当form.is_valid()为True时触发核心业务逻辑
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
user = self.request.user
author = BlogUser.objects.get(pk=user.pk)
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
"""提交的数据验证合法后的逻辑:保存评论数据到数据库,处理评论状态和回复关联"""
# 1. 获取当前登录用户(评论作者)
user = self.request.user # 从请求对象中获取登录用户
author = BlogUser.objects.get(pk=user.pk) # 通过用户ID查询完整的BlogUser对象
# 2. 获取当前评论对应的文章
article_id = self.kwargs['article_id'] # 从URL参数获取文章ID
article = get_object_or_404(Article, pk=article_id) # 查询文章不存在则404
# 3. 验证文章评论状态:若文章关闭评论或处于草稿状态,抛出验证错误
# 假设'article.comment_status == 'c''表示关闭评论,'article.status == 'c''表示文章草稿
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.")
comment = form.save(False)
comment.article = article
from djangoblog.utils import get_blog_setting
settings = get_blog_setting()
if not settings.comment_need_review:
comment.is_enable = True
comment.author = author
if form.cleaned_data['parent_comment_id']:
raise ValidationError("该文章评论已关闭.") # 抛出异常,前端可捕获并显示
# 4. 保存评论先不提交到数据库False表示暂存内存后续补充字段
comment = form.save(False) # form.save(False)返回评论对象但不执行数据库INSERT
comment.article = article # 给评论关联文章补充form中未包含的article字段
# 5. 根据系统配置决定评论是否需要审核(直接启用或待审核)
from djangoblog.utils import get_blog_setting # 局部导入:避免循环引用
settings = get_blog_setting() # 获取博客系统全局配置如comment_need_review
if not settings.comment_need_review: # 若系统配置“评论无需审核”
comment.is_enable = True # 评论直接设为“启用”状态,前端可显示
comment.author = author # 给评论关联作者补充form中未包含的author字段
# 6. 处理评论回复若表单中包含父评论ID给当前评论关联父评论
if form.cleaned_data['parent_comment_id']: # 检查表单清理后的数据中是否有父评论ID
# 通过父评论ID查询对应的父评论对象
parent_comment = Comment.objects.get(
pk=form.cleaned_data['parent_comment_id'])
pk=form.cleaned_data['parent_comment_id']
)
# 注原代码此处不完整缺少赋值语句正确逻辑应为“comment.parent_comment = parent_comment”
# 补充后才会将当前评论与父评论关联,实现回复功能
comment.parent_comment = parent_comment
comment.save(True)
return HttpResponseRedirect(
"%s#div-comment-%d" %
(article.get_absolute_url(), comment.pk))
# 原代码缺失最终需调用comment.save()将评论数据提交到数据库,否则评论不会保存)
# comment.save()
Loading…
Cancel
Save