添加第9-10周作业

YZX_branch
杨智鑫2315304452 3 months ago
parent f96378f246
commit 2e1a3bbbb6

@ -1,7 +1,9 @@
# admin.py
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 comments.models import Comment, CommentLike, CommentReport
def disable_commentstatus(modeladmin, request, queryset):
@ -45,3 +47,48 @@ class CommentAdmin(admin.ModelAdmin):
link_to_userinfo.short_description = _('User') # 杨智鑫:用户
link_to_article.short_description = _('Article') # 杨智鑫:文章
class CommentLikeAdmin(admin.ModelAdmin):
"""评论点赞管理界面"""
list_display = ('id', 'user', 'comment', 'created_time')
list_filter = ('created_time',)
search_fields = ('user__username', 'comment__body')
readonly_fields = ('created_time',)
list_per_page = 20
def get_queryset(self, request):
return super().get_queryset(request).select_related('user', 'comment')
class CommentReportAdmin(admin.ModelAdmin):
"""评论举报管理界面"""
list_display = ('id', 'user', 'comment', 'reason', 'is_handled', 'created_time')
list_filter = ('reason', 'is_handled', 'created_time')
search_fields = ('user__username', 'comment__body', 'description')
readonly_fields = ('created_time',)
list_per_page = 20
actions = ['mark_as_handled', 'mark_as_pending']
def mark_as_handled(self, request, queryset):
"""标记为已处理"""
updated_count = queryset.update(is_handled=True)
self.message_user(request, f'成功标记 {updated_count} 个举报为已处理')
mark_as_handled.short_description = '标记选中的举报为已处理'
def mark_as_pending(self, request, queryset):
"""标记为待处理"""
updated_count = queryset.update(is_handled=False)
self.message_user(request, f'成功标记 {updated_count} 个举报为待处理')
mark_as_pending.short_description = '标记选中的举报为待处理'
def get_queryset(self, request):
return super().get_queryset(request).select_related('user', 'comment')
# 注册新模型到admin
admin.site.register(CommentLike, CommentLikeAdmin)
admin.site.register(CommentReport, CommentReportAdmin)

@ -2,4 +2,4 @@ from django.apps import AppConfig
class CommentsConfig(AppConfig):
name = 'comments' # 杨智鑫:应用名称
name = 'comments' # 杨智鑫:应用名称

@ -1,7 +1,8 @@
# forms.py
from django import forms
from django.forms import ModelForm
from .models import Comment
from .models import Comment,CommentReport
class CommentForm(ModelForm):
@ -11,3 +12,24 @@ class CommentForm(ModelForm):
class Meta:
model = Comment # 杨智鑫:指定表单关联的模型
fields = ['body'] # 杨智鑫:表单只包含评论内容字段
# 新增评论举报表单
class CommentReportForm(forms.ModelForm):
"""评论举报表单"""
class Meta:
model = CommentReport
fields = ['reason', 'description']
widgets = {
'reason': forms.Select(attrs={
'class': 'form-control',
'placeholder': '请选择举报原因'
}),
'description': forms.Textarea(attrs={
'rows': 3,
'class': 'form-control',
'placeholder': '请详细描述举报原因(可选)'
})
}
labels = {
'reason': '举报原因',
'description': '详细说明'
}

@ -1,3 +1,4 @@
# models.py
from django.conf import settings
from django.db import models
from django.utils.timezone import now
@ -37,3 +38,63 @@ class Comment(models.Model):
def __str__(self):
return self.body # 杨智鑫:返回评论内容
# 在 Comment 类后面添加以下代码
class CommentLike(models.Model):
"""评论点赞模型"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('user'),
on_delete=models.CASCADE
)
comment = models.ForeignKey(
Comment,
verbose_name=_('comment'),
on_delete=models.CASCADE
)
created_time = models.DateTimeField(_('creation time'), default=now)
class Meta:
unique_together = ('user', 'comment') # 防止重复点赞
verbose_name = _('comment like')
verbose_name_plural = _('comment likes')
ordering = ['-created_time']
def __str__(self):
return f"{self.user.username} likes {self.comment.id}"
class CommentReport(models.Model):
"""评论举报模型"""
REPORT_REASONS = [
('spam', '垃圾广告'),
('abuse', '辱骂攻击'),
('illegal', '违法信息'),
('other', '其他原因')
]
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('user'),
on_delete=models.CASCADE
)
comment = models.ForeignKey(
Comment,
verbose_name=_('comment'),
on_delete=models.CASCADE
)
reason = models.CharField(_('report reason'), max_length=20, choices=REPORT_REASONS)
description = models.TextField(_('description'), max_length=500, blank=True)
created_time = models.DateTimeField(_('creation time'), default=now)
is_handled = models.BooleanField(_('is handled'), default=False)
class Meta:
unique_together = ('user', 'comment') # 防止重复举报
verbose_name = _('comment report')
verbose_name_plural = _('comment reports')
ordering = ['-created_time']
def __str__(self):
return f"Report {self.comment.id} by {self.user.username}"

@ -1,3 +1,4 @@
# tests.py
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
@ -106,4 +107,4 @@ class CommentsTest(TransactionTestCase):
self.assertIsNotNone(s) # 杨智鑫:判断数据是否为空
from comments.utils import send_comment_email
send_comment_email(comment) # 杨智鑫:发送邮件
send_comment_email(comment) # 杨智鑫:发送邮件

@ -1,11 +1,24 @@
from django.urls import path
from . import views
app_name = "comments" # 杨智鑫:定义应用命名空间
urlpatterns = [
path(
'article/<int:article_id>/postcomment',
views.CommentPostView.as_view(), # 杨智鑫:定义路由
name='postcomment'), # 杨智鑫:定义路由名称
]
name='postcomment' # 杨智鑫:定义路由名称
),
# 杨智鑫:添加点赞评论路由
path(
'comment/<int:comment_id>/like/', # 修改路径添加comment前缀
views.like_comment, # 杨智鑫:点赞视图
name='like_comment' # 杨智鑫:路由名称
),
# 杨智鑫:添加举报评论路由
path(
'comment/<int:comment_id>/report/', # 修改路径添加comment前缀
views.report_comment, # 杨智鑫:举报视图
name='report_comment' # 杨智鑫:路由名称
),
]

@ -1,3 +1,4 @@
# utils.py
import logging
from django.utils.translation import gettext_lazy as _

@ -1,20 +1,23 @@
# Create your views here.
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, JsonResponse
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.decorators.csrf import csrf_protect, csrf_exempt
from django.views.decorators.http import require_http_methods
from django.views.generic.edit import FormView
from django.contrib.auth.decorators import login_required
import json
from accounts.models import BlogUser
from blog.models import Article
from .forms import CommentForm
from .models import Comment
from .models import Comment, CommentLike, CommentReport
class CommentPostView(FormView):
form_class = CommentForm # 杨智鑫:指定使用的表单类
template_name = 'blog/article_detail.html' # 杨智鑫:指定使用的模板
template_name = 'blog/article_detail.html' # 杨智鑫:指定使用的模板
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs): # 杨智鑫添加csrf_protect装饰器
@ -43,21 +46,116 @@ class CommentPostView(FormView):
article = get_object_or_404(Article, pk=article_id) # 杨智鑫:获取文章对象
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.") # 杨智鑫:抛出异常
comment = form.save(False) # 杨智鑫:保存评论
raise ValidationError("该文章评论已关闭.") # 杨智鑫:抛出异常
comment = form.save(False) # 杨智鑫:保存评论
comment.article = article # 杨智鑫:设置评论所属文章
from djangoblog.utils import get_blog_setting
settings = 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']) # 杨智鑫:获取父级评论对象
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)) # 杨智鑫:跳转到评论区
# 杨智鑫:点赞评论视图
@login_required
@require_http_methods(["POST"])
@csrf_exempt
def like_comment(request, comment_id):
"""点赞/取消点赞评论"""
try:
comment = Comment.objects.get(pk=comment_id)
user = request.user
# 检查是否已经点赞
like, created = CommentLike.objects.get_or_create(
user=user,
comment=comment
)
if not created:
# 如果已经点赞,则取消点赞(删除记录)
like.delete()
liked = False
message = "取消点赞成功"
else:
# 新点赞
liked = True
message = "点赞成功"
# 获取当前点赞数
like_count = CommentLike.objects.filter(comment=comment).count()
return JsonResponse({
'success': True,
'liked': liked,
'like_count': like_count,
'message': message
})
except Comment.DoesNotExist:
return JsonResponse({
'success': False,
'message': '评论不存在'
}, status=404)
except Exception as e:
return JsonResponse({
'success': False,
'message': f'操作失败: {str(e)}'
}, status=500)
# 杨智鑫:举报评论视图
@login_required
@require_http_methods(["POST"])
@csrf_exempt
def report_comment(request, comment_id):
"""举报评论"""
try:
comment = Comment.objects.get(pk=comment_id)
user = request.user
# 解析请求数据
data = json.loads(request.body)
reason = data.get('reason')
description = data.get('description', '')
# 检查是否已经举报过
if CommentReport.objects.filter(user=user, comment=comment).exists():
return JsonResponse({
'success': False,
'message': '您已经举报过该评论'
})
# 创建举报记录
report = CommentReport.objects.create(
user=user,
comment=comment,
reason=reason,
description=description
)
return JsonResponse({
'success': True,
'message': '举报成功,我们会尽快处理'
})
except Comment.DoesNotExist:
return JsonResponse({
'success': False,
'message': '评论不存在'
}, status=404)
except Exception as e:
return JsonResponse({
'success': False,
'message': f'举报失败: {str(e)}'
}, status=500)

@ -18,7 +18,6 @@
class="url">{{ comment_item.author.username }}
</a>
</cite>
</div>
<div class="comment-meta commentmetadata">
@ -34,12 +33,37 @@
<p>{{ comment_item.body|escape|comment_markdown }}</p>
<div class="reply"><a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)" data-pk="{{ comment_item.pk }}"
aria-label="回复给{{ comment_item.author.username }}">回复</a></div>
</div>
<!-- 杨智鑫:点赞和举报按钮 -->
<div class="comment-actions">
<!-- 点赞按钮 -->
<span class="like-action">
<button class="btn-like" data-comment-id="{{ comment_item.pk }}"
title="点赞评论">
<!-- 使用Font Awesome的点赞图标确保class正确 -->
<i class="fa fa-thumbs-up"></i>
<!-- 显示点赞数量 -->
<span class="like-count">{{ comment_item.commentlike_set.count }}</span>
</button>
</span>
<!-- 举报按钮 -->
<span class="report-action">
<button class="btn-report" data-comment-id="{{ comment_item.pk }}"
title="举报评论">
<!-- 使用Font Awesome的旗帜图标确保class正确 -->
<i class="fa fa-flag"></i>
</button>
</span>
</li><!-- #comment-## -->
<!-- 原有的回复链接 -->
<span class="reply">
<a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)" data-pk="{{ comment_item.pk }}"
aria-label="回复给{{ comment_item.author.username }}">回复</a>
</span>
</div>
</div>
</li>
{% query article_comments parent_comment=comment_item as cc_comments %}
{% for cc in cc_comments %}
{% with comment_item=cc template_name="comments/tags/comment_item_tree.html" %}

@ -1,4 +1,7 @@
<dev>
<!-- 在文件开头添加Font Awesome和CSS样式 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<div>
<section id="comments" class="themeform">
{% load blog_tags %}
{% load comments_tags %}
@ -7,39 +10,317 @@
<ul class="comment-tabs group">
<li class="active"><a href="#commentlist-container"><i
class="fa fa-comments-o"></i>评论<span>{{ comment_count }}</span></a></li>
</ul>
{% if article_comments %}
<div id="commentlist-container" class="comment-tab" style="display: block;">
<ol class="commentlist">
{# {% query article_comments parent_comment=None as parent_comments %}#}
{% for comment_item in p_comments %}
{# 杨智鑫:确保这里使用正确的变量名 #}
{% for comment_item in article_comments %}
{% with 0 as depth %}
{% include "comments/tags/comment_item_tree.html" %}
{% endwith %}
{% endfor %}
</ol><!--/.commentlist-->
<div class="navigation">
<nav class="nav-single">
{% if comment_prev_page_url %}
<div class="nav-previous">
<span><a href="{{ comment_prev_page_url }}" rel="prev"><span
class="meta-nav">←</span> 上一页</a></span>
<span><a href="{{ comment_prev_page_url }}" rel="prev"><span
class="meta-nav">←</span> 上一页</a></span>
</div>
{% endif %}
{% if comment_next_page_url %}
<div class="nav-next">
<span><a href="{{ comment_next_page_url }}" rel="next">下一页 <span
class="meta-nav">→</span></a></span>
<span><a href="{{ comment_prev_page_url }}" rel="next">下一页 <span
class="meta-nav">→</span></a></span>
</div>
{% endif %}
</nav>
</div>
<br/>
</div>
{% else %}
<div id="commentlist-container" class="comment-tab" style="display: block;">
<p>暂无评论</p>
</div>
{% endif %}
</section>
</div>
<!-- 杨智鑫添加点赞和举报功能的CSS样式 -->
<style>
/* 评论操作按钮样式 */
.comment-actions {
margin-top: 10px;
display: flex;
gap: 15px;
align-items: center;
}
.like-action, .report-action, .reply {
display: inline-block;
}
.btn-like, .btn-report {
background: none;
border: 1px solid #ddd;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
color: #666;
transition: all 0.3s ease;
}
.btn-like:hover {
background: #e8f5e8;
border-color: #4CAF50;
color: #4CAF50;
}
.btn-report:hover {
background: #ffe8e8;
border-color: #f44336;
color: #f44336;
}
.btn-like.liked {
background: #4CAF50;
color: white;
border-color: #4CAF50;
}
.btn-like .like-count {
margin-left: 4px;
font-weight: bold;
}
/* 举报模态框样式 */
.report-modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.report-modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border-radius: 8px;
width: 400px;
max-width: 90%;
}
.report-reason-options {
margin: 15px 0;
}
.report-reason-options label {
display: block;
margin: 8px 0;
cursor: pointer;
}
.report-description {
width: 100%;
height: 80px;
margin: 10px 0;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
}
.report-actions {
text-align: right;
margin-top: 15px;
}
.btn-cancel, .btn-submit-report {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-left: 10px;
}
.btn-cancel {
background: #f5f5f5;
color: #333;
}
.btn-submit-report {
background: #f44336;
color: white;
}
</style>
<!-- 杨智鑫添加点赞和举报功能的JavaScript -->
<script>
// 杨智鑫:评论点赞和举报功能
document.addEventListener('DOMContentLoaded', function() {
// 点赞功能
document.querySelectorAll('.btn-like').forEach(button => {
button.addEventListener('click', function() {
const commentId = this.dataset.commentId;
likeComment(commentId, this);
});
});
// 举报功能
document.querySelectorAll('.btn-report').forEach(button => {
button.addEventListener('click', function() {
const commentId = this.dataset.commentId;
showReportModal(commentId);
});
});
});
// 点赞评论 - 修改URL
function likeComment(commentId, button) {
fetch(`/comment/${commentId}/like/`, { // 修改URL
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const likeCount = button.querySelector('.like-count');
likeCount.textContent = data.like_count;
if (data.liked) {
button.classList.add('liked');
} else {
button.classList.remove('liked');
}
// 显示成功消息
showMessage(data.message, 'success');
} else {
showMessage(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showMessage('操作失败,请稍后重试', 'error');
});
}
// 显示举报模态框
function showReportModal(commentId) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'report-modal';
modal.style.display = 'block';
modal.innerHTML = `
<div class="report-modal-content">
<h3>举报评论</h3>
<p>请选择举报原因:</p>
<div class="report-reason-options">
<label>
<input type="radio" name="report_reason" value="spam" required>
垃圾广告
</label>
<label>
<input type="radio" name="report_reason" value="abuse" required>
辱骂攻击
</label>
<label>
<input type="radio" name="report_reason" value="illegal" required>
违法信息
</label>
<label>
<input type="radio" name="report_reason" value="other" required>
其他原因
</label>
</div>
<textarea class="report-description" placeholder="请简要描述举报原因(可选)"></textarea>
<div class="report-actions">
<button class="btn-cancel" onclick="closeReportModal()">取消</button>
<button class="btn-submit-report" onclick="submitReport(${commentId})">提交举报</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 点击模态框外部关闭
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeReportModal();
}
});
}
// 关闭举报模态框
function closeReportModal() {
const modal = document.querySelector('.report-modal');
if (modal) {
modal.remove();
}
}
// 提交举报 - 修改URL
function submitReport(commentId) {
const reason = document.querySelector('input[name="report_reason"]:checked');
const description = document.querySelector('.report-description').value;
if (!reason) {
showMessage('请选择举报原因', 'error');
return;
}
fetch(`/comment/${commentId}/report/`, { // 修改URL
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
reason: reason.value,
description: description
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showMessage(data.message, 'success');
closeReportModal();
} else {
showMessage(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showMessage('举报失败,请稍后重试', 'error');
});
}
// 获取CSRF Token
function getCsrfToken() {
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]');
return csrfToken ? csrfToken.value : '';
}
</dev>
// 显示消息
function showMessage(message, type) {
// 这里可以使用你项目中已有的消息提示组件
// 如果没有可以简单使用alert或创建临时提示
if (type === 'error') {
alert('错误: ' + message);
} else {
alert(message);
}
}
</script>
Loading…
Cancel
Save