赵瑞萍代码注释 #19

Merged
pwe53ixz6 merged 1 commits from zrp_branch into develop 3 months ago

@ -1,64 +1,92 @@
# 赵瑞萍评论模型后台管理配置模块用于自定义Django Admin评论管理界面
# 功能:配置评论列表展示、筛选、批量操作及自定义字段,支持国际化和快速跳转关联数据
# 核心特性:批量启用/禁用评论、作者/文章快速跳转、列表字段自定义显示
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html # 用于安全渲染HTML内容
from django.utils.translation import gettext_lazy as _ # 用于国际化翻译
from django.urls import reverse # 用于反向生成Admin页面URL
from django.utils.html import format_html # 用于安全渲染HTML链接避免XSS风险
from django.utils.translation import gettext_lazy as _ # 用于后台字段名称国际化翻译
# 自定义批量操作:禁用选中的评论
# 赵瑞萍:自定义批量操作函数——禁用选中的评论
def disable_commentstatus(modeladmin, request, queryset):
# 将选中评论的is_enable字段批量更新为False
# 赵瑞萍:将选中评论的is_enable字段批量更新为False,实现批量隐藏评论
queryset.update(is_enable=False)
# 自定义批量操作:启用选中的评论
# 赵瑞萍:自定义批量操作函数——启用选中的评论
def enable_commentstatus(modeladmin, request, queryset):
# 将选中评论的is_enable字段批量更新为True
# 赵瑞萍:将选中评论的is_enable字段批量更新为True,实现批量显示评论
queryset.update(is_enable=True)
# 为批量操作设置显示名称(支持国际化)
# 赵瑞萍:为批量操作设置后台显示名称,支持国际化翻译
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
class CommentAdmin(admin.ModelAdmin):
"""评论模型的Admin配置类控制后台评论管理界面的展示和功能"""
"""
赵瑞萍评论模型的Admin配置类控制Django后台评论管理界面的各项功能
包括列表展示字段分页筛选编辑页字段批量操作等配置
"""
# 赵瑞萍列表页分页配置每页显示20条评论避免数据过多导致加载缓慢
list_per_page = 20
list_per_page = 20 # 每页显示20条评论
# 赵瑞萍:列表页显示的字段,包含基础字段和自定义跳转字段
list_display = (
'id', # 评论ID
'body', # 评论内容
'link_to_userinfo', # 自定义字段:评论作者的链接
'link_to_article', # 自定义字段:评论所属文章的链接
'is_enable', # 是否启用
'creation_time' # 创建时间
'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] # 注册批量操作
# 赵瑞萍:列表页中可点击跳转至编辑页的字段,方便快速编辑
list_display_links = ('id', 'body', 'is_enable')
# 赵瑞萍右侧筛选器配置按评论显示状态is_enable筛选快速筛选启用/禁用评论
list_filter = ('is_enable',)
# 赵瑞萍:编辑页排除的字段,创建时间和最后修改时间不允许手动修改,由系统自动维护
exclude = ('creation_time', 'last_modify_time')
# 赵瑞萍:注册批量操作函数,在列表页提供"禁用评论"和"启用评论"的批量操作按钮
actions = [disable_commentstatus, enable_commentstatus]
def link_to_userinfo(self, obj):
"""自定义列表字段:生成评论作者的后台编辑页链接"""
# 获取作者模型的app标签和模型名称用于反向生成URL
"""
赵瑞萍自定义列表字段生成评论作者的后台编辑页链接
实现从评论直接跳转至作者详情页方便关联数据管理
参数obj当前评论对象
返回安全渲染的HTML链接标签
"""
# 赵瑞萍获取作者模型的app标签和模型名称用于反向生成URL
info = (obj.author._meta.app_label, obj.author._meta.model_name)
# 反向生成作者模型的编辑页URL
# 赵瑞萍:反向生成作者模型的后台编辑页URL传入作者ID作为参数
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
# 渲染为HTML链接优先显示昵称无昵称则显示邮箱
# 赵瑞萍渲染HTML链接优先显示作者昵称无昵称则显示邮箱确保显示有意义的信息
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email)
)
def link_to_article(self, obj):
"""自定义列表字段:生成评论所属文章的后台编辑页链接"""
# 获取文章模型的app标签和模型名称
"""
赵瑞萍自定义列表字段生成评论所属文章的后台编辑页链接
实现从评论直接跳转至文章详情页方便关联数据核查
参数obj当前评论对象
返回安全渲染的HTML链接标签
"""
# 赵瑞萍获取文章模型的app标签和模型名称用于反向生成URL
info = (obj.article._meta.app_label, obj.article._meta.model_name)
# 反向生成文章模型的编辑页URL
# 赵瑞萍:反向生成文章模型的后台编辑页URL传入文章ID作为参数
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
# 渲染为HTML链接显示文章标题
# 赵瑞萍渲染HTML链接显示文章标题直观标识关联文章
return format_html(u'<a href="%s">%s</a>' % (link, obj.article.title))
# 为自定义字段设置显示名称(支持国际化)
# 赵瑞萍:为自定义字段设置后台显示名称,支持国际化翻译,适配多语言环境
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')

@ -1,13 +1,18 @@
# 赵瑞萍comments应用配置模块用于定义评论应用的元数据和初始化设置
# 该类是Django应用的核心配置类项目启动时自动加载用于识别和管理comments应用
from django.apps import AppConfig
class CommentsConfig(AppConfig):
"""
Django应用配置类用于定义"comments"应用的元数据和配置
Django中每个应用都需要通过继承AppConfig类来声明应用信息
该类会在项目启动时被Django自动加载用于初始化应用相关设置
赵瑞萍comments应用的配置类继承自Django的AppConfig
用于声明应用的基本信息控制应用的初始化行为是Django识别应用的关键
"""
# 应用的唯一标识名称必须与应用目录名一致用于Django识别和管理该应用
# 在settings.py的INSTALLED_APPS中注册时通常使用这个名称如'comments'
name = 'comments'
# 赵瑞萍:应用的唯一标识名称,必须与应用目录名完全一致
# 用于在settings.py的INSTALLED_APPS中注册应用Django通过该名称定位应用
name = 'comments'
# 赵瑞萍:可选配置,指定应用的 verbose 名称,用于后台管理界面显示
# 若不设置默认显示为name的值'comments'
verbose_name = '评论管理'

@ -1,21 +1,24 @@
# 导入Django表单相关模块
from django import forms
from django.forms import ModelForm # 导入模型表单类,用于快速生成与模型对应的表单
# 赵瑞萍:评论表单模块,用于构建用户提交评论的表单,支持顶级评论和嵌套回复功能
# 基于Comment模型快速生成表单控制前端输入字段适配评论提交的数据收集需求
# 导入当前应用下的Comment模型表单将基于该模型创建
from .models import Comment
# 赵瑞萍导入Django表单核心模块提供表单基础构建能力
from django import forms
from django.forms import ModelForm # 导入模型表单类,实现表单与数据模型的快速绑定
from .models import Comment # 导入当前应用的Comment模型作为表单的数据源
# 定义评论表单类继承自ModelForm模型表单
class CommentForm(ModelForm):
# 定义父评论ID字段用于实现评论回复功能
# IntegerField整数类型字段存储父评论的ID
# widget=forms.HiddenInput使用隐藏输入框不在页面上显示但会随表单提交
# required=False该字段为非必填顶级评论无父评论不需要填写
"""
赵瑞萍评论提交表单类继承ModelForm实现与Comment模型的关联
扩展父评论ID字段以支持回复功能仅暴露核心输入项简化用户操作
"""
# 赵瑞萍定义父评论ID字段用于识别当前评论的回复目标实现嵌套回复
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
widget=forms.HiddenInput, # 使用隐藏输入组件,不在前端页面展示,仅用于后端传递数据
required=False # 设为非必填,顶级评论(直接评论文章)无需传入该字段
)
# Meta类用于配置模型表单的元数据
# 赵瑞萍:表单元数据配置类,定义模型关联、字段筛选等关键配置
class Meta:
model = Comment # 指定表单对应的模型为Comment
fields = ['body'] # 指定需要在表单中显示的模型字段,这里只包含评论内容字段'body'
model = Comment # 绑定Comment模型表单数据将直接映射到模型对应的字段
fields = ['body'] # 仅指定评论正文字段为前端输入项,其他字段(如作者、时间)由后端处理

@ -1,88 +1,89 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 以上注释为Django自动生成显示生成该迁移文件的Django版本和时间
# 赵瑞萍评论模型迁移模块用于定义Comment模型在数据库中的初始结构
# 该模块通过Django迁移系统创建评论表支持用户对文章的评论及嵌套回复功能
# 生成于Django 4.1.7版本,包含字段定义、关联关系及数据库操作规则
# 导入Django必要的模块
from django.conf import settings # 用于获取项目设置,特别是用户模型配置
from django.db import migrations, models # 数据库迁移和模型字段相关模块
import django.db.models.deletion # 用于定义外键删除行为
import django.utils.timezone # 用于处理时间相关字段
# 赵瑞萍导入Django迁移所需核心模块
from django.conf import settings # 用于获取项目配置的用户模型
from django.db import migrations, models # 提供数据库迁移操作和字段类型
import django.db.models.deletion # 用于定义外键删除时的关联行为
import django.utils.timezone # 提供带时区的时间处理工具
class Migration(migrations.Migration):
"""
迁移类定义数据库结构变更的具体操作
每个迁移文件对应一个Migration类
赵瑞萍迁移类封装评论模型的数据库结构创建逻辑
负责定义迁移依赖关系和具体的表创建操作确保数据库结构与模型一致
"""
# 标记为初始迁移(第一次创建该模型的迁移)
# 赵瑞萍:标记为初始迁移,即首次创建该模型对应的数据库表
initial = True
# 依赖的其他迁移文件,确保执行顺序正确
# 赵瑞萍:迁移依赖配置,确保执行顺序正确
dependencies = [
('blog', '0001_initial'), # 依赖blog应用的0001_initial迁移确保Article模型已存在
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型迁移
('blog', '0001_initial'), # 依赖blog应用的初始迁移确保Article表已存在
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型迁移,确保用户表存在
]
# 具体的数据库操作列表
# 赵瑞萍数据库操作列表此处为创建Comment表的核心操作
operations = [
# 创建Comment模型对应数据库表
# 赵瑞萍:创建Comment模型对应数据库表
migrations.CreateModel(
name='Comment', # 模型名称
name='Comment', # 模型名称对应数据库表名为comments_comment
fields=[
# 主键字段,自动增长的大整数类型
# 赵瑞萍:主键字段,自动增长的大整数类型
('id', models.BigAutoField(
auto_created=True, # 自动创建
primary_key=True, # 设为主键
serialize=False, # 序列化
verbose_name='ID' # 后台显示名称
auto_created=True, # 自动生成主键值,无需手动赋值
primary_key=True, # 设为主键,唯一标识每条评论记录
serialize=False, # 序列化模型时不包含该字段
verbose_name='ID' # 后台管理界面显示的字段名称
)),
# 评论正文字段
# 赵瑞萍:评论正文字段,存储用户输入的评论内容
('body', models.TextField(
max_length=300, # 最大长度限制
max_length=300, # 限制最大长度为300字符控制评论长度
verbose_name='正文' # 后台显示名称
)),
# 评论创建时间字段
# 赵瑞萍:评论创建时间字段,记录评论发布的时间
('created_time', models.DateTimeField(
default=django.utils.timezone.now, # 默认值为当前时
default=django.utils.timezone.now, # 默认值为当前时区时
verbose_name='创建时间' # 后台显示名称
)),
# 评论最后修改时间字段
# 赵瑞萍:评论修改时间字段,记录评论最后更新的时间
('last_mod_time', models.DateTimeField(
default=django.utils.timezone.now, # 默认值为当前时间
default=django.utils.timezone.now, # 默认值为当前时间,更新时需手动刷新
verbose_name='修改时间' # 后台显示名称
)),
# 评论是否显示状态字段
# 赵瑞萍:评论显示状态字段,控制评论是否在前端展示
('is_enable', models.BooleanField(
default=True, # 默认显示
default=True, # 默认值为True新评论默认可见
verbose_name='是否显示' # 后台显示名称
)),
# 外键关联文章模型
# 赵瑞萍:外键字段,关联文章模型,建立评论与所属文章的关系
('article', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, # 文章删除时,关联评论也删除
to='blog.article', # 关联blog应用的Article模型
on_delete=django.db.models.deletion.CASCADE, # 级联删除:文章删除时,关联评论同步删除
to='blog.article', # 关联目标为blog应用的Article模型
verbose_name='文章' # 后台显示名称
)),
# 外键关联用户模型
# 赵瑞萍:外键字段,关联用户模型,记录评论的发布者
('author', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, # 用户删除时,关联评论也删除
to=settings.AUTH_USER_MODEL, # 关联项目配置的用户模型
on_delete=django.db.models.deletion.CASCADE, # 级联删除:用户删除时,其发布的评论同步删除
to=settings.AUTH_USER_MODEL, # 关联目标为项目配置的用户模型
verbose_name='作者' # 后台显示名称
)),
# 外键关联到自身,实现评论嵌套回复功能
# 赵瑞萍:外键关联,实现评论嵌套回复功能
('parent_comment', models.ForeignKey(
blank=True, # 允许为空
null=True, # 数据库允许为NULL
on_delete=django.db.models.deletion.CASCADE, # 上级评论删除时,子评论也删除
to='comments.comment', # 关联当前应用的Comment模型
blank=True, # 表单提交时允许为空(一级评论无需上级评论)
null=True, # 数据库字段允许为NULL
on_delete=django.db.models.deletion.CASCADE, # 级联删除:上级评论删除时,回复评论同步删除
to='comments.comment', # 关联目标为当前应用的Comment模型
verbose_name='上级评论' # 后台显示名称
)),
],
# 模型元数据配置
# 赵瑞萍:模型元数据配置,定义表的显示和行为规则
options={
'verbose_name': '评论', # 模型的单数显示名称
'verbose_name_plural': '评论', # 模型的复数显示名称(中文单复数相同
'ordering': ['-id'], # 默认排序方式按id倒序新评论在前
'get_latest_by': 'id', # 使用latest()方法时按id字段判断最新
'verbose_name_plural': '评论', # 模型的复数显示名称(中文无复数变化
'ordering': ['-id'], # 默认排序规则按id降序最新评论优先显示
'get_latest_by': 'id', # 调用latest()方法时以id字段作为判断最新的依据
},
),
]

@ -1,33 +1,33 @@
# Generated by Django 4.1.7 on 2023-04-24 13:48
# 以上为Django自动生成的注释表明该迁移文件由Django 4.1.7版本在2023-04-24生成
# 赵瑞萍评论模型修改迁移模块用于调整Comment模型中is_enable字段的默认属性
# 该模块通过Django迁移系统更新评论表结构将评论默认显示状态从"显示"改为"隐藏"
# 生成于Django 4.1.7版本,专注于单字段属性的变更操作
# 导入Django数据库迁移相关模块
from django.db import migrations, models
# 赵瑞萍导入Django数据库迁移核心模块
from django.db import migrations, models # 提供迁移操作类和模型字段类型
class Migration(migrations.Migration):
"""
迁移类用于修改评论模型的字段属性
这是一个修改性迁移而非初始创建迁移
赵瑞萍迁移类封装评论模型字段的修改逻辑
负责将Comment模型的is_enable字段默认值从True改为False实现评论默认不显示的功能
"""
# 依赖关系必须在comments应用的0001_initial迁移之后执行
# 确保先有Comment模型才能对其进行修改
# 赵瑞萍:迁移依赖配置,确保执行顺序正确
dependencies = [
('comments', '0001_initial'),
('comments', '0001_initial'), # 依赖comments应用的初始迁移确保Comment表已存在
]
# 具体的数据库操作
# 赵瑞萍数据库操作列表此处为修改is_enable字段属性的核心操作
operations = [
# 修改Comment模型的is_enable字段属性
# 赵瑞萍修改Comment模型的is_enable字段配置
migrations.AlterField(
model_name='comment', # 要修改的模型名称
name='is_enable', # 要修改的字段名称
model_name='comment', # 目标模型comments应用的Comment模型
name='is_enable', # 目标字段控制评论显示状态的is_enable字段
# 修改后的字段定义
# 赵瑞萍:修改后的字段定义,仅变更默认值
field=models.BooleanField(
default=False, # 关键变更默认值从True改为False
verbose_name='是否显示' # 保持字段的后台显示名称不变
default=False, # 关键变更默认值从True改为False,新评论默认不显示
verbose_name='是否显示' # 保持后台显示名称不变
),
),
]

@ -1,93 +1,115 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
# Django自动生成的注释显示生成该迁移文件的Django版本和时间
# 赵瑞萍Comment模型优化迁移模块用于对评论模型进行多维度结构调整
# 核心变更包括字段名规范化、verbose_name国际化中文转英文、元数据更新适配项目国际化需求
# 生成于Django 4.2.5版本,基于之前的迁移版本迭代修改
# 导入必要的模块
from django.conf import settings # 用于获取用户模型配置
from django.db import migrations, models # 数据库迁移和模型字段相关
import django.db.models.deletion # 外键删除行为定义
import django.utils.timezone # 时间处理工具
# 赵瑞萍导入Django迁移所需核心模块
from django.conf import settings # 用于获取项目配置的用户模型
from django.db import migrations, models # 提供数据库迁移操作和字段类型定义
import django.db.models.deletion # 用于配置外键删除时的关联行为
import django.utils.timezone # 提供带时区支持的时间处理工具
class Migration(migrations.Migration):
"""
这是一个对Comment模型进行多方面修改的迁移类
主要涉及字段重命名 verbose_name 国际化调整等
赵瑞萍多字段修改迁移类封装Comment模型的结构优化逻辑
主要实现字段名标准化字段备注国际化同时保持模型核心功能不变
"""
# 依赖关系:指定了执行此迁移前需要先完成的迁移
# 赵瑞萍:迁移依赖配置,确保执行顺序符合依赖关系
dependencies = [
# 依赖用户模型的迁移
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
# 依赖blog应用的特定迁移版本
('blog', '0005_alter_article_options_alter_category_options_and_more'),
# 依赖comments应用的上一个迁移版本
('comments', '0002_alter_comment_is_enable'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型迁移,确保用户表存在
('blog', '0005_alter_article_options_alter_category_options_and_more'), # 依赖blog应用的指定迁移版本
('comments', '0002_alter_comment_is_enable'), # 依赖comments应用的上一个迁移版本确保基础结构存在
]
# 具体的数据库操作列表
# 赵瑞萍:数据库操作列表,包含字段增删改、元数据调整等操作
operations = [
# 修改Comment模型的元数据选项
# 赵瑞萍修改Comment模型的元数据配置适配国际化显示
migrations.AlterModelOptions(
name='comment', # 目标模型
# 将verbose_name从中文"评论"改为英文"comment",实现国际化调整
options={'get_latest_by': 'id', 'ordering': ['-id'],
'verbose_name': 'comment', 'verbose_name_plural': 'comment'},
name='comment', # 目标模型comments应用的Comment模型
# 关键变更verbose_name从中文"评论"改为英文"comment",保持排序和最新记录判断规则不变
options={
'get_latest_by': 'id', # 仍以id字段判断最新评论
'ordering': ['-id'], # 仍按id降序排序新评论在前
'verbose_name': 'comment', # 单数显示名称(国际化调整)
'verbose_name_plural': 'comment' # 复数显示名称(国际化调整)
},
),
# 删除原有的创建时间字段
# 赵瑞萍:删除原有创建时间字段,后续将替换为命名更规范的新字段
migrations.RemoveField(
model_name='comment',
name='created_time',
),
# 删除原有的最后修改时间字段
# 赵瑞萍:删除原有修改时间字段,后续替换为命名更规范的新字段
migrations.RemoveField(
model_name='comment',
name='last_mod_time',
),
# 添加新的创建时间字段(字段名变更)
# 赵瑞萍:添加新的创建时间字段,字段名规范化并更新备注
migrations.AddField(
model_name='comment',
name='creation_time', # 新字段名从created_time改为creation_time
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
name='creation_time', # 新字段名从created_time改为creation_time命名更规范
field=models.DateTimeField(
default=django.utils.timezone.now, # 默认值仍为当前时区时间
verbose_name='creation time' # 备注改为英文(国际化调整)
),
),
# 添加新的最后修改时间字段(字段名变更)
# 赵瑞萍:添加新的修改时间字段,字段名规范化并更新备注
migrations.AddField(
model_name='comment',
name='last_modify_time', # 新字段名从last_mod_time改为last_modify_time
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
name='last_modify_time', # 新字段名从last_mod_time改为last_modify_time命名更规范
field=models.DateTimeField(
default=django.utils.timezone.now, # 默认值仍为当前时区时间
verbose_name='last modify time' # 备注改为英文(国际化调整)
),
),
# 修改article外键字段的verbose_name从中文改为英文
# 赵瑞萍修改article外键字段的备注信息国际化调整
migrations.AlterField(
model_name='comment',
name='article',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article',
verbose_name='article'),
name='article', # 目标字段:关联文章的外键
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, # 级联删除规则保持不变
to='blog.article', # 关联目标保持不变blog应用的Article模型
verbose_name='article' # 备注从中文"文章"改为英文(国际化调整)
),
),
# 修改author外键字段的verbose_name从中文改为英文
# 赵瑞萍修改author外键字段的备注信息国际化调整
migrations.AlterField(
model_name='comment',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,
verbose_name='author'),
name='author', # 目标字段:关联用户的外键
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, # 级联删除规则保持不变
to=settings.AUTH_USER_MODEL, # 关联目标保持不变(项目配置的用户模型)
verbose_name='author' # 备注从中文"作者"改为英文(国际化调整)
),
),
# 修改is_enable字段的verbose_name从"是否显示"改为"enable"
# 赵瑞萍修改is_enable字段的备注信息国际化调整
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='enable'),
name='is_enable', # 目标字段:控制评论显示状态的字段
field=models.BooleanField(
default=False, # 默认值保持不变(新评论默认不显示)
verbose_name='enable' # 备注从中文"是否显示"改为英文(国际化调整)
),
),
# 修改parent_comment外键字段的verbose_name从中文改为英文
# 赵瑞萍修改parent_comment外键字段的备注信息国际化调整
migrations.AlterField(
model_name='comment',
name='parent_comment',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='comments.comment', verbose_name='parent comment'),
name='parent_comment', # 目标字段:实现嵌套回复的自关联外键
field=models.ForeignKey(
blank=True, # 允许表单为空(一级评论无需上级评论)
null=True, # 数据库字段允许为NULL
on_delete=django.db.models.deletion.CASCADE, # 级联删除规则保持不变
to='comments.comment', # 关联目标保持不变当前应用的Comment模型
verbose_name='parent comment' # 备注从中文"上级评论"改为英文(国际化调整)
),
),
]

@ -1,60 +1,76 @@
# 导入Django配置、数据库模型、时间工具和翻译工具
from django.conf import settings
from django.db import models
from django.utils.timezone import now # 获取当前时区时间的工具
from django.utils.translation import gettext_lazy as _ # 国际化翻译工具,用于多语言支持
# 赵瑞萍:评论模型模块,定义评论数据结构及关联关系
# 功能:存储用户对文章的评论信息,支持嵌套回复,包含评论内容、时间、作者、关联文章等核心数据
# 特性:支持国际化显示、级联删除关联数据、默认排序规则及评论状态管理
# 导入博客文章模型,评论将与文章关联
from blog.models import Article
# 赵瑞萍导入Django核心模块提供配置、数据库操作、时间处理和国际化支持
from django.conf import settings # 获取项目配置(如用户模型)
from django.db import models # 数据库模型基类及字段类型
from django.utils.timezone import now # 获取带时区的当前时间,用于时间字段默认值
from django.utils.translation import gettext_lazy as _ # 国际化翻译工具,实现多语言显示
# 赵瑞萍:导入关联模型,评论需与博客文章建立关联关系
from blog.models import Article # 博客文章模型,评论将关联到具体文章
# 创建评论模型(数据库表结构的抽象)
class Comment(models.Model):
# 评论正文文本字段最大长度300 verbose_name为'正文'(后台显示名称)
"""
赵瑞萍评论模型类映射数据库中存储评论的表结构
记录评论的内容时间作者关联文章父评论及显示状态等信息
支持评论嵌套回复和国际化管理
"""
# 赵瑞萍评论正文字段限制最大300字符避免内容过长
body = models.TextField('正文', max_length=300)
# 创建时间:日期时间字段,使用当前时区时间作为默认值,国际化翻译字段名为'creation time'
# 赵瑞萍:评论创建时间字段,默认值为当前时区时间,支持国际化显示
creation_time = models.DateTimeField(_('creation time'), default=now)
# 最后修改时间:日期时间字段,默认值为当前时间(后续可通过代码更新)
# 赵瑞萍:评论最后修改时间字段,默认值为当前时间,可通过代码更新为修改时的时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 评论作者外键关联到Django内置用户模型settings.AUTH_USER_MODEL
# on_delete=models.CASCADE表示如果用户被删除其评论也会被级联删除
# 赵瑞萍:评论作者外键,关联项目配置的用户模型
# 级联删除:当用户账号被删除时,其发布的所有评论也会被删除
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
on_delete=models.CASCADE)
verbose_name=_('author'), # 国际化字段名:作者
on_delete=models.CASCADE
)
# 关联文章外键关联到博客文章模型Article
# on_delete=models.CASCADE表示如果文章被删除其下所有评论也会被级联删除
# 赵瑞萍:关联文章外键,评论属于某篇具体文章
# 级联删除:当文章被删除时,其下所有评论也会被删除
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE)
verbose_name=_('article'), # 国际化字段名:文章
on_delete=models.CASCADE
)
# 父评论自关联外键,用于实现评论回复功能
# 'self'表示关联到当前模型自身blank=True和null=True允许为空即顶级评论
# 父评论被删除时,子评论也会被级联删除
# 赵瑞萍:父评论自关联外键,实现评论嵌套回复功能
# 允许为空:顶级评论(直接评论文章)无父评论
# 级联删除:当父评论被删除时,其所有回复评论也会被删除
parent_comment = models.ForeignKey(
'self',
verbose_name=_('parent comment'),
blank=True,
null=True,
on_delete=models.CASCADE)
verbose_name=_('parent comment'), # 国际化字段名:上级评论
blank=True, # 表单提交时允许为空
null=True, # 数据库中允许为NULL
on_delete=models.CASCADE
)
# 是否启用:布尔字段,默认不启用(可能用于评论审核功能)
# blank=False和null=False表示该字段必填且不能为NULL
is_enable = models.BooleanField(_('enable'),
default=False, blank=False, null=False)
# 赵瑞萍:评论显示状态字段,用于评论审核功能
# 默认值为False需审核后显示不允许为空
is_enable = models.BooleanField(
_('enable'), # 国际化字段名:是否启用
default=False,
blank=False,
null=False
)
# 模型元数据配置(影响模型的整体行为和显示)
# 赵瑞萍:模型元数据配置,定义模型的整体行为和显示规则
class Meta:
ordering = ['-id'] # 默认排序按ID降序最新评论在前
verbose_name = _('comment') # 模型的单数显示名称(用于后台
verbose_name_plural = verbose_name # 模型复数显示名称(与单数相同)
get_latest_by = 'id' # 指定通过id字段获取最新记录
ordering = ['-id'] # 默认排序按ID降序,最新评论优先显示
verbose_name = _('comment') # 模型单数显示名称(国际化
verbose_name_plural = verbose_name # 模型复数显示名称(与单数相同)
get_latest_by = 'id' # 使用latest()方法时按ID字段判断最新记录
# 模型的字符串表示:在后台和打印对象时显示评论正文
# 赵瑞萍:定义模型实例的字符串表示形式
# 在后台管理和调试时,直观显示评论内容(取正文作为标识)
def __str__(self):
return self.body

@ -1,43 +1,58 @@
# 赵瑞萍:评论模板标签模块,提供解析评论嵌套结构和渲染评论项的自定义标签
# 功能支持在Django模板中处理评论树的层级关系生成嵌套评论的HTML展示结构
# 主要包含两个标签parse_commenttree解析子评论和show_comment_item渲染评论项
from django import template
# 注册模板标签库用于在Django模板中加载自定义标签
# 赵瑞萍:注册模板标签库,使自定义标签可在模板中通过{% load 模块名 %}加载使用
register = template.Library()
@register.simple_tag
def parse_commenttree(commentlist, comment):
"""获得当前评论子评论的列表
用法: {% parse_commenttree article_comments comment as childcomments %}
"""
# 存储所有子评论的列表(包括嵌套子评论)
赵瑞萍解析评论树的模板标签用于获取当前评论的所有嵌套子评论含多级回复
采用递归方式遍历评论的所有后代仅收集已启用的评论is_enable=True
参数
commentlist包含当前文章所有评论的查询集
comment当前评论对象需获取其下的所有子评论
返回按层级顺序排列的子评论列表
用法示例{% parse_commenttree article_comments comment as childcomments %}
"""
# 赵瑞萍:初始化列表,用于存储递归过程中收集到的所有子评论
datas = []
# 递归函数:用于遍历评论的所有后代评论
# 赵瑞萍:定义内部递归函数,用于深度优先遍历子评论
def parse(c):
# 筛选出当前评论c的直接子评论已启用状态
# commentlist是评论查询集通过parent_comment关联父评论
# 赵瑞萍筛选出当前评论c的直接子评论且状态为已启用
# 条件parent_comment外键指向c且is_enable=True
childs = commentlist.filter(parent_comment=c, is_enable=True)
# 遍历每个直接子评论
# 赵瑞萍:遍历每个直接子评论,进行递归处理
for child in childs:
# 将子评论添加到结果列表
datas.append(child)
# 递归处理子评论的子评论(深度优先遍历)
parse(child)
datas.append(child) # 将子评论添加到结果列表
parse(child) # 递归调用,处理该子评论的下一级回复
# 从当前评论开始递归解析所有子评论
# 赵瑞萍:从当前评论开始递归解析,收集所有嵌套子评论
parse(comment)
# 返回收集到的所有子评论列表
# 赵瑞萍:返回收集到的子评论列表,供模板循环渲染
return datas
@register.inclusion_tag('comments/tags/comment_item.html')
def show_comment_item(comment, ischild):
"""评论渲染标签:用于在模板中展示单个评论项"""
# 根据是否为子评论设置层级深度(可能用于前端样式区分,如缩进)
# 子评论depth=1顶级评论depth=2这里的数值可根据实际样式需求调整
"""
赵瑞萍评论项渲染标签用于将单个评论对象渲染为HTML片段
关联模板文件comments/tags/comment_item.html传递评论数据和样式层级参数
参数
comment需要渲染的评论对象包含作者内容时间等属性
ischild布尔值标识该评论是否为子评论用于区分样式层级
返回传递给模板的上下文变量字典
"""
# 赵瑞萍:根据是否为子评论设置层级深度,用于前端样式区分(如缩进量)
# 子评论depth=1顶级评论depth=2可在模板中通过该值控制CSS样式
depth = 1 if ischild else 2
# 向模板传递变量:评论对象和层级深度
# 赵瑞萍:向模板传递上下文变量,模板中可通过{{ comment_item }}和{{ depth }}访问
return {
'comment_item': comment, # 评论对象,包含作者、内容、时间等信息
'depth': depth # 层级深度,用于前端渲染样式
'comment_item': comment, # 评论对象,提供评论的核心数据
'depth': depth # 层级深度,用于控制评论的显示样式
}

@ -1,104 +1,146 @@
# 导入Django测试相关工具
# 赵瑞萍:评论功能测试模块,用于验证评论发布、回复、审核及显示等核心流程
# 基于Django TransactionTestCase支持数据库事务操作覆盖评论功能的关键场景测试
# 赵瑞萍导入Django测试核心工具提供请求模拟、URL解析等能力
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse # 用于反向解析URL
from django.urls import reverse # 用于反向解析视图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 # 工具函数
# 赵瑞萍:导入测试所需的模型和工具,覆盖用户、文章、评论等关联数据
from accounts.models import BlogUser # 自定义用户模型,用于创建测试用户
from blog.models import Category, Article, BlogSettings # 博客分类、文章、设置模型
from comments.models import Comment # 评论模型,测试核心对象
from comments.templatetags.comments_tags import * # 评论模板标签,用于测试评论树解析
from djangoblog.utils import get_max_articleid_commentid # 工具函数,辅助测试数据处理
# 创建测试类继承TransactionTestCase支持事务的测试类适合涉及数据库事务的测试
class CommentsTest(TransactionTestCase):
def setUp(self):
"""测试前的初始化设置,每个测试方法执行前都会调用"""
self.client = Client() # 创建测试客户端,用于模拟用户请求
self.factory = RequestFactory() # 创建请求工厂,用于构造请求对象
"""
赵瑞萍评论功能测试类继承TransactionTestCase支持事务回滚确保测试独立性
覆盖场景评论发布回复评论评论审核状态评论列表显示等核心功能
"""
# 配置博客设置(评论需要审核)
from blog.models import BlogSettings
def setUp(self):
"""
赵瑞萍测试前置初始化方法每个测试函数执行前自动调用
初始化测试客户端请求工厂测试数据用户博客设置为测试提供基础环境
"""
# 赵瑞萍创建测试客户端用于模拟用户发起HTTP请求如提交评论
self.client = Client()
# 赵瑞萍:创建请求工厂,用于构造自定义请求对象(灵活模拟请求场景)
self.factory = RequestFactory()
# 赵瑞萍:配置博客评论设置,开启"评论需要审核"功能(模拟真实业务场景)
value = BlogSettings()
value.comment_need_review = True # 评论需要审核才能显示
value.save()
value.comment_need_review = True # 评论需审核才显示
value.save() # 保存设置到数据库
# 创建超级用户(用于测试登录状态下的评论功能)
# 赵瑞萍:创建超级用户,用于测试登录状态下的评论发布权限
self.user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="liangliangyy1")
password="liangliangyy1"
)
def update_article_comment_status(self, article):
"""辅助方法:更新文章所有评论为启用状态(模拟审核通过)"""
comments = article.comment_set.all() # 获取文章的所有评论
"""
赵瑞萍测试辅助方法用于批量更新文章评论为"启用"状态模拟审核通过
参数article - 目标文章对象需启用其下所有评论
"""
# 赵瑞萍:获取该文章关联的所有评论查询集
comments = article.comment_set.all()
# 赵瑞萍遍历评论将is_enable设为True审核通过并保存
for comment in comments:
comment.is_enable = True # 设为启用
comment.save() # 保存更改
comment.is_enable = True
comment.save()
def test_validate_comment(self):
"""测试评论功能的核心逻辑:发布评论、回复评论、评论显示等"""
# 登录测试用户
"""
赵瑞萍核心测试方法验证评论发布回复审核显示等完整流程
包含登录验证文章创建普通评论发布嵌套回复Markdown内容测试
"""
# 赵瑞萍:模拟用户登录,获取评论发布权限(需登录才能评论)
self.client.login(username='liangliangyy1', password='liangliangyy1')
# 创建测试分类
# 赵瑞萍:创建测试分类(文章必须关联分类,满足模型约束)
category = Category()
category.name = "categoryccc"
category.save()
# 创建测试文章
# 赵瑞萍:创建测试文章(评论需关联文章,为评论提供目标对象)
article = Article()
article.title = "nicetitleccc"
article.body = "nicecontentccc"
article.author = self.user # 关联作者
article.title = "nicetitleccc" # 文章标题
article.body = "nicecontentccc" # 文章内容
article.author = self.user # 关联创建者(超级用户)
article.category = category # 关联分类
article.type = 'a' # 文章类型(假设'a'表示普通文章
article.status = 'p' # 发布状态(假设'p'表示已发布
article.type = 'a' # 假设'a'为普通文章类型(符合项目定义
article.status = 'p' # 假设'p'为已发布状态(只有已发布文章可评论
article.save()
# 获取评论提交的URL反向解析评论提交视图
# 赵瑞萍反向解析评论提交视图的URL传入文章ID参数
comment_url = reverse(
'comments:postcomment', kwargs={
'article_id': article.id}) # 传入文章ID参数
# 测试发布第一条评论
response = self.client.post(comment_url,
{
'body': '123ffffffffff' # 评论内容
})
# 验证评论提交后是否重定向(通常评论成功后会跳转到文章页)
'comments:postcomment', kwargs={'article_id': article.id}
)
# 赵瑞萍测试场景1 - 发布第一条普通评论
response = self.client.post(
comment_url,
{'body': '123ffffffffff'} # 评论内容
)
# 赵瑞萍验证评论提交后是否重定向通常跳回文章详情页状态码302
self.assertEqual(response.status_code, 302)
# 重新获取文章对象(从数据库刷新
# 赵瑞萍:从数据库刷新文章对象(获取最新关联的评论数据
article = Article.objects.get(pk=article.pk)
# 因为评论需要审核初始is_enable=False所以评论列表长度应为0
# 赵瑞萍未审核的评论is_enable=False评论列表长度应为0
self.assertEqual(len(article.comment_list()), 0)
# 调用辅助方法,将评论设为启用(模拟审核通过)
# 赵瑞萍:调用辅助方法模拟审核通过,启用评论
self.update_article_comment_status(article)
# 此时评论列表应包含1条评论
# 赵瑞萍审核后评论应显示列表长度应为1
self.assertEqual(len(article.comment_list()), 1)
# 测试发布第二条评论
response = self.client.post(comment_url,
{
'body': '123ffffffffff',
})
# 赵瑞萍测试场景2 - 发布第二条普通评论(验证多评论存储)
response = self.client.post(
comment_url,
{'body': '123ffffffffff'}
)
self.assertEqual(response.status_code, 302) # 验证重定向
article = Article.objects.get(pk=article.pk)
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 2) # 验证评论数量
self.assertEqual(len(article.comment_list()), 2) # 验证评论数量增加
# 测试回复评论功能
# 获取第一条评论的ID作为父评论ID
# 赵瑞萍:测试场景3 - 回复评论(嵌套回复功能
# 获取第一条评论的ID作为父评论ID(建立回复关联)
parent_comment_id = article.comment_list()[0].id
# 发布带Markdown格式的回复测试富文本内容
response = self.client.post(comment_url,
{
'body': '''
# Title1 Markdown标题
# 赵瑞萍发布带Markdown格式的回复测试富文本内容支持
response = self.client.post(
comment_url,
{
'body': '''
# Title1 Markdown标题
```python
import os # Markdown代码块
import os # Markdown代码块
''',
'parent_comment_id': parent_comment_id # 传入父评论ID标识为回复
}
)
# 赵瑞萍:验证回复提交是否成功(重定向)
self.assertEqual(response.status_code, 302)
# 赵瑞萍:刷新文章对象并启用所有评论(模拟审核)
article = Article.objects.get(pk=article.pk)
self.update_article_comment_status(article)
# 赵瑞萍验证总评论数原有2条+1条回复=3条
self.assertEqual(len(article.comment_list()), 3)
# 赵瑞萍测试场景4 - 验证评论树解析(通过模板标签解析嵌套关系)
# 获取所有已启用的评论查询集
comment_list = Comment.objects.filter(is_enable=True)
# 获取第一条评论(父评论)
parent_comment = comment_list.first()
# 调用模板标签函数,解析该父评论的所有子评论
child_comments = parse_commenttree(comment_list, parent_comment)
# 赵瑞萍验证子评论数量仅1条回复
self.assertEqual(len(child_comments), 1)
# 赵瑞萍验证子评论的父评论ID是否正确匹配
self.assertEqual(child_comments[0].parent_comment.id, parent_comment.id)

@ -1,21 +1,23 @@
# 导入Django的path函数用于定义URL路径
# 赵瑞萍评论应用URL配置模块用于定义评论相关的URL路由规则
# 核心功能映射评论提交的URL路径到对应视图支持通过文章ID关联评论目标
# 赵瑞萍导入Django URL核心函数用于定义路径匹配规则
from django.urls import path
# 导入当前应用下的views模块用于关联视图函数/类
# 赵瑞萍:导入当前应用的视图模块,关联评论提交的处理逻辑
from . import views
# 定义应用命名空间为"comments"用于在模板中通过命名空间引用URL避免多应用URL名称冲突
# 赵瑞萍:定义应用命名空间"comments"避免多应用间URL名称冲突
# 模板中引用格式:{% url 'comments:postcomment' article_id %}
app_name = "comments"
# URL模式列表定义该应用的URL路由规则
# 赵瑞萍URL模式列表存储该应用的所有路由规则
urlpatterns = [
# 定义一个评论提交的URL路径
# 赵瑞萍:评论提交路由,用于处理用户发布/回复评论的请求
path(
# URL路径字符串包含一个整数类型的文章ID参数(article_id),用于指定评论所属的文章
'article/<int:article_id>/postcomment',
# 关联的视图类使用as_view()方法将类视图转换为可调用的视图函数
'article/<int:article_id>/postcomment', # URL路径包含整数类型的文章ID参数article_id
# 关联视图类将CommentPostView类视图转换为可调用的视图函数
views.CommentPostView.as_view(),
# 为该URL指定名称"postcomment",结合应用命名空间可通过"comments:postcomment"引用
name='postcomment'
name='postcomment' # URL名称用于反向解析如reverse('comments:postcomment')
),
]

@ -1,34 +1,37 @@
# 导入日志模块,用于记录程序运行中的信息和错误
# 赵瑞萍:评论邮件通知模块,用于评论提交后发送邮件通知
# 核心功能:向评论者发送感谢邮件,向被回复者发送评论回复通知,支持多语言和链接跳转
# 赵瑞萍:导入日志模块,记录邮件发送过程中的信息和异常
import logging
# 导入Django的翻译函数用于实现多语言支持_为常用别名
# 赵瑞萍导入Django国际化翻译工具实现邮件内容多语言支持
from django.utils.translation import gettext_lazy as _
# 从项目工具模块导入获取当前站点信息和发送邮件的工具函数
from djangoblog.utils import get_current_site
from djangoblog.utils import send_email
# 赵瑞萍:导入项目工具函数,获取站点信息和发送邮件
from djangoblog.utils import get_current_site # 获取当前站点域名,用于构建文章链接
from djangoblog.utils import send_email # 项目封装的邮件发送函数
# 创建当前模块的日志记录器,用于记录该模块相关的日志信息
# 赵瑞萍:创建当前模块的日志记录器,命名为当前模块名,便于日志定位
logger = logging.getLogger(__name__)
def send_comment_email(comment):
"""
发送评论相关邮件通知
功能说明
1. 向评论者发送感谢邮件
2. 若该评论是回复其他评论的即有父评论则向被回复的评论者发送回复通知邮件
赵瑞萍发送评论相关邮件通知的核心函数
分两种场景发送邮件
1. 向当前评论的发布者发送感谢评论邮件
2. 若当前评论是回复其他评论有父评论向被回复者发送回复通知邮件
参数comment - 已保存的Comment模型实例包含评论作者关联文章父评论等信息
"""
# 获取当前站点的域名如example.com
# 赵瑞萍:获取当前站点的域名(如www.example.com,用于构建完整的文章访问链接
site = get_current_site().domain
# 邮件主题:感谢评论(支持多语言)
# 赵瑞萍:邮件主题(支持多语言,根据项目语言配置自动切换
subject = _('Thanks for your comment')
# 构建评论所属文章的完整URL包含协议和域名
# 赵瑞萍构建评论所属文章的完整URLHTTPS协议+域名+文章绝对路径
article_url = f"https://{site}{comment.article.get_absolute_url()}"
# 构建给评论者的邮件内容HTML格式支持多语言
# 使用占位符替换文章URL和标题
# 赵瑞萍构建给评论者的感谢邮件内容HTML格式支持超链接
# 使用字符串格式化替换占位符,注入文章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,
@ -36,19 +39,19 @@ def send_comment_email(comment):
<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
'article_url': article_url, # 文章完整访问链接
'article_title': comment.article.title # 评论所属文章的标题
}
# 获取评论者的邮箱地址
# 赵瑞萍:获取当前评论者的邮箱地址(从评论作者关联的用户模型中获取)
tomail = comment.author.email
# 发送感谢邮件给评论者
# 赵瑞萍:调用邮件发送函数,向评论者发送感谢邮件
send_email([tomail], subject, html_content)
try:
# 检查当前评论是否有父评论(即是否是回复其他评论
# 赵瑞萍:判断当前评论是否有父评论(即是否是对其他评论的回复
if comment.parent_comment:
# 构建给被回复者的邮件内容HTML格式支持多语言
# 赵瑞萍:构建给被回复者的邮件内容HTML格式告知其评论收到回复
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
<br/>
@ -57,14 +60,14 @@ def send_comment_email(comment):
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 # 被回复的评论内容
'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)
except Exception as e:
# 若发送过程中出现异常,记录错误日志
# 赵瑞萍:捕获邮件发送过程中的所有异常,记录错误日志(不中断程序执行)
logger.error(e)

@ -1,96 +1,115 @@
# Create your views here.
# 导入Django相关异常、响应和工具类
from django.core.exceptions import ValidationError # 用于抛出数据验证异常
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 accounts.models import BlogUser # 用户模型
from blog.models import Article # 文章模型
from .forms import CommentForm # 评论表单
from .models import Comment # 评论模型
# 赵瑞萍:评论提交视图模块,用于处理用户评论发布、数据验证、业务逻辑处理及响应返回
# 基于Django FormView实现支持CSRF保护、表单验证、评论状态控制及回复功能处理
# 赵瑞萍导入Django核心组件提供异常处理、响应、数据查询及装饰器支持
from django.core.exceptions import ValidationError # 数据验证异常,用于抛出评论相关业务错误
from django.http import HttpResponseRedirect # 重定向响应类,用于评论成功后跳转
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 accounts.models import BlogUser # 自定义用户模型,用于关联评论作者
from blog.models import Article # 文章模型,评论需关联具体文章
from .forms import CommentForm # 评论表单,用于前端输入验证
from .models import Comment # 评论模型,用于数据存储
class CommentPostView(FormView):
"""评论提交处理的类视图"""
form_class = CommentForm # 指定使用的表单类
template_name = 'blog/article_detail.html' # 表单渲染和错误显示的模板
"""
赵瑞萍评论提交处理类视图继承FormView封装表单处理流程
核心功能接收评论提交请求验证数据合法性处理评论保存逻辑返回对应响应
支持场景普通评论发布评论回复评论审核状态控制CSRF防护
"""
# 赵瑞萍指定表单类为CommentForm用于数据验证和字段映射
form_class = CommentForm
# 赵瑞萍:指定模板为文章详情页,用于表单错误时重新渲染页面并显示错误
template_name = 'blog/article_detail.html'
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
"""
重写dispatch方法添加CSRF保护
使用method_decorator将csrf_protect装饰器应用到dispatch方法
确保表单提交经过CSRF验证防止跨站请求伪造攻击
赵瑞萍重写dispatch方法添加CSRF保护
通过method_decorator将csrf_protect装饰器应用到请求分发流程确保所有请求经过CSRF验证
防止跨站请求伪造攻击保护评论提交接口安全
"""
# 赵瑞萍调用父类dispatch方法维持原有请求分发逻辑
return super(CommentPostView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
"""
处理GET请求
当通过GET访问评论提交URL时重定向到对应的文章详情页的评论区
赵瑞萍处理GET请求避免直接通过URL访问评论提交接口
当用户以GET方式访问时重定向到对应文章详情页的评论区
"""
article_id = self.kwargs['article_id'] # 从URL参数中获取文章ID
article = get_object_or_404(Article, pk=article_id) # 获取对应的文章对象
url = article.get_absolute_url() # 获取文章的绝对URL
return HttpResponseRedirect(url + "#comments") # 重定向到文章详情页的评论锚点
# 赵瑞萍从URL参数中获取文章ID确定评论所属文章
article_id = self.kwargs['article_id']
# 赵瑞萍查询文章对象不存在则返回404
article = get_object_or_404(Article, pk=article_id)
# 赵瑞萍获取文章绝对URL拼接评论区锚点重定向到评论区
url = article.get_absolute_url()
return HttpResponseRedirect(url + "#comments")
def form_invalid(self, form):
"""
表单数据验证失败时的处理
当表单提交的数据验证不通过如必填项为空格式错误等
重新渲染文章详情页并携带错误表单对象和文章对象方便前端显示错误信息
赵瑞萍表单数据验证失败时的处理方法
当用户输入不符合规则如评论为空长度超限重新渲染文章详情页并携带错误信息
"""
article_id = self.kwargs['article_id'] # 获取文章ID
article = get_object_or_404(Article, pk=article_id) # 获取文章对象
# 赵瑞萍从URL参数获取文章ID关联当前评论的文章
article_id = self.kwargs['article_id']
# 赵瑞萍:查询文章对象,确保页面渲染时有文章数据
article = get_object_or_404(Article, pk=article_id)
# 赵瑞萍:返回渲染后的页面,携带包含错误信息的表单和文章对象
return self.render_to_response({
'form': form, # 包含错误信息的表单对象
'article': article # 文章对象,用于页面渲染
'form': form, # 含错误提示的表单对象,前端可渲染错误信息
'article': article # 文章对象,用于页面展示文章内容
})
def form_valid(self, form):
"""
表单数据验证合法后的处理逻辑
当表单数据验证通过后执行评论保存等业务逻辑
赵瑞萍表单数据验证通过后的核心业务逻辑处理
完成评论对象的构建关联数据设置业务规则校验及保存最后重定向到评论位置
"""
user = self.request.user # 获取当前登录用户
author = BlogUser.objects.get(pk=user.pk) # 根据用户ID获取对应的用户对象
article_id = self.kwargs['article_id'] # 获取文章ID
article = get_object_or_404(Article, pk=article_id) # 获取文章对象
# 检查文章评论状态:若文章评论关闭或文章状态为关闭,则抛出验证异常
# 赵瑞萍:获取当前登录用户对象(需登录才能评论)
user = self.request.user
# 赵瑞萍根据用户ID查询BlogUser实例作为评论作者
author = BlogUser.objects.get(pk=user.pk)
# 赵瑞萍从URL参数获取文章ID确定评论所属文章
article_id = self.kwargs['article_id']
# 赵瑞萍查询文章对象不存在则返回404
article = get_object_or_404(Article, pk=article_id)
# 赵瑞萍:业务规则校验——检查文章是否允许评论
# 若文章评论状态为'c'(关闭)或文章状态为'c'(关闭),抛出验证异常
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.")
# 保存表单数据但不提交到数据库commit=False以便后续补充字段
# 赵瑞萍:保存表单数据但不提交到数据库commit=False预留字段补充空间
comment = form.save(False)
comment.article = article # 关联评论对应的文章
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.is_enable = True
comment.author = author # 设置评论的作者
comment.author = author # 关联评论到作者
# 处理回复功能若存在父评论ID则关联父评论
# 赵瑞萍处理评论回复功能——判断是否存在父评论ID
if form.cleaned_data['parent_comment_id']:
# 赵瑞萍根据父评论ID查询父评论对象建立回复关联
parent_comment = Comment.objects.get(
pk=form.cleaned_data['parent_comment_id'])
comment.parent_comment = parent_comment
comment.parent_comment = parent_comment # 关联当前评论到父评论
comment.save(True) # 最终保存评论到数据库
# 赵瑞萍:最终将评论数据提交到数据库保存
comment.save(True)
# 重定向到文章详情页中当前评论的锚点位置
# 赵瑞萍:重定向到文章详情页中当前评论的锚点位置,方便用户查看自己的评论
return HttpResponseRedirect(
"%s#div-comment-%d" %
(article.get_absolute_url(), comment.pk))
(article.get_absolute_url(), comment.pk) # 拼接文章URL和评论锚点如#div-comment-1
)
Loading…
Cancel
Save