Compare commits

...

24 Commits

Author SHA1 Message Date
zxc 2907454eb6 Update admin.py
5 months ago
zxc cfb4939c5a Update 0002_alter_owntracklog_options_and_more.py
5 months ago
zxc 82c7f26e8f Update 0001_initial.py
5 months ago
zxc 50b5eda147 Update 0001_initial.py
5 months ago
plhw57tbe c8630a7f95 Merge pull request '合并源代码' (#4) from smy_branch into develop
5 months ago
plhw57tbe ba2c683180 Update docker-compose.yml
5 months ago
plhw57tbe 94dd264394 Update docker-compose.es.yml
5 months ago
plhw57tbe dc3eef1f13 Update views.py
5 months ago
plhw57tbe 11ae3570e0 Update utils.py
5 months ago
plhw57tbe aa95dc52b3 Update urls.py
5 months ago
plhw57tbe 955b0a6ead Update tests.py
5 months ago
plhw57tbe 82d4f9a98b Update models.py
5 months ago
plhw57tbe 65020e1115 Update forms.py
5 months ago
plhw57tbe 190f420c49 Update apps.py
5 months ago
plhw57tbe a8dcf5aa39 Update admin.py
5 months ago
plhw57tbe 4836e57e98 Update comments_tags.py
5 months ago
plhw57tbe 662cfd9759 Update 0003_alter_comment_options_remove_comment_created_time_and_more.py
5 months ago
plhw57tbe d2ff176400 Update 0002_alter_comment_is_enable.py
5 months ago
plhw57tbe 63328bcfcb Update 0001_initial.py
5 months ago
smy bf828e6d08 Merge branch 'smy_branch' of https://bdgit.educoder.net/plhw57tbe/SoftwareMethodology into smy_branch
5 months ago
smy f27ecb885f Merge branch 'develop' into smy_branch
5 months ago
plhw57tbe 42884c5b0d ADD file via upload
5 months ago
smy 4201412bb4 在master分支添加doc目录及README.md文档
5 months ago
smy 0ef2d99dbc 删除README.md文件
5 months ago

@ -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'<a href="%s">%s</a>' %
(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'<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,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通过此名称识别该应用

@ -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

@ -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字段
},
),
]
]

@ -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评论默认不显示
),
]
]

@ -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'), # 将显示名称改为英文
),
]

@ -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

@ -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 # 评论深度(用于样式控制)
}

@ -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,

@ -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/<int:article_id>/postcomment',
views.CommentPostView.as_view(),
name='postcomment'),
]
'article/<int:article_id>/postcomment', # URL路径包含文章ID参数整数类型
views.CommentPostView.as_view(), # 关联的视图类使用as_view()方法转换为可调用视图
name='postcomment' # 该URL的名称用于反向解析
),
]

@ -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 = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
Thank you again!
<br />
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
%(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/>
@ -30,9 +47,16 @@ 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,
'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)

@ -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锚点

@ -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"

@ -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"

@ -1,7 +1,13 @@
# 导入Django的admin模块用于管理后台配置
from django.contrib import admin
# 注册模型的地方(当前尚未注册任何模型)
# Register your models here.
# 定义OwnTrackLogs模型的Admin管理类
# 继承自ModelAdmin这是Django admin的基础管理类
class OwnTrackLogsAdmin(admin.ModelAdmin):
# pass表示暂时不添加任何自定义配置
# 此时会使用ModelAdmin的默认配置来展示和管理模型数据
pass

@ -1,31 +1,40 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 由Django 4.1.7在2023年3月2日07:14生成
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
"""数据库迁移类,用于定义数据库结构的变更"""
# 标识这是初始迁移(第一次创建模型)
initial = True
# 依赖的其他迁移文件,初始迁移没有依赖
dependencies = [
]
# 定义要执行的数据库操作列表
operations = [
# 创建一个新的数据模型(数据库表)
migrations.CreateModel(
name='OwnTrackLog',
fields=[
name='OwnTrackLog', # 模型名称,对应数据库中的表名
fields=[ # 模型包含的字段定义
# 自增主键字段BigAutoField会自动生成大整数类型的唯一ID
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
# 用户标识符字段字符串类型最大长度100
('tid', models.CharField(max_length=100, verbose_name='用户')),
# 纬度字段,浮点型
('lat', models.FloatField(verbose_name='纬度')),
# 经度字段,浮点型
('lon', models.FloatField(verbose_name='经度')),
# 创建时间字段,默认值为当前时间
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
],
options={
'verbose_name': 'OwnTrackLogs',
'verbose_name_plural': 'OwnTrackLogs',
'ordering': ['created_time'],
'get_latest_by': 'created_time',
options={ # 模型的额外配置选项
'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称
'verbose_name_plural': 'OwnTrackLogs', # 模型的复数显示名称
'ordering': ['created_time'], # 默认排序方式,按创建时间升序
'get_latest_by': 'created_time', # 指定获取最新记录时使用的字段
},
),
]

@ -1,22 +1,36 @@
# Generated by Django 4.2.5 on 2023-09-06 13:19
# 由Django 4.2.5版本在2023年9月6日13:19自动生成
from django.db import migrations
class Migration(migrations.Migration):
"""
数据库迁移类用于修改现有数据模型的结构和配置
这是一个增量迁移基于之前的迁移进行修改
"""
# 依赖关系:表示此迁移依赖于'owntracks'应用中的0001_initial迁移
# 执行此迁移前必须先执行完依赖的迁移
dependencies = [
('owntracks', '0001_initial'),
]
# 定义要执行的数据库操作列表
operations = [
# 修改模型的配置选项
migrations.AlterModelOptions(
name='owntracklog',
options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'},
name='owntracklog', # 要修改的模型名称
# 新的模型配置选项
options={
'get_latest_by': 'creation_time', # 更新获取最新记录的字段为creation_time
'ordering': ['creation_time'], # 更新默认排序字段为creation_time
'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称(未变)
'verbose_name_plural': 'OwnTrackLogs' # 模型的复数显示名称(未变)
},
),
# 重命名字段
migrations.RenameField(
model_name='owntracklog',
old_name='created_time',
new_name='creation_time',
model_name='owntracklog', # 要操作的模型名称
old_name='created_time', # 原字段名
new_name='creation_time', # 新字段名
),
]

Loading…
Cancel
Save