1,ajax评论

2.ajax回复

3.自定义模板标签评论统计

1,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>&nbsp;<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>&nbsp;'+
                                '<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>'+
                                        '&nbsp;<span>{2}</span>:'+
                                        '&nbsp;<span> 回复 </span>&nbsp;'+
                                        '<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

 

2.ajax回复

    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>
                                    &nbsp;<span>{{ reply.created | date:'Y-m-d H:i:s' }}</span>:
                                    &nbsp;<span>回复</span>&nbsp;
                                    <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时候改的路径

3.自定义模板标签评论统计

首先在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 %}&nbsp;&nbsp;&nbsp;</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>