diff --git a/comments/admin.py b/comments/admin.py index 52d09d6..9f15c1d 100644 --- a/comments/admin.py +++ b/comments/admin.py @@ -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'%s' % (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'%s' % (link, obj.article.title)) - # 为自定义字段设置显示名称(支持国际化) + # 赵瑞萍:为自定义字段设置后台显示名称,支持国际化翻译,适配多语言环境 link_to_userinfo.short_description = _('User') link_to_article.short_description = _('Article') \ No newline at end of file diff --git a/comments/apps.py b/comments/apps.py index d3b6eb9..e1fca92 100644 --- a/comments/apps.py +++ b/comments/apps.py @@ -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' \ No newline at end of file + # 赵瑞萍:应用的唯一标识名称,必须与应用目录名完全一致 + # 用于在settings.py的INSTALLED_APPS中注册应用,Django通过该名称定位应用 + name = 'comments' + + # 赵瑞萍:可选配置,指定应用的 verbose 名称,用于后台管理界面显示 + # 若不设置,默认显示为name的值('comments') + verbose_name = '评论管理' \ No newline at end of file diff --git a/comments/forms.py b/comments/forms.py index 2112458..9a3bb14 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -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' \ No newline at end of file + model = Comment # 绑定Comment模型,表单数据将直接映射到模型对应的字段 + fields = ['body'] # 仅指定评论正文字段为前端输入项,其他字段(如作者、时间)由后端处理 \ No newline at end of file diff --git a/comments/migrations/0001_initial.py b/comments/migrations/0001_initial.py index 5199831..93b0336 100644 --- a/comments/migrations/0001_initial.py +++ b/comments/migrations/0001_initial.py @@ -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字段作为判断最新的依据 }, ), ] \ No newline at end of file diff --git a/comments/migrations/0002_alter_comment_is_enable.py b/comments/migrations/0002_alter_comment_is_enable.py index 29a22cc..2c60efe 100644 --- a/comments/migrations/0002_alter_comment_is_enable.py +++ b/comments/migrations/0002_alter_comment_is_enable.py @@ -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='是否显示' # 保持后台显示名称不变 ), ), ] \ No newline at end of file diff --git a/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py index 45fe48e..b60919c 100644 --- a/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py +++ b/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py @@ -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' # 备注从中文"上级评论"改为英文(国际化调整) + ), ), ] \ No newline at end of file diff --git a/comments/models.py b/comments/models.py index 69b3cb7..5ff9040 100644 --- a/comments/models.py +++ b/comments/models.py @@ -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 \ No newline at end of file diff --git a/comments/templatetags/comments_tags.py b/comments/templatetags/comments_tags.py index b46a954..a119a87 100644 --- a/comments/templatetags/comments_tags.py +++ b/comments/templatetags/comments_tags.py @@ -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 # 层级深度,用于控制评论的显示样式 } \ No newline at end of file diff --git a/comments/tests.py b/comments/tests.py index 26cc0a6..674a0ec 100644 --- a/comments/tests.py +++ b/comments/tests.py @@ -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代码块 \ No newline at end of file + 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) \ No newline at end of file diff --git a/comments/urls.py b/comments/urls.py index 35e9999..e123114 100644 --- a/comments/urls.py +++ b/comments/urls.py @@ -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//postcomment', - # 关联的视图类,使用as_view()方法将类视图转换为可调用的视图函数 + 'article//postcomment', # URL路径:包含整数类型的文章ID参数(article_id) + # 关联视图类:将CommentPostView类视图转换为可调用的视图函数 views.CommentPostView.as_view(), - # 为该URL指定名称"postcomment",结合应用命名空间可通过"comments:postcomment"引用 - name='postcomment' + name='postcomment' # URL名称,用于反向解析(如reverse('comments:postcomment')) ), ] \ No newline at end of file diff --git a/comments/utils.py b/comments/utils.py index 2becf36..fbd27f7 100644 --- a/comments/utils.py +++ b/comments/utils.py @@ -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(包含协议和域名) + # 赵瑞萍:构建评论所属文章的完整URL(HTTPS协议+域名+文章绝对路径) article_url = f"https://{site}{comment.article.get_absolute_url()}" - # 构建给评论者的邮件内容(HTML格式,支持多语言) - # 使用占位符替换文章URL和标题 + # 赵瑞萍:构建给评论者的感谢邮件内容(HTML格式,支持超链接) + # 使用字符串格式化替换占位符,注入文章URL和标题 html_content = _("""

Thank you very much for your comments on this site

You can visit %(article_title)s to review your comments, @@ -36,19 +39,19 @@ 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 + '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 %(article_title)s
has received a reply.
%(comment_body)s
@@ -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) \ No newline at end of file diff --git a/comments/views.py b/comments/views.py index 9b99bb0..6a48096 100644 --- a/comments/views.py +++ b/comments/views.py @@ -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)) \ No newline at end of file + (article.get_absolute_url(), comment.pk) # 拼接文章URL和评论锚点(如#div-comment-1) + ) \ No newline at end of file