评论回复ajax使用的进化演变
blog\home\urls.py
# 详情视图路由
path('detail/', DetailView.as_view(), name='detail'),
"\blog\home\views.py"
from home.forms import CommentForm
from django.contrib.contenttypes.models import ContentType
from django.http import JsonResponse
class DetailView(View):
def get(self, request):
"""
1.接收文章id信息
2.根据文章id进行文章数据的查询
3.查询分类信息
4.获取分页请求参数
5.根据文章信息查询评论数据
6.创建分页器
7.进行分页处理
8.组织模板数据
:param request:
:return:
"""
is_mobile = checkMobile(request)
# print(is_mobile)
# write_log(is_mobile)
# 1.接收文章id信息
id = request.GET.get('id')
# 2.根据文章id进行文章数据的查询
try:
article = Article.objects.get(id=id)
content_type = ContentType.objects.get_for_model(article)
previous_blog = Article.objects.filter(created__gt=article.created).last()
next_blog = Article.objects.filter(created__lt=article.created).first()
# print(previous_blog, next_blog)
except Article.DoesNotExist:
return render(request, '404.html')
else:
# 让浏览量+1(如果没有访问cookie就+1,如果读到就不进)
if not request.COOKIES.get("blog_%s_readed" % id):
article.total_views += 1
article.save()
# 3.查询分类信息
categories = ArticleCategory.objects.all()
# # 查询浏览器前10的文章数据(推荐的)
# hot_articles = Article.objects.order_by('-total_views')[:9]
# 4.获取分页请求参数
page_size = request.GET.get('page_size', 10)
page_num = request.GET.get('page_num', 1)
# 5.根据文章信息查询评论数据
comments = Comment.objects.filter(article=article).order_by('-created') # 根据创建时间排序
# 获取评论总数
total_count = comments.count()
# 6.创建分页器
paginator = Paginator(comments, page_size)
try:
pag_commens = paginator.page(page_num)
except EmptyPage:
return HttpResponseNotFound('empty, page')
# 总页数
total_page = paginator.num_pages
# 7.进行分页处理
# 8.组织模板数据
comment_form = CommentForm(initial={'content_type': content_type.model, 'object_id': id}) # 评论使用富文本设定
# print(comment_form)
context = {
'categories': categories,
'category': article.category,
'article': article,
# 'hot_articles': hot_articles,
'total_count': total_count,
'comments': pag_commens,
'page_size': page_size,
'total_page': total_page,
'page_num': page_num,
'comment_form': comment_form,
'is_mobile': is_mobile,
'previous_blog': previous_blog,
'next_blog': next_blog
}
response = render(request, 'detail.html', context=context)
response.set_cookie('blog_%s_readed' % id, 'ture') # 为了浏览量设置
return response
def post(self, request):
comment_form = CommentForm(request.POST, user=request.user)
data = {}
if comment_form.is_valid():
# 检查通过,保存数据
# Comment.objects.create(
# content=comment_form.cleaned_data['content'],
# article=comment_form.cleaned_data['content_object'],
# user=comment_form.cleaned_data['user']
# )
comment = Comment()
comment.content = comment_form.cleaned_data['content']
comment.article = comment_form.cleaned_data['content_object']
comment.user = comment_form.cleaned_data['user']
comment.save()
# 计算评论
comments = Comment.objects.filter(article=comment.article).order_by('-created') # 根据创建时间排序
comment.article.comments_count = comments.count() # 2.4修改文章的评论数量
comment.article.save() # 2.4保存文章的评论数量
# comment.article.save()
# 返回数据
data['status'] = 'SUCCESS'
data['user'] = comment.user.mobile
data['created'] = comment.created.strftime('%Y-%m-%d %H:%M:%S')
data['content'] = comment.content
data['comments_count'] = comment.article.comments_count
# print(comment.user, comment.content)
# return JsonResponse(data)
else:
data['status'] = 'ERROR'
data['message'] = list(comment_form.errors.values())[0][0]
return JsonResponse(data)
"\blog\home\forms.py"
from django import forms
from ckeditor.widgets import CKEditorWidget
from django.contrib.contenttypes.models import ContentType
from django.db.models import ObjectDoesNotExist
class CommentForm(forms.Form):
content_type = forms.CharField(widget=forms.HiddenInput)
object_id = forms.IntegerField(widget=forms.HiddenInput)
content = forms.CharField(widget=CKEditorWidget(config_name='selfmake'),
error_messages={'required': '评论内容不能为空'})
def __init__(self, *args, **kwargs):
if 'user' in kwargs:
self.user = kwargs.pop('user')
super(CommentForm, self).__init__(*args, **kwargs)
def clean(self):
# 判断用户是否登录
if self.user.is_authenticated:
self.cleaned_data['user'] = self.user
else:
raise forms.ValidationError('用户尚未登录')
# 评论对象验证
content_type = self.cleaned_data["content_type"]
object_id = self.cleaned_data['object_id']
try:
model_class = ContentType.objects.get(model=content_type).model_class()
model_obj = model_class.objects.get(pk=object_id)
# 标题
self.cleaned_data['content_object'] = model_obj
except ObjectDoesNotExist:
raise forms.ValidationError("评论信息不存在")
return self.cleaned_data
"\blog\home\models.py"
class Comment(models.Model):
"""
评论内容
评论文章
评论用户
评论时间
"""
# 评论内容
# content = models.TextField()
content = RichTextUploadingField(config_name="selfmake") # 自己定义的CKeditor
# 评论文章 # on_delete=models.CASCADE删除Article文章后可以把评论删除了
article = models.ForeignKey(Article, on_delete=models.CASCADE, null=True)
# 评论用户
user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True)
# 评论时间
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.article.title
class Meta:
db_table = 'tb_comment'
verbose_name = '评论管理'
verbose_name_plural = verbose_name
C:\Users\yys53\OneDrive\python\blog\templates\detail.html
<form id="comment_form" method="POST">
{# 回复时出现的#}
<div id="reply_content_container" style="display: none">
<p id="reply_title">回复: <span id="reply_user"></span></p>
<div id="reply_content"></div>
</div>
{% csrf_token %}
{# <a href="#" onclick="confirm_delete()">删除文章</a>#}
<input type="hidden" name="id" value="{{ article.id }}">
<div class="form-group">
<div>
<div class="django-ckeditor-widget" data-field-id="id_body"
style="display: inline-block;">
{% get_comment_form article as comment_form %}
{% for filed in comment_form %}
{{ filed }}
{% endfor %}
<span id="comment_error" class="text-danger pull-left "></span>
</div>
</div>
</div>
<!-- 提交按钮 如果登录显示第一个发送,没登录显示第二个发送,-->
<div align="right">
<button type="submit" class="btn btn-primary ">发送</button>
</div>
</form>
</div>
<br>
<!-- 显示评论 -->
<h4>共有<span id="addone">{% get_comment_count article %}</span>条评论</h4>
<div id="comment_list">
{#下面引用comment_tags的, as把得到值起个别名 #}
{% for comment in comments %}
<div id="root_{{ comment.id }}" class="comment">
<p><strong style="color: pink"></strong></p>
<div><span><strong
id="replay_user_{{ comment.id }}">{{ comment.user.mobile }}</strong></span> <span
style="color: gray">{{ comment.created | date:'Y-m-d H:i:s' }}</span></div>
<br>
<div id="comment_{{ comment.id }}">
{{ comment.content|safe }}
</div>
<a href="javascript:reply({{ comment.id }});">回复</a>
{# 点赞 #}
<div class="like"
onclick="likeChange(this, '{% get_content_type comment %}', {{ comment.id }})">
<span class="fa fa-thumbs-o-up {% get_like_status comment %}"></span>
<span class="liked-num">{% get_like_count comment %}</span>
</div>
</div>
{% endfor %}
\blog\templates\detail.html
<script type="text/javascript">
$("#comment_form").submit(function(){
// 判断是否为空
{% if user.is_authenticated %}
$("#comment_error").text('');
if(CKEDITOR.instances["id_content"].document.getBody().getText().trim()==''){
$("#comment_error").text('评论内容不能为空');
return false;
}
{% else %}
//点发送时没有登录,提示登录
$("#comment_error").text('请登录后评论');
//跳出登录弹窗
$('#login_modal').modal('show');
$('#login_model_tips').text('请登录后评论');
return false;
{% endif %}
// 更新数据到textarea
CKEDITOR.instances['id_content'].updateElement();
// 异步提交
$.ajax({
url: '{% url 'home:detail' %}?id={{ article.id }}',
type: 'POST',
data: $(this).serialize(),
cache: false,
success: function(data){
console.log(data);
if(data['status']=="SUCCESS"){
// 插入数据
if($('#reply_comment_id').val()=='0'){
//如果id等于0插入评论
var comment_html =
'<div id ="root_{0}" class="comment">'+
'<p><strong style="color: pink"></strong></p>'+
'<div><span><strong id="replay_user_{0}">{1}</strong></span> '+
'<span style="color: gray">'+
'{2}</span></div><br> <p><div id="comment_{0}">'+
'{3}</div></p> <a href="javascript:reply({0});">回复 </a>'+
{# 点赞 #}
'<div class="like" onclick="likeChange(this, \'{4}\', {0})">'+
'<span class="fa fa-thumbs-o-up"></span>'+
'<span class="liked-num">0</span></div>';
comment_html = comment_html.format(data['id'],data['user'],data['created'],data['content'], data['content_type']);
$("#comment_list").prepend(comment_html);
}
else
{
//插入回复
var reply_html =
'<div class="reply">'+
'<span><strong id="replay_user_{0}">{1}</strong></span>'+
' <span>{2}</span>:'+
' <span> 回复 </span> '+
'<span ><strong id="replay_user_{0}">{3}</strong></span>'+
'<p><div id="comment_{0}"> <span>{4}</span> </div></p>'+
'<a href="javascript:reply({0});">回复 </a>'+
{# 点赞 #}
'<div class="like" onclick="likeChange(this, \'{5}\', {0})">'+
'<span class="fa fa-thumbs-o-up"></span>'+
'<span class="liked-num">0</span></div></div>';
reply_html = reply_html.format(data['id'],data['user'],data['created'],data['reply_to'],data['content'],data['content_type']);
$("#root_" + data['root_id']).append(reply_html);
}
// 清空编辑框的内容
CKEDITOR.instances['id_content'].setData('');
$('#reply_content_container').hide();
$('#reply_comment_id').val('0');
//评论后提示评论成功
$("#comment_error").text('评论成功');
//评论数量
i++;
$("#addone").html({% get_comment_count article %}+i);
{#$("#addone").html(data['comments_count']);#}
}else{
// 显示错误信息
$("#comment_error").text(data['message']);
}
},
error: function(xhr){
console.log(xhr);
}
});
return false;
});
</script>
对于上面的content_type
\blog\home\views.py
data['content_type'] = comment.article._meta.model_name
blog\home\urls.py
# 详情视图路由
path('detail/', DetailView.as_view(), name='detail'),
class DetailView(View):
def post(self, request):
# 回复
if parent:
data['reply_to'] = comment.reply_to.mobile
else:
data['reply_to'] = ''
data['id'] = comment.id
data['root_id'] = comment.root.id if comment.root else ''
print("#root_%s" % data['root_id'])
\blog\home\forms.py
from django import forms
from ckeditor.widgets import CKEditorWidget
from django.contrib.contenttypes.models import ContentType
from django.db.models import ObjectDoesNotExist
from .models import Comment
def clean_reply_comment_id(self):
reply_comment_id = self.cleaned_data['reply_comment_id']
if reply_comment_id < 0:
raise forms.ValidationError('回复出错')
elif reply_comment_id == 0:
self.cleaned_data['parent'] = None
elif Comment.objects.filter(id=reply_comment_id).exists():
self.cleaned_data['parent'] = Comment.objects.get(id=reply_comment_id)
else:
raise forms.ValidationError('回复出错')
return reply_comment_id
\blog\home\models.py
class Comment(models.Model):
"""
评论内容
评论文章
评论用户
评论时间
"""
# 评论内容
# content = models.TextField()
content = RichTextUploadingField(config_name="selfmake") # 自己定义的CKeditor
# 评论文章 # on_delete=models.CASCADE删除Article文章后可以把评论删除了
article = models.ForeignKey(Article, on_delete=models.CASCADE, null=True)
# 评论用户,related_name防止冲突,名字自定义的
user = models.ForeignKey('users.User', related_name="comments", on_delete=models.SET_NULL, null=True)
# 评论时间
created = models.DateTimeField(auto_now_add=True)
# 记录每一条回复从哪里开始的,related_name防止冲突,名字自定义的
root = models.ForeignKey('self', related_name='root_comment', null=True, on_delete=models.DO_NOTHING)
# 上一级
parent = models.ForeignKey('self', related_name='parent_comment', null=True, on_delete=models.DO_NOTHING)
# 回复谁的
reply_to = models.ForeignKey(User, related_name="replies", null=True, on_delete=models.DO_NOTHING)
def __str__(self):
return self.article.title
class Meta:
db_table = 'tb_comment'
verbose_name = '评论管理'
verbose_name_plural = verbose_name
ordering = ['created'] # 让回复内容正序,从上往下
\blog\templates\detail.html
{% for reply in comment.root_comment.all %}
<div class="reply">
<span><strong
id="replay_user_{{ reply.id }}">{{ reply.user.mobile }}</strong></span>
<span>{{ reply.created | date:'Y-m-d H:i:s' }}</span>:
<span>回复</span>
<span><strong
id="replay_user_{{ reply.id }}">{{ reply.reply_to.mobile }}</strong></span>
<div id="comment_{{ reply.id }}">
<span>{{ reply.content|safe }}</span>
</div>
<a href="javascript:reply({{ reply.id }});">回复</a>
{# 点赞 #}
<div class="like"
onclick="likeChange(this, '{% get_content_type reply %}', {{ reply.id }})">
<span class="fa fa-thumbs-o-up {% get_like_status reply %}"></span>
<span class="liked-num">{% get_like_count reply %}</span>
</div>
</div>
{% endfor %}
{#回复#}
<script type="text/javascript">
function reply(reply_comment_id) {
// 设置值
$('#reply_comment_id').val(reply_comment_id);
{#显示被回复用户,htm是变量一般不能相同同#}
var htm = $("#replay_user_" + reply_comment_id).html();
$('#reply_user').html(htm);
{#显示被回复着的内容#}
var html = $("#comment_" + reply_comment_id).html();
$('#reply_content').html(html);
{#显示回复框#}
$('#reply_content_container').show();
//点回复时,如果登录跳转到CKeditor,如果没登录跳到登录
{% if user.is_authenticated %}
{#跳到CKeditor,让滚动条滚动,偏移60, 滚动时间300毫秒#}
$('html').animate({scrollTop: $('#comment_form').offset().top - 60}, 300, function () {
CKEDITOR.instances['id_content'].focus();
});
{% else %}
//跳出登录弹窗
$("#comment_error").text('请登录后回复');
$('#login_modal').modal('show');
$('#login_model_tips').text('请登录后回复');
{% endif %}
}
</script>
回复结构图
补充:jQuery HTML节点元素修改、追加的方法 html()、append()、prepend()、
我们先拟定一个代码场景
<div>start</div>
<p>123</p>
<div>end</div>
html() 操作节点中的内容,一般我们可以用来快速给一个容器中赋值。
$("p").html("abc");
最终看到的结果
<div>start</div>
<p>abc</p>
<div>end</div>
append() 追加到节点中的末尾处
$("p").append("abc");
最终看到的结果
div>start</div>
<p>123abc</p>
<div>end</div>
prepend() 追加到节点中的起始处
$("p").prepend("abc");
最终看到的结果
<div></div>
<p>abc123</p>
<div>end</div>
alert() 追加到节点的后面
$("p").prepend("<p>abc</p>");
最终看到的结果
<div>start</div>
<p>123</p>
<p>abc</p>
<div>end</div>
before() 追加到节点的前面
$("p").prepend("<p>abc</p>");
最终看到的结果
<div>start</div>
<p>abc</p>
<p>123</p>
<div>end</div>
\blog\templates\include\selfcss.html
{#透明色#}
<style type="text/css" rel="stylesheet">
*{
padding: 0px;
margin:0px;
}
.mainbox{
width: 200px;
height: 200px;
clear: both;
overflow: hidden;
margin: 100px auto 0px auto;
background-color: #f06;
}
.sub-mainbox
{
{#width: 250px;#}
{#height: 200px;#}
margin: -50px auto 0px auto;
border:1px solid white;
border-radius: 5px;
background-color:rgba(0,152,50,0.7);
{#background:url(path/my_bg.jpg) no-repeat center center scroll;#}
{#opacity: 0.7;#}
{#filter:alpha(opacity=70);#}
}
{#我的名片样式#}
div.blog-more{
margin-top: 1em;
}
.about { background: #FFF url('/static/banner.png') no-repeat top center; overflow: hidden; }
.avatar { margin: 80px auto 20px; width: 100px }
.avatar img { width: 100px; border-radius: 50% }
.abname { color: #3f3f3f; font-weight: bold; font-size: 16px; margin-bottom: 10px; text-align: center }
.abposition { color: #1abc9c; text-align: center }
.abtext { padding: 20px 40px; color: #9a9a9a; line-height: 26px }
{#去掉li的点#}
li {list-style-type:none;}
{# CKeditor 评论模块 #}
div.django-ckeditor-widget{
width: 100%;
}
div.comment {
border-bottom: 1px dashed #ccc;
margin-bottom: 0.5em;
padding-bottom: 0.5em;
}
div.reply {
margin-left: 2em;
}
</style>
遇到困难
如果
python manage.py makemigrations
遇到下面情况
修改seetings.py
\blog\blog\settings.py
STATIC_ROOT = os.path.join(BASE_DIR, "/static/")
# STATIC_ROOT = os.path.join(BASE_DIR, "/home/blog/static/") # centos7用python manage.py collectstatic时候改的路径
首先在home新建一个名字templatetags的Pathon Package(名字不能错)
新建一个comment_tags.py来写自定义模板
\blog\home\templatetags\comment_tags.py
from django import template
from home.models import Comment
from ..forms import CommentForm
# 自定义模板标签
# 注册变成模板标签
register = template.Library()
@register.simple_tag
def get_comment_count(obj):
# obj文章名字
total_count = Comment.objects.filter(article=obj).order_by('-created').count()
return total_count
@register.simple_tag
def get_comment_form(obj):
# 获取该类对应表名(字符串类型)
form = CommentForm(
initial={'content_type': obj._meta.model_name, 'object_id': obj.id, 'reply_comment_id': '0'}) # 评论使用富文本设定
return form
上面_meta方法说明
django--_meta方法
model.UserInfo._meta.app_label
#获取该类所在app的app名称
model.UserInfo._meta.model_name
#获取该类对应表名(字符串类型)
model.UserInfo._meta.get_field('username')
#获取该类内指定字段信息(对象)
model.UserInfo._meta.fields
#获取该类内所有字段对象
model.UserInfo._meta.get_fields
#获取该类内所有字段信息(对象),包含反向关联的字段
model.UserInfo._meta.many_to_many
#获取该类内多对多字段信息
model.UserInfo._meta.get_field('username').verbose_name
#获取该类内‘username’字段,verbose_name 的值
obj = models.UserInfo.objects.create(...)
#源码位置
#from django.db.models.options import Options
#from django.db.models.fields.reverse_related import ManyToOneRel
field = obj._meta.related_objects[0]
#拿到当前记录对象所对应的反向关联字段的queryset
print(field[0].limit_choices_to)
#拿到对应的limit_choices_to的字典的数据
print(field[0].related_name)
#拿到related_name属性所对应的值
print(field[0].field_name)
#拿到反向关联字段里的关联本表的字段
print(field[0].field.model._meta.model_name)
#拿到反向关联字段所在类名称
在detail.html可以直接这样引用
\blog\templates\detail.html
{% load comment_tags %}
{#下面引用comment_tags的, as把得到值起个别名 #}
{% get_comment_form article as comment_form %}
{% for filed in comment_form %}
{{ filed }}
{% endfor %}
<!-- 显示评论 -->
<h4>共有<span id="addone">{% get_comment_count article %}</span>条评论</h4>
首页显示评论总数indedx.html
\blog\templates\index.html
{% load comment_tags %}
<span><i class="fas fa-comments" style="color: yellowgreen;"></i>{% get_comment_count article %} </span>
回复的css样式selfcss.html
\blog\templates\include\selfcss.html
div#reply_content_container{
border: 1px solid #acacac;
border-bottom: none;
background-color: #e5e6e5;
overflow: hidden;
padding: 1em 1em 0.5em;
}
p#reply_title{
border-bottom: 1px dashed #ccc;
padding-bottom: 1em;
}
回到顶部
\blog\templates\include\scroll_top.html
<div id="return-top" class="top_e ">
<img src="/static/toTop.png " width="60" id="img" >
<div style="width:60px;margin:auto;">
<p id='font' style="font-size:20px;margin-left:10px;display:none;">返回<br>顶部</p>
</div>
</div>
<style type="text/css">
.top_e{
position:fixed;right:10px;bottom:40px;
background:rgba(204,200,255,0.2);
border-radius:15px;
cursor:pointer;
display:none;
width:60px;
height:60px;
}
.top_e:hover
{
color:white;
background:#1296DB;
}
</style>
<script type="text/javascript">
// 控制按钮的显示和消失
$(window).scroll(function(){
if($(window).scrollTop()>300){
$('#return-top').fadeIn(300);
}
else{$('#return-top').fadeOut(200);}
})
//当在滑动事件中想执行某个事件,但是滑动事件总是触发事件,但是会有这样的需求,停止后执行,那么下面代码就会可以实现
$(window).scroll(function() {
clearTimeout($.data(this, 'scrollTimer'));
$.data(this, 'scrollTimer', setTimeout(function() {
//执行的事件图标隐藏
$("#return-top").hide(1000);
//停止后多少毫秒执行,如3000是3秒
}, 3000));
});
// 点击按钮,使得页面返回顶部
$("#return-top").click(function(){
scrollTo(0,0);
});
// 鼠标悬浮按钮之上,图片消失,文字显示
$(".top_e").mouseover(function(){
$("#img").hide();
$("#font").show();
})
//鼠标离开,文字消失,图片显示。
$(".top_e").mouseout(function(){
$("#font").hide();
$("#img").show();
})
</script>
评论列表 (0 条评论)