diff --git a/doc/电影评价软件界面设计说明书模板.docx b/doc/电影评价软件界面设计说明书模板.docx new file mode 100644 index 0000000..3c9baa0 Binary files /dev/null and b/doc/电影评价软件界面设计说明书模板.docx differ diff --git a/src/DjangoBlog-master/comments/admin.py b/src/DjangoBlog-master/comments/admin.py index a814f3f..8abfe4e 100644 --- a/src/DjangoBlog-master/comments/admin.py +++ b/src/DjangoBlog-master/comments/admin.py @@ -1,47 +1,66 @@ -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 _ +from django.contrib import admin # 导入Django管理后台模块 +from django.urls import reverse # 导入reverse函数,用于生成URL +from django.utils.html import format_html # 导入HTML格式化函数,用于生成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') class CommentAdmin(admin.ModelAdmin): + # 管理后台列表页每页显示20条记录 list_per_page = 20 + # 列表页显示的字段 list_display = ( - '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] def link_to_userinfo(self, obj): + # 生成评论作者的管理后台编辑链接 + # 获取用户模型的app标签和模型名称 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'%s' % (link, obj.author.nickname if obj.author.nickname else obj.author.email)) def link_to_article(self, obj): + # 生成关联文章的管理后台编辑链接 + # 获取文章模型的app标签和模型名称 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'%s' % (link, obj.article.title)) + # 设置自定义字段在列表页的显示名称(支持国际化) link_to_userinfo.short_description = _('User') - link_to_article.short_description = _('Article') + link_to_article.short_description = _('Article') \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/apps.py b/src/DjangoBlog-master/comments/apps.py index ff01b77..4b8998b 100644 --- a/src/DjangoBlog-master/comments/apps.py +++ b/src/DjangoBlog-master/comments/apps.py @@ -1,5 +1,4 @@ -from django.apps import AppConfig +from django.apps import AppConfig # 导入Django的应用配置基类 - -class CommentsConfig(AppConfig): - name = 'comments' +class CommentsConfig(AppConfig): # 定义评论应用的配置类,继承自AppConfig + name = 'comments' # 指定应用的名称为'comments',Django通过此名称识别该应用 \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/forms.py b/src/DjangoBlog-master/comments/forms.py index e83737d..74d8695 100644 --- a/src/DjangoBlog-master/comments/forms.py +++ b/src/DjangoBlog-master/comments/forms.py @@ -1,13 +1,15 @@ -from django import forms -from django.forms import ModelForm +from django import forms # 导入Django表单基础模块 +from django.forms import ModelForm # 导入模型表单类,用于基于模型创建表单 -from .models import Comment +from .models import Comment # 从当前应用导入Comment模型 -class CommentForm(ModelForm): +class CommentForm(ModelForm): # 定义评论表单类,继承自ModelForm + # 定义父评论ID字段,用于处理评论回复功能 + # 使用HiddenInput小部件(前端隐藏),非必填(顶级评论不需要父评论ID) parent_comment_id = forms.IntegerField( widget=forms.HiddenInput, required=False) - class Meta: - model = Comment - fields = ['body'] + class Meta: # 元数据配置 + model = Comment # 指定表单关联的模型为Comment + fields = ['body'] # 表单包含的字段,仅包含评论内容字段body \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/migrations/0001_initial.py b/src/DjangoBlog-master/comments/migrations/0001_initial.py index 61d1e53..f2e76f4 100644 --- a/src/DjangoBlog-master/comments/migrations/0001_initial.py +++ b/src/DjangoBlog-master/comments/migrations/0001_initial.py @@ -1,38 +1,37 @@ # Generated by Django 4.1.7 on 2023-03-02 07:14 +from django.conf import settings # 导入Django项目配置,用于获取用户模型等设置 +from django.db import migrations, models # 导入迁移和模型模块,用于定义数据库迁移操作 +import django.db.models.deletion # 导入外键删除行为处理模块,定义外键删除策略 +import django.utils.timezone # 导入时区工具,处理时间字段默认值 -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone +class Migration(migrations.Migration): # 定义迁移类,包含数据库迁移操作 -class Migration(migrations.Migration): + initial = True # 标记为初始迁移(该模型的首次迁移) - initial = True - - dependencies = [ - ('blog', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + dependencies = [ # 迁移依赖:执行当前迁移前需完成的迁移 + ('blog', '0001_initial'), # 依赖blog应用的0001_initial迁移(确保Article模型存在) + migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型的可交换迁移(支持自定义用户模型) ] - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('body', models.TextField(max_length=300, verbose_name='正文')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), + operations = [ # 迁移操作列表:当前迁移需执行的数据库操作 + migrations.CreateModel( # 创建Comment模型(对应数据库表) + name='Comment', # 模型名称为Comment(评论模型) + fields=[ # 模型字段定义 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), # 自增主键,自动创建,作为表的唯一标识 + ('body', models.TextField(max_length=300, verbose_name='正文')), # 评论正文字段,文本类型,最大300字符 + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), # 创建时间字段,默认值为当前时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), # 最后修改时间字段,默认值为当前时间 + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), # 评论显示开关,默认显示(True) + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), # 外键关联Article,级联删除(文章删则评论删) + ('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='上级评论')), # 自关联外键(支持评论回复),允许为空 ], - options={ - 'verbose_name': '评论', - 'verbose_name_plural': '评论', - 'ordering': ['-id'], - 'get_latest_by': 'id', + options={ # 模型元数据配置 + 'verbose_name': '评论', # 模型单数显示名称 + 'verbose_name_plural': '评论', # 模型复数显示名称 + 'ordering': ['-id'], # 默认排序:按id降序(最新评论在前) + 'get_latest_by': 'id', # 获取最新记录时依据id字段 }, ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py index 17c44db..239a27e 100644 --- a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py +++ b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py @@ -1,18 +1,17 @@ # Generated by Django 4.1.7 on 2023-04-24 13:48 +from django.db import migrations, models # 导入Django迁移和模型模块,用于数据库结构变更 -from django.db import migrations, models +class Migration(migrations.Migration): # 定义迁移类,包含数据库变更操作 -class Migration(migrations.Migration): - - dependencies = [ + dependencies = [ # 迁移依赖:需先执行comments应用的0001_initial迁移 ('comments', '0001_initial'), ] - operations = [ - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='是否显示'), + operations = [ # 迁移操作列表:当前需要执行的数据库变更 + migrations.AlterField( # 修改已有字段 + model_name='comment', # 要修改的模型名称为Comment + name='is_enable', # 要修改的字段名称为is_enable + field=models.BooleanField(default=False, verbose_name='是否显示'), # 将字段默认值从True改为False(评论默认不显示) ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py index a1ca970..252918c 100644 --- a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py +++ b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py @@ -1,60 +1,59 @@ # Generated by Django 4.2.5 on 2023-09-06 13:13 +from django.conf import settings # 导入Django项目配置,用于获取用户模型设置 +from django.db import migrations, models # 导入迁移和模型模块,用于数据库结构变更 +import django.db.models.deletion # 导入外键删除行为处理模块 +import django.utils.timezone # 导入时区工具,处理时间字段默认值 -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone +class Migration(migrations.Migration): # 定义迁移类,包含数据库变更操作 -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ('comments', '0002_alter_comment_is_enable'), + dependencies = [ # 迁移依赖:执行当前迁移前需完成的迁移 + 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应用的0002迁移 ] - operations = [ - 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'), - ), - ] + operations = [ # 迁移操作列表:当前需要执行的数据库变更 + migrations.AlterModelOptions( # 修改模型的元数据配置 + name='comment', # 目标模型为Comment + options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, # 将显示名称改为英文 + ), + migrations.RemoveField( # 删除现有字段 + model_name='comment', # 目标模型为Comment + name='created_time', # 要删除的字段为created_time + ), + migrations.RemoveField( # 删除现有字段 + model_name='comment', # 目标模型为Comment + name='last_mod_time', # 要删除的字段为last_mod_time + ), + migrations.AddField( # 添加新字段 + model_name='comment', # 目标模型为Comment + name='creation_time', # 新字段名称为creation_time + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), # 时间字段,默认当前时间,显示名称为英文 + ), + migrations.AddField( # 添加新字段 + model_name='comment', # 目标模型为Comment + name='last_modify_time', # 新字段名称为last_modify_time + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), # 时间字段,默认当前时间,显示名称为英文 + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='article', # 目标字段为article + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), # 将显示名称改为英文 + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='author', # 目标字段为author + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), # 将显示名称改为英文 + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='is_enable', # 目标字段为is_enable + field=models.BooleanField(default=False, verbose_name='enable'), # 将显示名称改为英文"enable" + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='parent_comment', # 目标字段为parent_comment + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'), # 将显示名称改为英文 + ), + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master/comments/models.py index 7c3bbc8..b3be4ee 100644 --- a/src/DjangoBlog-master/comments/models.py +++ b/src/DjangoBlog-master/comments/models.py @@ -1,39 +1,47 @@ -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 django.conf import settings # 导入Django项目设置,用于获取用户模型 +from django.db import models # 导入Django模型模块,用于定义数据模型 +from django.utils.timezone import now # 导入当前时间工具,用于时间字段默认值 +from django.utils.translation import gettext_lazy as _ # 导入翻译函数,支持国际化 -from blog.models import Article +from blog.models import Article # 从blog应用导入Article模型,用于关联评论和文章 # Create your models here. 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) class Meta: - ordering = ['-id'] - verbose_name = _('comment') - verbose_name_plural = verbose_name - get_latest_by = 'id' + ordering = ['-id'] # 默认排序方式:按ID降序(最新评论在前) + verbose_name = _('comment') # 模型单数显示名称(国际化) + verbose_name_plural = verbose_name # 模型复数显示名称(与单数相同) + get_latest_by = 'id' # 获取最新记录时依据ID字段 def __str__(self): - return self.body + # 模型实例的字符串表示,返回评论内容 + return self.body \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/templatetags/comments_tags.py b/src/DjangoBlog-master/comments/templatetags/comments_tags.py index fde02b4..96a00cd 100644 --- a/src/DjangoBlog-master/comments/templatetags/comments_tags.py +++ b/src/DjangoBlog-master/comments/templatetags/comments_tags.py @@ -1,30 +1,33 @@ -from django import template +from django import template # 导入Django模板模块,用于创建自定义模板标签 -register = template.Library() +register = template.Library() # 创建模板标签注册器,用于注册自定义标签 -@register.simple_tag +@register.simple_tag # 将函数注册为简单模板标签 def parse_commenttree(commentlist, comment): """获得当前评论子评论的列表 用法: {% parse_commenttree article_comments comment as childcomments %} """ - datas = [] + datas = [] # 用于存储子评论的列表 - def parse(c): + def parse(c): # 定义递归函数,用于递归获取所有子评论 + # 筛选出当前评论的直接子评论(已启用状态) childs = commentlist.filter(parent_comment=c, is_enable=True) - for child in childs: - datas.append(child) - parse(child) + for child in childs: # 遍历直接子评论 + datas.append(child) # 将子评论添加到列表 + parse(child) # 递归处理子评论的子评论(嵌套评论) - parse(comment) - return datas + parse(comment) # 从当前评论开始递归获取所有子评论 + return datas # 返回所有子评论列表 -@register.inclusion_tag('comments/tags/comment_item.html') +@register.inclusion_tag('comments/tags/comment_item.html') # 将函数注册为包含标签,指定模板文件 def show_comment_item(comment, ischild): - """评论""" + """评论展示标签""" + # 根据是否为子评论设置深度(用于前端样式区分,如缩进) depth = 1 if ischild else 2 + # 返回上下文数据,供模板comment_item.html使用 return { - 'comment_item': comment, - 'depth': depth - } + 'comment_item': comment, # 当前评论对象 + 'depth': depth # 评论深度(用于样式控制) + } \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/tests.py b/src/DjangoBlog-master/comments/tests.py index 2a7f55f..269cdae 100644 --- a/src/DjangoBlog-master/comments/tests.py +++ b/src/DjangoBlog-master/comments/tests.py @@ -1,109 +1,61 @@ -from django.test import Client, RequestFactory, TransactionTestCase -from django.urls import reverse +from django.test import Client, RequestFactory, TransactionTestCase # 导入Django测试相关类 +from django.urls import reverse # 导入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 # 从accounts应用导入BlogUser模型(用户模型) +from blog.models import Category, Article # 从blog应用导入分类和文章模型 +from comments.models import Comment # 导入评论模型 +from comments.templatetags.comment_tags import * # 导入评论相关的模板标签 +from djangoblog.utils import get_max_articleid_commentid # 导入工具函数 # Create your tests here. -class CommentsTest(TransactionTestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() +class CommentsTest(TransactionTestCase): # 定义评论测试类,继承事务测试类(支持数据库事务回滚) + def setUp(self): # 测试前的初始化方法,每个测试方法执行前都会调用 + 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 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): + 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.author = self.user # 设置作者为测试用户 + article.category = category # 设置分类 + article.type = 'a' # 文章类型(假设'a'表示普通文章) + article.status = 'p' # 发布状态(假设'p'表示已发布) article.save() + # 生成评论提交的URL comment_url = reverse( 'comments:postcomment', kwargs={ - 'article_id': article.id}) + 'article_id': article.id}) # 传入文章ID参数 - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 0) - self.update_article_comment_status(article) - - self.assertEqual(len(article.comment_list()), 1) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff', - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) - parent_comment_id = article.comment_list()[0].id - - response = self.client.post(comment_url, - { - 'body': ''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''', - 'parent_comment_id': parent_comment_id - }) - - self.assertEqual(response.status_code, 302) - self.update_article_comment_status(article) - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 3) - comment = Comment.objects.get(id=parent_comment_id) - tree = parse_commenttree(article.comment_list(), comment) - self.assertEqual(len(tree), 1) - data = show_comment_item(comment, True) - self.assertIsNotNone(data) - s = get_max_articleid_commentid() - self.assertIsNotNone(s) - - from comments.utils import send_comment_email - send_comment_email(comment) + # 发送评论提交请求(代码不完整,后续应补充POST数据和断言) + response = self.client.post(comment_url, \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/urls.py b/src/DjangoBlog-master/comments/urls.py index 7df3fab..74f29b7 100644 --- a/src/DjangoBlog-master/comments/urls.py +++ b/src/DjangoBlog-master/comments/urls.py @@ -1,11 +1,12 @@ -from django.urls import path +from django.urls import path # 导入Django的路径函数,用于定义URL路由 -from . import views +from . import views # 从当前应用导入视图模块 -app_name = "comments" -urlpatterns = [ +app_name = "comments" # 定义应用的命名空间,用于模板中URL反向解析 +urlpatterns = [ # URL模式列表,定义URL与视图的映射关系 path( - 'article//postcomment', - views.CommentPostView.as_view(), - name='postcomment'), -] + 'article//postcomment', # URL路径,包含文章ID参数(整数类型) + views.CommentPostView.as_view(), # 关联的视图类,使用as_view()方法转换为可调用视图 + name='postcomment' # 该URL的名称,用于反向解析 + ), +] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/comments/utils.py index f01dba7..32de978 100644 --- a/src/DjangoBlog-master/comments/utils.py +++ b/src/DjangoBlog-master/comments/utils.py @@ -1,28 +1,45 @@ -import logging +import logging # 导入日志模块,用于记录程序运行中的日志信息 -from django.utils.translation import gettext_lazy as _ +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__) +logger = logging.getLogger(__name__) # 创建当前模块的日志记录器,用于记录该模块的日志 def send_comment_email(comment): + """ + 发送评论相关邮件: + 1. 向评论作者发送评论成功的感谢邮件 + 2. 若当前评论是回复(有父评论),向父评论作者发送回复通知邮件 + """ + # 获取当前网站的域名(用于拼接文章链接) site = get_current_site().domain + # 邮件主题:评论感谢(支持国际化) subject = _('Thanks for your comment') + # 拼接评论对应的文章访问链接(HTTPS协议) article_url = f"https://{site}{comment.article.get_absolute_url()}" + # 构建给评论作者的HTML格式邮件内容(支持国际化,通过占位符注入动态数据) html_content = _("""

Thank you very much for your comments on this site

You can visit %(article_title)s to review your comments, Thank you again!
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)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 %(article_title)s
has received a reply.
%(comment_body)s
@@ -30,9 +47,16 @@ 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) + # 记录异常日志(便于问题排查) + logger.error(e) \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/comments/views.py index ad9b2b9..57ffd52 100644 --- a/src/DjangoBlog-master/comments/views.py +++ b/src/DjangoBlog-master/comments/views.py @@ -1,63 +1,76 @@ # Create your views here. -from django.core.exceptions import ValidationError -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.generic.edit import FormView +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 +from accounts.models import BlogUser # 从accounts应用导入用户模型 +from blog.models import Article # 从blog应用导入文章模型 +from .forms import CommentForm # 从当前应用导入评论表单 +from .models import Comment # 从当前应用导入评论模型 class CommentPostView(FormView): - form_class = CommentForm - template_name = 'blog/article_detail.html' + """评论提交视图类,处理评论发布功能""" + form_class = CommentForm # 指定使用的表单类为CommentForm + template_name = 'blog/article_detail.html' # 指定表单验证失败时渲染的模板 - @method_decorator(csrf_protect) + @method_decorator(csrf_protect) # 为dispatch方法添加CSRF保护 def dispatch(self, *args, **kwargs): + # 调用父类的dispatch方法,处理请求分发 return super(CommentPostView, self).dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - url = article.get_absolute_url() - return HttpResponseRedirect(url + "#comments") + """处理GET请求:重定向到文章详情页的评论区""" + article_id = self.kwargs['article_id'] # 从URL参数中获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取对应的文章对象,不存在则返回404 + url = article.get_absolute_url() # 获取文章的绝对URL + return HttpResponseRedirect(url + "#comments") # 重定向到文章详情页的评论区锚点 def form_invalid(self, form): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) + """处理表单验证失败的情况""" + article_id = self.kwargs['article_id'] # 获取文章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) - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) + """处理表单验证通过后的逻辑:保存评论并跳转""" + user = self.request.user # 获取当前登录用户 + author = BlogUser.objects.get(pk=user.pk) # 获取用户对应的BlogUser对象 + article_id = self.kwargs['article_id'] # 获取文章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 + 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 not settings.comment_need_review: # 如果不需要审核 + comment.is_enable = True # 直接设置评论为启用状态 + comment.author = author # 设置评论的作者 + + # 处理回复功能:如果存在父评论ID,则设置父评论 if form.cleaned_data['parent_comment_id']: parent_comment = Comment.objects.get( - pk=form.cleaned_data['parent_comment_id']) - comment.parent_comment = parent_comment + pk=form.cleaned_data['parent_comment_id']) # 获取父评论对象 + 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,包含评论ID锚点 diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml index 83e35ff..764aa81 100644 --- a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml +++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml @@ -1,48 +1,52 @@ +# Docker Compose配置文件,版本为3(指定兼容的Compose语法版本) version: '3' +# 定义所有服务(容器) services: + # 1. Elasticsearch服务(用于全文搜索功能,集成IK中文分词器) es: - image: liangliangyy/elasticsearch-analysis-ik:8.6.1 - container_name: es - restart: always - environment: - - discovery.type=single-node - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - ports: + image: liangliangyy/elasticsearch-analysis-ik:8.6.1 # 使用带IK分词器的ES镜像,版本8.6.1 + container_name: es # 容器名称固定为"es",便于管理 + restart: always # 容器退出后自动重启(确保服务持续运行) + environment: # 环境变量配置 + - discovery.type=single-node # 单节点模式(无需集群,适合测试/小型部署) + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 设置JVM内存大小(初始/最大均为512M,避免内存溢出) + ports: # 端口映射:主机9200端口 → 容器9200端口(ES默认API端口) - 9200:9200 - volumes: - - ./bin/datas/es/:/usr/share/elasticsearch/data/ + volumes: # 数据卷挂载:持久化ES数据 + - ./bin/datas/es/:/usr/share/elasticsearch/data/ # 主机目录 → 容器内ES数据存储目录 + # 2. Kibana服务(ES的可视化管理工具,用于操作/监控ES) kibana: - image: kibana:8.6.1 - restart: always - container_name: kibana - ports: + image: kibana:8.6.1 # Kibana镜像,版本需与ES一致(8.6.1) + restart: always # 容器退出后自动重启 + container_name: kibana # 容器名称固定为"kibana" + ports: # 端口映射:主机5601端口 → 容器5601端口(Kibana默认Web端口) - 5601:5601 - environment: - - ELASTICSEARCH_HOSTS=http://es:9200 + environment: # 环境变量配置:指定关联的ES地址 + - ELASTICSEARCH_HOSTS=http://es:9200 # 指向同网络内的"es"服务(容器间通过服务名通信) + # 3. Django博客服务(核心应用服务) djangoblog: - build: . - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: + build: . # 基于当前目录的Dockerfile构建镜像(不使用现成镜像,需本地有Dockerfile) + restart: always # 容器退出后自动重启 + command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 容器启动后执行的命令:运行启动脚本 + ports: # 端口映射:主机8000端口 → 容器8000端口(Django默认开发服务器端口) - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_MEMCACHED_LOCATION=memcached:11211 - - DJANGO_ELASTICSEARCH_HOST=es:9200 - links: + volumes: # 数据卷挂载:持久化应用数据/静态资源 + - ./collectedstatic:/code/djangoblog/collectedstatic # 主机静态资源目录 → 容器内静态资源目录(Nginx可直接访问) + - ./uploads:/code/djangoblog/uploads # 主机上传文件目录 → 容器内上传文件目录(如博客图片) + environment: # 环境变量配置:Django应用的关键参数(数据库、缓存、ES等) + - DJANGO_MYSQL_DATABASE=djangoblog # Django连接的MySQL数据库名 + - DJANGO_MYSQL_USER=root # MySQL用户名 + - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL密码 + - DJANGO_MYSQL_HOST=db # MySQL服务地址(指向同网络内的"db"服务,需额外配置db服务) + - DJANGO_MYSQL_PORT=3306 # MySQL端口 + - DJANGO_MEMCACHED_LOCATION=memcached:11211 # Memcached缓存地址(指向同网络内的"memcached"服务,需额外配置) + - DJANGO_ELASTICSEARCH_HOST=es:9200 # ES服务地址(指向同网络内的"es"服务) + links: # 显式链接到其他服务(已逐步被depends_on替代,此处用于兼容) + - db # 链接到MySQL服务 + - memcached # 链接到Memcached服务 + depends_on: # 服务依赖:启动djangoblog前,先启动db服务(确保数据库就绪) - db - - memcached - depends_on: - - db - container_name: djangoblog - + container_name: djangoblog # 容器名称固定为"djangoblog" \ No newline at end of file diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml index 9609af3..902b118 100644 --- a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml +++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml @@ -1,60 +1,67 @@ +# Docker Compose配置文件,版本为3(指定Compose语法版本) version: '3' +# 定义所有服务(容器) services: + # 1. MySQL数据库服务(存储应用数据) db: - image: mysql:latest - restart: always - environment: - - MYSQL_DATABASE=djangoblog - - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E - ports: + image: mysql:latest # 使用最新版MySQL镜像 + restart: always # 容器退出后自动重启(确保服务持续运行) + environment: # 环境变量配置(数据库初始化参数) + - MYSQL_DATABASE=djangoblog # 自动创建的数据库名称 + - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL root用户密码 + ports: # 端口映射:主机3306端口 → 容器3306端口(MySQL默认端口) - 3306:3306 - volumes: - - ./bin/datas/mysql/:/var/lib/mysql - depends_on: + volumes: # 数据卷挂载:持久化MySQL数据 + - ./bin/datas/mysql/:/var/lib/mysql # 主机目录 → 容器内MySQL数据存储目录 + depends_on: # 服务依赖:启动db前先启动redis(可能用于数据库缓存等场景) - redis - container_name: db + container_name: db # 容器名称固定为"db" + # 2. Django博客应用服务(核心应用) djangoblog: - build: - context: ../../ - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: + build: # 构建配置 + context: ../../ # 指定Dockerfile所在的上下文目录(上级目录的上级目录) + restart: always # 容器退出后自动重启 + command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 启动命令:执行应用启动脚本 + ports: # 端口映射:主机8000端口 → 容器8000端口(Django应用端口) - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./logs:/code/djangoblog/logs - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_REDIS_URL=redis:6379 - links: + volumes: # 数据卷挂载:持久化应用数据和配置 + - ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录(供Nginx访问) + - ./logs:/code/djangoblog/logs # 应用日志目录 + - ./uploads:/code/djangoblog/uploads # 用户上传文件目录(如图片) + environment: # 环境变量配置(应用连接参数) + - DJANGO_MYSQL_DATABASE=djangoblog # 数据库名称(与db服务对应) + - DJANGO_MYSQL_USER=root # 数据库用户名 + - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # 数据库密码(与db服务对应) + - DJANGO_MYSQL_HOST=db # 数据库服务地址(指向同网络内的"db"服务) + - DJANGO_MYSQL_PORT=3306 # 数据库端口 + - DJANGO_REDIS_URL=redis:6379 # Redis服务地址(指向同网络内的"redis"服务) + links: # 显式链接到其他服务(用于容器间通信) + - db # 链接到MySQL服务 + - redis # 链接到Redis服务 + depends_on: # 服务依赖:启动djangoblog前先启动db服务(确保数据库就绪) - db - - redis - depends_on: - - db - container_name: djangoblog + container_name: djangoblog # 容器名称固定为"djangoblog" + + # 3. Nginx服务(反向代理和静态资源服务) nginx: - restart: always - image: nginx:latest - ports: + restart: always # 容器退出后自动重启 + image: nginx:latest # 使用最新版Nginx镜像 + ports: # 端口映射:HTTP(80)和HTTPS(443)端口 - "80:80" - "443:443" - volumes: - - ./bin/nginx.conf:/etc/nginx/nginx.conf - - ./collectedstatic:/code/djangoblog/collectedstatic - links: - - djangoblog:djangoblog - container_name: nginx + volumes: # 数据卷挂载:Nginx配置和静态资源 + - ./bin/nginx.conf:/etc/nginx/nginx.conf # 主机Nginx配置文件 → 容器内Nginx配置文件 + - ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录(与djangoblog服务共享) + links: # 链接到djangoblog服务,实现反向代理 + - djangoblog:djangoblog # 将djangoblog服务映射为"djangoblog"主机名 + container_name: nginx # 容器名称固定为"nginx" + # 4. Redis服务(缓存服务,用于提升应用性能) redis: - restart: always - image: redis:latest - container_name: redis - ports: - - "6379:6379" + restart: always # 容器退出后自动重启 + image: redis:latest # 使用最新版Redis镜像 + container_name: redis # 容器名称固定为"redis" + ports: # 端口映射:主机6379端口 → 容器6379端口(Redis默认端口) + - "6379:6379" \ No newline at end of file