周俊杰 1 month ago
commit 1efbacc3a7

@ -1,49 +1,79 @@
# 模块级注释Django管理后台配置模块 - 评论管理
# 本模块定义了评论模型在Django管理后台的显示配置和操作功能
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 _
# 函数级注释:禁用评论状态操作
# 管理员动作函数,用于批量禁用选中的评论
def disable_commentstatus(modeladmin, request, queryset):
# 核心代码将查询集中所有评论的is_enable字段更新为False
queryset.update(is_enable=False)
# 函数级注释:启用评论状态操作
# 管理员动作函数,用于批量启用选中的评论
def enable_commentstatus(modeladmin, request, queryset):
# 核心代码将查询集中所有评论的is_enable字段更新为True
queryset.update(is_enable=True)
# 设置动作函数的显示名称(国际化)
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
# 类级注释:评论管理类
# 继承自admin.ModelAdmin自定义评论模型在Django管理后台的显示和行为
class CommentAdmin(admin.ModelAdmin):
# 每页显示记录数配置
list_per_page = 20
# 列表页显示的字段配置
list_display = (
'id',
'body',
'link_to_userinfo',
'link_to_article',
'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]
# 使用原始ID输入框的外键字段提升大表性能
raw_id_fields = ('author', 'article')
# 搜索字段配置
search_fields = ('body',)
# 方法级注释:用户信息链接显示
# 自定义方法,在列表页显示带链接的用户信息
def link_to_userinfo(self, obj):
# 核心代码获取用户模型的app_label和model_name
info = (obj.author._meta.app_label, obj.author._meta.model_name)
# 核心代码生成用户编辑页面的URL
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
# 核心代码返回带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_label和model_name
info = (obj.article._meta.app_label, obj.article._meta.model_name)
# 核心代码生成文章编辑页面的URL
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))
# 设置自定义方法在列表页的显示名称(国际化)
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')
link_to_article.short_description = _('Article')

@ -1,5 +1,11 @@
# 模块级注释Django应用配置模块
# 本模块定义了comments应用的配置信息用于Django应用注册和初始化设置
from django.apps import AppConfig
# 类级注释:评论应用配置类
# 继承自AppConfig用于配置comments应用的基本信息和启动行为
class CommentsConfig(AppConfig):
name = 'comments'
# 应用名称字段定义应用的完整Python路径
# 此名称用于Django内部识别和应用引用
name = 'comments'

@ -1,13 +1,24 @@
# 模块级注释Django表单定义模块 - 评论功能
# 本模块定义了评论相关的表单类,用于前端评论数据的验证和处理
from django import forms
from django.forms import ModelForm
# 导入评论模型,用于构建模型表单
from .models import Comment
# 类级注释:评论表单类
# 继承自ModelForm基于Comment模型自动生成表单字段和验证规则
class CommentForm(ModelForm):
# 父级评论ID字段隐藏输入字段用于处理评论回复功能
# 存储被回复评论的ID用户不可见但表单会处理
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
# 元数据类:配置模型表单的基本行为
class Meta:
# 指定关联的模型Comment模型
model = Comment
fields = ['body']
# 定义表单中包含的字段:只包含评论正文字段
# 其他字段如作者、文章等通过其他方式自动设置
fields = ['body']

@ -1,38 +1,66 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 模块级注释Django数据库迁移文件
# 本模块定义了评论功能的数据库迁移操作,包括创建评论表和相关字段
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
# 类级注释:数据库迁移类
# 继承自migrations.Migration定义数据库结构变更的完整操作序列
class Migration(migrations.Migration):
# 标记为初始迁移
# 表示这是comments应用的第一个迁移文件
initial = True
# 依赖关系定义
# 指定本迁移执行前需要先完成的依赖迁移
dependencies = [
# 依赖blog应用的初始迁移确保文章表已创建
('blog', '0001_initial'),
# 依赖用户模型迁移,确保用户表已存在
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
# 迁移操作列表
# 按顺序执行的数据库操作集合
operations = [
# 创建模型操作
# 定义Comment模型的数据库表结构
migrations.CreateModel(
name='Comment',
fields=[
# 主键字段自增BigAutoField作为评论的唯一标识
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
# 正文字段存储评论内容限制最大长度300字符
('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='修改时间')),
# 启用状态字段控制评论是否显示布尔类型默认True
('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='上级评论')),
],
# 模型元选项配置
# 定义模型在admin中的显示名称和默认排序等行为
options={
# 单数显示名称在Django admin中显示的单数名称
'verbose_name': '评论',
# 复数显示名称在Django admin中显示的复数名称
'verbose_name_plural': '评论',
# 默认排序按ID倒序排列最新评论显示在最前面
'ordering': ['-id'],
# 最新记录定义指定按id字段获取最新记录
'get_latest_by': 'id',
},
),
]
]

@ -1,18 +1,34 @@
# Generated by Django 4.1.7 on 2023-04-24 13:48
# 模块级注释Django数据库迁移文件 - 评论功能字段修改
# 本模块用于修改评论表中is_enable字段的默认值配置
from django.db import migrations, models
# 类级注释:数据库迁移类
# 继承自migrations.Migration定义对现有数据库结构的修改操作
class Migration(migrations.Migration):
# 依赖关系定义
# 指定本迁移依赖于comments应用的初始迁移文件
dependencies = [
# 依赖comments应用的0001_initial迁移
# 确保评论表已创建后再执行本迁移
('comments', '0001_initial'),
]
# 迁移操作列表
# 包含对数据库结构的具体修改操作
operations = [
# 修改字段操作
# 对Comment模型的is_enable字段进行配置修改
migrations.AlterField(
# 指定要修改的模型名称
model_name='comment',
# 指定要修改的字段名称
name='is_enable',
# 新的字段配置将默认值从True改为False
# 新创建的评论默认不显示,需要手动启用
field=models.BooleanField(default=False, verbose_name='是否显示'),
),
]
]

@ -1,60 +1,89 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
# 模块级注释Django数据库迁移文件 - 评论模型字段重构
# 本模块对评论模型进行重大重构,包括字段重命名、显示名称国际化等操作
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
# 类级注释:数据库迁移类
# 继承自migrations.Migration定义对评论模型的多个结构变更操作
class Migration(migrations.Migration):
# 依赖关系定义
# 指定本迁移执行前需要完成的依赖迁移文件
dependencies = [
# 依赖用户模型迁移,确保用户表结构就绪
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
# 依赖博客应用的第5次迁移确保文章表结构稳定
('blog', '0005_alter_article_options_alter_category_options_and_more'),
# 依赖评论应用的第2次迁移确保之前的字段修改已应用
('comments', '0002_alter_comment_is_enable'),
]
# 迁移操作列表
# 包含多个对评论模型的结构变更操作,按顺序执行
operations = [
# 修改模型选项操作
# 更新Comment模型的元数据配置主要修改显示名称
migrations.AlterModelOptions(
name='comment',
options={'get_latest_by': 'id', 'ordering': ['-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',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
# 新增字段操作
# 添加新的最后修改时间字段,使用更清晰的字段命名
migrations.AddField(
model_name='comment',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
# 修改字段操作
# 更新文章外键字段的显示名称,改为英文
migrations.AlterField(
model_name='comment',
name='article',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'),
),
# 修改字段操作
# 更新作者外键字段的显示名称,改为英文
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'),
),
# 修改字段操作
# 更新启用状态字段的显示名称,改为英文
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='enable'),
),
# 修改字段操作
# 更新父级评论外键字段的显示名称,改为英文
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'),
),
]
]

@ -1,39 +1,56 @@
# 模块级注释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
# Create your models here.
# 类级注释:评论模型类
# 继承自models.Model定义了评论数据的数据库表结构和字段约束
class Comment(models.Model):
# 正文字段存储评论内容最大长度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)
# 作者字段:外键关联到用户模型,级联删除,使用国际化标签
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
on_delete=models.CASCADE)
# 文章字段:外键关联到文章模型,级联删除,使用国际化标签
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE)
# 父级评论字段:自关联外键,支持评论回复功能,允许空值
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)
# 元数据类配置模型在数据库和admin中的行为
class Meta:
# 默认排序按ID倒序最新评论在前
ordering = ['-id']
# 单数显示名称:使用国际化翻译
verbose_name = _('comment')
# 复数显示名称:与单数相同
verbose_name_plural = verbose_name
# 最新记录定义按ID字段确定最新记录
get_latest_by = 'id'
# 字符串表示方法定义对象在Python中的显示格式
def __str__(self):
return self.body
# 返回评论正文作为对象的字符串表示
return self.body

@ -1,68 +1,92 @@
# 模块级注释Django测试模块 - 评论系统功能测试
# 本模块包含评论系统的完整测试用例,验证评论发布、回复、显示等核心功能
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
# 导入相关模型,用于测试数据准备
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.
# 类级注释:评论系统测试类
# 继承自TransactionTestCase支持数据库事务的测试用例
class CommentsTest(TransactionTestCase):
# 测试初始化方法:在每个测试方法执行前运行
def setUp(self):
# 创建测试客户端用于模拟HTTP请求
self.client = Client()
# 创建请求工厂,用于构建请求对象
self.factory = RequestFactory()
# 配置博客设置:启用评论审核功能
from blog.models import BlogSettings
value = BlogSettings()
value.comment_need_review = True
value.comment_need_review = True # 设置评论需要审核
value.save()
# 创建超级用户,用于测试认证相关的评论功能
self.user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="liangliangyy1")
# 辅助方法:更新文章评论状态为启用
def update_article_comment_status(self, article):
# 获取文章的所有评论
comments = article.comment_set.all()
# 遍历所有评论,将其状态设置为启用
for comment in comments:
comment.is_enable = True
comment.save()
# 测试方法:验证评论功能
def test_validate_comment(self):
# 使用测试用户登录系统
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.category = category
article.type = 'a'
article.status = 'p'
article.type = 'a' # 文章类型
article.status = 'p' # 发布状态
article.save()
# 生成评论提交URL
comment_url = reverse(
'comments:postcomment', kwargs={
'article_id': article.id})
# 测试提交第一条评论
response = self.client.post(comment_url,
{
'body': '123ffffffffff'
})
# 验证响应状态码为302重定向
self.assertEqual(response.status_code, 302)
# 重新获取文章对象验证评论数量由于审核机制初始应为0
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 0)
self.update_article_comment_status(article)
# 更新评论状态为启用后验证评论数量变为1
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 1)
# 测试提交第二条评论
response = self.client.post(comment_url,
{
'body': '123ffffffffff',
@ -70,11 +94,15 @@ class CommentsTest(TransactionTestCase):
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)
# 获取第一条评论的ID用于测试回复功能
parent_comment_id = article.comment_list()[0].id
# 测试提交带Markdown格式的回复评论
response = self.client.post(comment_url,
{
'body': '''
@ -93,17 +121,25 @@ class CommentsTest(TransactionTestCase):
'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)
# 测试获取最大文章ID和评论ID功能
s = get_max_articleid_commentid()
self.assertIsNotNone(s)
# 测试评论邮件发送功能
from comments.utils import send_comment_email
send_comment_email(comment)
send_comment_email(comment)

@ -1,11 +1,23 @@
# 模块级注释Django URL配置模块 - 评论系统路由
# 本模块定义了评论系统的URL路由配置将URL路径映射到对应的视图函数
from django.urls import path
# 导入当前应用的视图模块用于处理URL请求
from . import views
# 应用命名空间定义:设置评论应用的命名空间为"comments"
# 用于Django的URL反向解析避免不同应用间的URL名称冲突
app_name = "comments"
# URL模式列表定义评论系统的所有URL路由规则
urlpatterns = [
# 评论提交路由:处理文章评论的提交请求
path(
# URL路径模式匹配/article/{文章ID}/postcomment格式的URL
# 其中<int:article_id>为路径参数捕获整数类型的文章ID
'article/<int:article_id>/postcomment',
# 对应的视图类使用CommentPostView类视图处理该路径的请求
views.CommentPostView.as_view(),
# URL名称命名为'postcomment'用于在模板和代码中进行URL反向解析
name='postcomment'),
]
]

@ -1,17 +1,29 @@
# 模块级注释:评论邮件通知模块
# 本模块提供评论相关的邮件通知功能,包括新评论确认邮件和评论回复通知邮件
import logging
# 导入Django国际化翻译模块
from django.utils.translation import gettext_lazy as _
# 导入工具函数:获取当前站点信息和发送邮件
from djangoblog.utils import get_current_site
from djangoblog.utils import send_email
# 创建日志记录器实例,用于记录邮件发送过程中的错误信息
logger = logging.getLogger(__name__)
# 函数级注释:发送评论邮件通知
# 主要功能:向评论作者发送评论确认邮件,如果是对回复的评论,则同时向被回复者发送通知邮件
def send_comment_email(comment):
# 获取当前站点域名,用于构建完整的文章链接
site = get_current_site().domain
# 邮件主题:使用国际化翻译
subject = _('Thanks for your comment')
# 构建完整的文章URL地址
article_url = f"https://{site}{comment.article.get_absolute_url()}"
# 构建评论确认邮件的HTML内容
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,
@ -19,10 +31,17 @@ 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}
# 获取评论作者的邮箱地址
tomail = comment.author.email
# 发送评论确认邮件给评论作者
send_email([tomail], subject, html_content)
# 异常处理块:处理评论回复通知邮件的发送
try:
# 检查是否存在父级评论(即当前评论是否为回复评论)
if comment.parent_comment:
# 构建回复通知邮件的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/>
@ -32,7 +51,11 @@ def send_comment_email(comment):
%(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)
# 捕获邮件发送过程中可能出现的任何异常
except Exception as e:
logger.error(e)
# 记录异常信息到日志,但不中断程序执行
logger.error(e)

@ -1,3 +1,5 @@
# 模块级注释Django视图模块 - 评论系统
# 本模块定义了评论提交的视图处理逻辑,包括评论验证、保存和重定向等功能
# Create your views here.
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
@ -6,58 +8,94 @@ from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
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
# 类级注释:评论提交视图类
# 继承自FormView处理评论表单的提交和验证
class CommentPostView(FormView):
# 指定使用的表单类
form_class = CommentForm
# 指定模板名称
template_name = 'blog/article_detail.html'
# 方法装饰器添加CSRF保护
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 调用父类的dispatch方法确保CSRF保护生效
return super(CommentPostView, self).dispatch(*args, **kwargs)
# GET请求处理方法
def get(self, request, *args, **kwargs):
# 从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):
# 获取文章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
})
# 表单验证成功处理方法
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
# 获取当前登录用户
user = self.request.user
# 根据用户ID获取用户对象
author = BlogUser.objects.get(pk=user.pk)
# 从URL参数获取文章ID
article_id = self.kwargs['article_id']
# 获取文章对象
article = get_object_or_404(Article, pk=article_id)
# 检查文章是否允许评论
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']:
# 获取父级评论对象
parent_comment = Comment.objects.get(
pk=form.cleaned_data['parent_comment_id'])
# 设置父级评论
comment.parent_comment = parent_comment
# 保存评论到数据库
comment.save(True)
# 重定向到文章页面并定位到新评论的位置
return HttpResponseRedirect(
"%s#div-comment-%d" %
(article.get_absolute_url(), comment.pk))
(article.get_absolute_url(), comment.pk))
Loading…
Cancel
Save