文章详情页文章评论功能

摘要:
两者之间的区别在于是否有父注释。由于textarea是一个内联标签,所以按钮标签直接连接在下面。文本框和按钮将显示在同一行。类似地,按钮将被一层p标签包裹,这样按钮将显示在文本框下方。

一、文章评论功能实现流程  

  文章评论包含两种评论,根评论:对文章的评论;子评论:对评论的评论。两者的区别在于是否存在父评论。

  实现流程:1、构建样式;2、提交根评论;3、显示根评论(分为render显示和Ajax显示);4、提交子评论;5、显示子评论(分为render显示和Ajax显示);6、评论树显示(博客园是楼层显示)。

二、构建评论样式

1、article_detail.html:

{# 文章点赞,清除浮动 #}
<div class="clearfix">
    <div id="div_digg">
    {# 推荐 #}
    <div class="diggit action">
        <span   id="digg_count">{{ article_obj.up_count }}</span>
    </div>
    {# 点灭 #}
    <div class="buryit action">
        <span   id="bury_count">{{ article_obj.down_count }}</span>
    </div>
    <div class="clear"></div>
    <div     style="color: red;"></div>
</div>
</div>

<div class="comments">
    <p>发表评论</p>
    <p>昵称: <input type="text"     disabled="disabled" size="50" value="{{ request.user.username }}"></p>
    <p>评论内容</p>
    <textarea name="" id="" cols="60" rows="10"></textarea> {# textarea是一个内联标签 #}
    <p><button class="btn btn-default comment_btn">提交评论</button></p>
</div>

  由于id=div_digg中存在浮动,显示时会造成点赞区域和评论区域在同一行,在点赞区外包了一层div标签,添加bootstrap的clearfix清除浮动。

  由于textarea是一个内联标签,下面直接接button标签,文本框和按钮会显示在同一行,同样给按钮包一层p标签,让按钮在文本框下方显示。

2、article_detail.css评论区样式调整

/* 评论 */
input.author {
    background-image: url("/static/font/icon_form.gif");
    background-repeat: no-repeat;
    border: 1px solid #ccc;
    padding: 4px 4px 4px 30px;
     300px;
    font-size: 13px;
    background-position: 3px -3px;
}

3、显示效果如下所示:

  文章详情页文章评论功能第1张

三、提交根评论

1、创建评论路由

urlpatterns = [
    ...
    path('digg/', views.digg),  # 点赞
    path('comment/', views.comment),  # 评论
    ...
]

2、创建评论视图函数comment

def comment(request):
    print(request.POST)

    article_id = request.POST.get("article_id")
    pid = request.POST.get("pid")
    content = request.POST.get("content")
    user_id = request.user.pk

    # 在数据库生成一条评论对象  父评论为空是根评论,不为空则是子评论
    comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)

    return HttpResponse("comment")

3、在article_detail.html模板中创建评论事件

<script>
    // 点赞请求
    $('#div_digg .action').click(function () {
    ...

    // 评论请求
    $(".comment_btn").click(function () {
        var content = $('#comment_content').val();    // 拿到评论框的内容
        var pid = "";   // 父评论默认为空

        $.ajax({
            url: "/comment/",
            type: "post",
            data: {
                'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
                'article_id': "{{ article_obj.pk }}",
                'content': content,
                'pid': pid,
            },
            success:function (data) {
                console.log(data);

                // 提交后清空评论框
                $("#comment_content").val("");
            }
        })
    })
</script>

4、提交评论后,在数据库blog_comment表中可以看到评论记录

  文章详情页文章评论功能第2张

四、显示根评论

1、render显示根评论

(1)要render显示评论,需要修改article_detail视图函数

def article_detail(request, username, article_id):

    user = UserInfo.objects.filter(username=username).first()
    blog = user.blog

    article_obj = models.Article.objects.filter(pk=article_id).first()

    comment_list = models.Comment.objects.filter(article_id=article_id)

    return render(request, "article_detail.html", locals())

  传递comment_list到模板中,根据过滤条件获取的是当前文章的评论。

(2)在article_detail.html中构建评论列表

{# 文章评论列表 #}
<div class="comments">
    <p>评论列表</p>
    <ul class="list-group comment_list">
        {% for comment in comment_list %}
            <li class="list-group-item">
                <div>
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"># {{ forloop.counter }}楼</a>    
                    <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>    
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><{{ comment.user.username }}/span></a>    
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html" class="pull-right">回复</a>
                </div>
                <div class="comment_con">
                    {# 评论内容 #}
                    <p>{{ comment.content }}</p>
                </div>
            </li>
        {% endfor %}
    </ul>

    <p>发表评论</p>
    <p>昵称: <input type="text"     disabled="disabled" size="50" value="{{ request.user.username }}"></p>
    <p>评论内容</p>
    <textarea name=""   cols="60" rows="10"></textarea> {# textarea是一个内联标签 #}
    <p><button class="btn btn-default comment_btn">提交评论</button></p>
</div>

  1)利用bootstrap的列表组组件来构建文章列表样式。

  2)修饰评论内容样式article_detail.css:

.comment_con {
    margin-top: 10px;
}

(3)显示效果

  文章详情页文章评论功能第3张

2、Ajax显示根评论

 (1)更新comment视图函数,准备返回给ajax回调函数处理的数据

def comment(request):
    print(request.POST)

    article_id = request.POST.get("article_id")
    pid = request.POST.get("pid")
    content = request.POST.get("content")
    user_id = request.user.pk

    # 在数据库生成一条评论对象  父评论为空是根评论,不为空则是子评论
    comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)

    response = {}
    # create_time是一个datetime.datetime对象,在json序列化时不能对对象进行json序列化,必须进行strftime的转换
    response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
    # 评论人
    response["username"] = request.user.username
    # 内容
    response["content"] = content

    return JsonResponse(response)

(2)回调函数处理数据,显示新的评论

// 评论请求
$(".comment_btn").click(function () {
    var content = $('#comment_content').val();    // 拿到评论框的内容
    var pid = "";   // 父评论默认为空

    $.ajax({
        url: "/comment/",
        type: "post",
        data: {
            'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
            'article_id': "{{ article_obj.pk }}",
            'content': content,
            'pid': pid,
        },
        success: function (data) {
            console.log(data);

            // 获取视图函数返回的数据
            var create_time = data.create_time;
            var username = data.username;
            var content = data.content;

            // ES6特性:字符串模板。
            // ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。
            var s = `
                <li class="list-group-item">
                    <div>
                        <span>${create_time}</span>    
                        <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><${username}/span></a>    
                        <a href="http://t.zoukankan.com/xiugeng-p-9429510.html" class="pull-right">回复</a>
                    </div>
                    <div class="comment_con">
                        {# 评论内容 #}
                        <p>${content}</p>
                    </div>
                </li>`;
            // DOM操作把标签字符串整个放入ul的标签中去
            $("ul.comment_list").append(s);

            // 提交后清空评论框
            $("#comment_content").val("");
        }
    })
})

注意:1)ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。

//产生一个随机数
var num=Math.random();
//将这个数字输出到console
console.log(`your num is ${num}`);

  2)获取视图函数返回的数据:

// 获取视图函数返回的数据
var create_time = data.create_time;
var username = data.username;
var content = data.content;

(3)显示效果如下:

  文章详情页文章评论功能第4张

 五、提交子评论

   通过点击评论后的回复按钮来提交子评论,点击回复按钮事件:光标挪到输出框下;输出框显示 @用户名。

1、修改article_detail.html文章评论列表回复标签

{# 文章评论列表 #}
<div class="comments">
    <p>评论列表</p>
    <ul class="list-group comment_list">
        {% for comment in comment_list %}
            <li class="list-group-item">
                <div>
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"># {{ forloop.counter }}楼</a>   
                    <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>   
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><{{ comment.user.username }}/span></a>   
                    <a   username="{{ comment.user.username }}">回复</a>
                </div>
                <div class="comment_con">
                    {# 评论内容 #}
                    <p>{{ comment.content }}</p>
                </div>
            </li>
        {% endfor %}
    </ul>

    <p>发表评论</p>
    <p>昵称: <input type="text"     disabled="disabled" size="50"
                  value="{{ request.user.username }}"></p>
    <p>评论内容</p>
    <textarea name=""   cols="60" rows="10"></textarea> {# textarea是一个内联标签 #}
    <p>
        <button class="btn btn-default comment_btn">提交评论</button>
    </p>
</div>

  给<a username="{{ comment.user.username }}">回复</a>,添加了属性username,拿到当前行评论的用户。

  注意:回复这个a标签不能添加href="http://t.zoukankan.com/xiugeng-p-9429510.html"属性。否则在触发回复事件后,会刷新当前页面

2、在article_detail.html中编辑点击回复按钮事件

// 回复按钮事件
$(".reply_btn").click(function () {
    $('#comment_content').focus();   // 获取焦点
    // 拿到对应的父评论的用户名
    var val = "@" + $(this).attr("username")+"
";
    // 给输入框赋值
    $('#comment_content').val(val);

});

  显示效果如下所示:

  文章详情页文章评论功能第5张

 3、提交子评论

article_detail.html做如下处理

<script>
    // 点赞请求
    ...
    
    var pid = "";   // 父评论默认为空
    // 评论请求
    $(".comment_btn").click(function () {
        var content = $('#comment_content').val();    // 拿到评论框的内容
        if (pid) {
            // pid有值,是子评论
            // 处理拿到子评论值方法一:
            var index = content.indexOf("
");  // 拿到换行符索引值
            content = content.slice(index+1);  // 切片处理,从index+1一直取到最后
        }

        $.ajax({
            url: "/comment/",
            type: "post",
            data: {
                'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
                'article_id': "{{ article_obj.pk }}",
                'content': content,
                'pid': pid,
            },
            success: function (data) {
                console.log(data);

                // 获取视图函数返回的数据
                var create_time = data.create_time;
                var username = data.username;
                var content = data.content;

                // ES6特性:字符串模板。
                // ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。
                var s = `
            <li class="list-group-item">
                <div>
                    <span>${create_time}</span>    
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><${username}/span></a>    
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html" class="pull-right">回复</a>
                </div>
                <div class="comment_con">
                    {# 评论内容 #}
                    <p>${content}</p>
                </div>
            </li>`;
                // DOM操作把标签字符串整个放入ul的标签中去
                $("ul.comment_list").append(s);

                // 提交后清空评论框
                $("#comment_content").val("");
                // pid重新赋值
                pid = "";
            }
        })
    });

    // 回复按钮事件
    $(".reply_btn").click(function () {
        $('#comment_content').focus();   // 获取焦点
        // 拿到对应的父评论的用户名
        var val = "@" + $(this).attr("username")+"
";
        // 给输入框赋值
        $('#comment_content').val(val);
        // 拿到父评论的主键值
        pid = $(this).attr("comment_pk");
    });
</script>

注意:

(1)将var pid="";  改为全局变量,拿到事件外。根据pid是否有值,处理评论框内容:

  pid没有值的时候:

var content = $('#comment_content').val();

  pid有值的时候:

if (pid) {
    // pid有值,是子评论
    // 处理拿到子评论值方法一:
    var index = content.indexOf("
");  // 拿到换行符索引值
    content = content.slice(index+1);  // 切片处理,从index+1一直取到最后
}

(2)给回复按钮这个a标签添加一个新的自定义属性:comment_pk。

<a   username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}">回复</a>

  点击回复按钮拿到父评论的主键值

// 拿到父评论的主键值
pid = $(this).attr("comment_pk");

(3)每次提交评论后,都要清空pid。这样发布子评论后,不刷新页面紧接着又发布评论,这个新评论就不会有父评论了,

(4)查看数据库中的blog_comment表,子评论的父评论id:

  文章详情页文章评论功能第6张

六、显示子评论

1、render显示子评论

{# 文章评论列表 #}
<div class="comments">
    <p>评论列表</p>
    <ul class="list-group comment_list">
        {% for comment in comment_list %}
            <li class="list-group-item">
                <div>
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"># {{ forloop.counter }}楼</a>   
                    <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>   
                    <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><{{ comment.user.username }}/span></a>   
                    <a   username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}">回复</a>
                </div>

                {% if comment.parent_comment_id %}
                    <div class="pid_info well">
                        <p>
                            {# 拿到父评论对象评论人和评论内容 #}
                            {{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}
                        </p>
                    </div>
                {% endif %}

                <div class="comment_con">
                    {# 评论内容 #}
                    <p>{{ comment.content }}</p>
                </div>
            </li>
        {% endfor %}
    </ul>

    <p>发表评论</p>
    <p>昵称: <input type="text"     disabled="disabled" size="50"
                  value="{{ request.user.username }}"></p>
    <p>评论内容</p>
    <textarea name=""   cols="60" rows="10"></textarea> {# textarea是一个内联标签 #}
    <p>
        <button class="btn btn-default comment_btn">提交评论</button>
    </p>
</div>

(1)在模板中判断评论对象是否有父评论,如果有显示父评论人和评论对象:

{% if comment.parent_comment_id %}
    <div class="pid_info well">
        <p>
            {# 拿到父评论对象评论人和评论内容 #}
            {{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}
        </p>
    </div>
{% endif %}

(2)bootstrap的well用在元素上,就能有嵌入(inset)的简单效果。

(3)显示效果:

  文章详情页文章评论功能第7张

2、Ajax显示子评论

 (1)改写article.detail.html评论请求事件回调函数事件处理:

var pid = "";   // 父评论默认为空
// 评论请求
$(".comment_btn").click(function () {
    var content = $('#comment_content').val();    // 拿到评论框的内容
    if (pid) {
        // pid有值,是子评论
        // 处理拿到子评论值方法一:
        var index = content.indexOf("
");  // 拿到换行符索引值
        content = content.slice(index + 1);  // 切片处理,从index+1一直取到最后
    }

    $.ajax({
        url: "/comment/",
        type: "post",
        data: {
            'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
            'article_id': "{{ article_obj.pk }}",
            'content': content,
            'pid': pid,
        },
        success: function (data) {
            console.log(data);

            // 获取视图函数返回的数据
            var create_time = data.create_time;
            var username = data.username;
            var content = data.content;

            var parent_username = data.parent_username;
            var parent_content = data.parent_content;

            if (pid) {
                // ES6特性:字符串模板。
                // ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。
                var s = `
                    <li class="list-group-item">
                        <div>
                            <span>${create_time}</span>    
                            <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><${username}/span></a>    
                            <a href="http://t.zoukankan.com/xiugeng-p-9429510.html" class="pull-right">回复</a>
                        </div>
                        <div class="pid_info well">
                            <p>
                                {# 拿到父评论对象评论人和评论内容 #}
                                ${ parent_username }: ${ parent_content }
                            </p>
                        </div>
                        <div class="comment_con">
                            {# 评论内容 #}
                            <p>${content}</p>
                        </div>
                    </li>`;
            } else {
                var s = `
                    <li class="list-group-item">
                        <div>
                            <span>${create_time}</span>    
                            <a href="http://t.zoukankan.com/xiugeng-p-9429510.html"><span><${username}/span></a>    
                            <a href="http://t.zoukankan.com/xiugeng-p-9429510.html" class="pull-right">回复</a>
                        </div>
                        <div class="comment_con">
                            {# 评论内容 #}
                            <p>${content}</p>
                        </div>
                    </li>`;
            }
            // DOM操作把标签字符串整个放入ul的标签中去
            $("ul.comment_list").append(s);

            // 提交后清空评论框
            $("#comment_content").val("");
            // pid重新赋值
            pid = "";
        }
    })
});

  注意:根据pid是否有值对var s的标签字符串构建不同的结构和样式。也就是针对根评论和子评论构建不同的结构。由于构建子评论需要显示父评论的用户名和评论内容。因此需要在视图函数中返回响应的数据。

(2)在comment视图函数中添加父评论对象用户名和评论内容

def comment(request):
    print(request.POST)

    article_id = request.POST.get("article_id")
    pid = request.POST.get("pid")
    content = request.POST.get("content")
    user_id = request.user.pk

    # 在数据库生成一条评论对象  父评论为空是根评论,不为空则是子评论
    comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)

    response = {}
    # create_time是一个datetime.datetime对象,在json序列化时不能对对象进行json序列化,必须进行strftime的转换
    response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
    # 评论人
    response["username"] = request.user.username
    # 内容
    response["content"] = content

    # 父评论对象评论人和评论内容
    response["parent_username"] = comment_obj.parent_comment.user.username
    response["parent_content"] = comment_obj.parent_comment.content

    return JsonResponse(response)

(3)显示效果:

  文章详情页文章评论功能第8张

七、评论树

  前面都是用的评论楼来展示评论列表,但是这种方式结构不够清晰。因此也需要学会用评论树来展示评论列表。

1、评论树的请求数据

(1)构建article_detail.html中评论树标签和点击事件

{# 文章评论列表 #}
<div class="comments list-group">
    <p class="tree_btn">评论树</p>
    <div class="comment_tree">

    </div>
    <script>
        $(".tree_btn").click(function () {
            $.ajax({
                url: "/get_comment_tree/",
                type: "get",
                data: {
                    article_id: "{{ article_obj.pk }}",

                },
                success: function (data) {
                    console.log(data);
                }
            })
        })
    </script>
    <p>评论列表</p>
    ...
</div>

(2)根据ajax的请求url建立对应的url

urlpatterns = [
    path('admin/', admin.site.urls),
    ...
    path('comment/', views.comment),  # 评论
    path("get_comment_tree/", views.get_comment_tree),   # 评论树
    ...
]

(3)建立对应的视图函数get_comment_tree

def get_comment_tree(request):
    article_id = request.GET.get("article_id")
    # 过滤出文章对应的评论,挑出主键值、评论内容、父评论id,拿到的是一个queryset,结构类似一个列表里面装着一个个字典
    # 但是queryset并不是一个列表,可以用list()函数将其转换为列表
    ret = list(models.Comment.objects.filter(article_id=article_id).values("pk", "content", "parent_comment_id"))

    # JsonResponse对非字典的数据进行序列化,必须设置一个参数safe=False
    return JsonResponse(ret, safe=False)

  注意:QuerySet虽然结构类似一个列表里面装着一个个字典但并不是一个列表。可以用list()函数转换为列表。

  其次一般JsonResponse都是用来对字典进行序列化,如果要对一个非字典的数据序列化,必须设置一个参数safe=False,否则会报错。

(4)访问页面点击页面中评论树时,页面控制台输出了一条数组数据:

  文章详情页文章评论功能第9张

2、展开评论树

{# 文章评论列表 #}
    <div class="comments list-group">
        <p class="tree_btn">评论树</p>
        <div class="comment_tree">

        </div>
        <script>
            $(".tree_btn").click(function () {
                $.ajax({
                    url: "/get_comment_tree/",
                    type: "get",
                    data: {
                        article_id: "{{ article_obj.pk }}",

                    },
                    success: function (data) {
                        console.log(data);  // data是一个列表,列表中包含一个个字典
                        $.each(data, function (index, comment_object) {
                            var pk = comment_object.pk;
                            var content = comment_object.content;
                            var parent_comment_id = comment_object.parent_comment_id;

                            var s = '<div   comment_id='+pk+' style="margin-left: 20px"><span>'+content+'</span></div>';

                            // 判断评论是根评论还是子评论
                            if (!parent_comment_id) {    // 感叹号取反
                                // 根评论
                                $(".comment_tree").append(s);
                            } else {
                                // 子评论
                                // 放入父评论的div标签
                                // 属性选择器,找到comment_id属性值对应的div
                                $("[comment_id="+parent_comment_id+"]").append(s);
                            }

                        })
                    }
                })
            })
        </script>
    ...
</div>

  注意:

(1)变量s是将要插入模板中的标签字符串,标签字符串内定义的评论的内容。

<div   comment_id='pk' style="margin-left: 20px">
    <span>评论内容</span>
</div>

  每个标签字符串都定义了comment_id,对应评论对象的主键值。定义样式,则是为了子评论相对父评论向左移动20像素,样式得到错开。

(2)在ajax回调函数循环get_comment_tree视图函数返回列表

success: function (data) {
    console.log(data);  // data是一个列表,列表中包含一个个字典
    $.each(data, function (index, comment_object) {
        var pk = comment_object.pk;
        var content = comment_object.content;
        var parent_comment_id = comment_object.parent_comment_id;   
     ...
   })
}    

(3)如果判断是子评论的话,需要利用属性选择器,找到属性comment_id=父评论的id,这样筛选到对应的评论将标签字符串放如对应的div中。

var s = '<div   comment_id='+pk+' style="margin-left: 20px"><span>'+content+'</span></div>';

// 判断评论是根评论还是子评论
if (!parent_comment_id) {    // 感叹号取反
    // 根评论
    $(".comment_tree").append(s);
} else {
    // 子评论
    // 放入父评论的div标签
    // 属性选择器,找到comment_id属性值对应的div
    $("[comment_id="+parent_comment_id+"]").append(s);
}

(4)显示效果:

  文章详情页文章评论功能第10张

3、评论树回顾和优化

(1)在视图中获取的评论列表,会不会有子评论在前,父评论在后,导致在回调函数中循环列表时,发现有子评论无法找到父评论无法插入?

答:这个问题是不会发生的,因为评论生成是按主键进行排序的,根评论一定在前,与它关联的子评论一定在后。

  为了保险在获取评论对象时还是order_by做一下排序:

def get_comment_tree(request):
    article_id = request.GET.get("article_id")
    ret = list(models.Comment.objects.filter(article_id=article_id).order_by("pk").values("pk", "content", "parent_comment_id"))

    return JsonResponse(ret, safe=False)

(2)不再通过点击评论树标签,触发事件来显示评论树。直接访问文章页面就显示评论树?

答:不再把ajax事件内嵌在click事件中,浏览器加载标签字符串时就会直接执行ajax,立刻发新的请求拿到响应结果,由于速度非常快,立刻就将评论树显示在页面上。

<script>
    // $(".tree_btn").click(function () {
    $.ajax({
        url: "/get_comment_tree/",
        type: "get",
        data: {
            article_id: "{{ article_obj.pk }}",

        },
        success: function (data) {
            console.log(data);  // data是一个列表,列表中包含一个个字典
            $.each(data, function (index, comment_object) {
                var pk = comment_object.pk;
                var content = comment_object.content;
                var parent_comment_id = comment_object.parent_comment_id;

                var s = '<div   comment_id='+pk+' style="margin-left: 20px"><span>'+content+'</span></div>';

                // 判断评论是根评论还是子评论
                if (!parent_comment_id) {    // 感叹号取反
                    // 根评论
                    $(".comment_tree").append(s);
                } else {
                    // 子评论
                    // 放入父评论的div标签
                    // 属性选择器,找到comment_id属性值对应的div
                    $("[comment_id="+parent_comment_id+"]").append(s);
                }

            })
        }
    })
    // })
</script>

八、事务操作

  比如在数据库生成一条评论对象,同时将文章的评论数进行更新。这两步操作需要设计为同进同退,即如果有一步没完成,完成的操作需要在数据库回退。

def comment(request):
    print(request.POST)

    article_id = request.POST.get("article_id")
    pid = request.POST.get("pid")
    content = request.POST.get("content")
    user_id = request.user.pk

    # 事务操作:生成记录和评论数更新同进同退
    with transaction.atomic():
        # 在数据库生成一条评论对象  父评论为空是根评论,不为空则是子评论
        comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
        # 文章评论数更新
        models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)

    response = {}
    # create_time是一个datetime.datetime对象,在json序列化时不能对对象进行json序列化,必须进行strftime的转换
    response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
    # 评论人
    response["username"] = request.user.username
    # 内容
    response["content"] = content

    # 父评论对象评论人和评论内容
    response["parent_username"] = comment_obj.parent_comment.user.username
    response["parent_content"] = comment_obj.parent_comment.content

    return JsonResponse(response)

1、引入以下模块来实现事务操作:

from django.db import transaction

2、将几个操作作为一个事务的方法:

# 事务操作:生成记录和评论数更新同进同退
with transaction.atomic():
    # 在数据库生成一条评论对象  父评论为空是根评论,不为空则是子评论
    comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
    # 文章评论数更新          
    models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)

九、发送邮件

  文章被评论了,给作者发一份站内信通知他多了一条评论。这里使用django提供的模块来实现发送邮件:

from django.core.mail import send_mail

1、send_mail函数的源码分析

def send_mail(subject, message, from_email, recipient_list,
              fail_silently=False, auth_user=None, auth_password=None,
              connection=None, html_message=None):
    """

    :param subject: 标题
    :param message: 邮件内容
    :param from_email: 发件邮箱
    :param recipient_list: 收件邮箱
    :param fail_silently:
    :param auth_user:
    :param auth_password:
    :param connection:
    :param html_message:
    :return:
    """
    connection = connection or get_connection(
        username=auth_user,
        password=auth_password,
        fail_silently=fail_silently,
    )
    mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, connection=connection)
    if html_message:
        mail.attach_alternative(html_message, 'text/html')

    return mail.send()

2、settings配置官方邮箱

# 邮箱配置
EMAIL_HOST = 'smtp.163.com'  # 如果是 qq 改成 smtp.exmail.qq.com
EMAIL_PORT = 465  # qqs是465
EMAIL_HOST_USER = 'xxx@163.com'           # 帐号
EMAIL_HOST_PASSWORD = 'xxxxxx'  # 密码(授权码)
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER    # 默认使用当前配置的user
EMAIL_USE_SSL = True  # 是否使用SSL证书, 网易邮箱关闭SSL后SMTP应该为25

3、在comment视图中配置邮件发送

def comment(request):
    ...
    # 为了发送邮件拿到文章对象
    article_obj = models.Article.objects.filter(pk=article_id).first()
    ...

    # 发送邮件
    from django.core.mail import send_mail
    from cnblog import settings
    send_mail(
        "您的文章%s新增了一条评论内容" % article_obj.title,
        content,
        settings.EMAIL_HOST_USER,   # 发送方
        ["44xxxx@qq.com"]   # 接收方
    )
    ...

  接收的邮箱,应该是用户注册时填写的邮箱信息。

  评论后,我的qq邮箱收到邮件:

  文章详情页文章评论功能第11张

4、多线程解决邮件发送网络延迟引起的网页卡顿

  点击评论会发现,页面需要卡很久,严重影响了用户体验。

# 发送邮件
from django.core.mail import send_mail
from cnblog import settings
# send_mail(
#     "您的文章%s新增了一条评论内容" % article_obj.title,
#     content,
#     settings.EMAIL_HOST_USER,   # 发送方
#     ["44xxxx@qq.com"]   # 接收方
# )

import threading

t = threading.Thread(target=send_mail, args=(
    "您的文章%s新增了一条评论内容" % article_obj.title,
    content,
    settings.EMAIL_HOST_USER,  # 发送方
    ["443xxxx@qq.com"]  # 接收方
))
t.start()

  这样用一个线程去跑send_email,就不会影响响应结果反馈了。

5、改完后完整的comment视图函数如下所示

文章详情页文章评论功能第12张文章详情页文章评论功能第13张
def comment(request):
    print(request.POST)

    article_id = request.POST.get("article_id")
    pid = request.POST.get("pid")
    content = request.POST.get("content")
    user_id = request.user.pk

    # 为了发送邮件拿到文章对象
    article_obj = models.Article.objects.filter(pk=article_id).first()

    # 事务操作:生成记录和评论数更新同进同退
    with transaction.atomic():
        # 在数据库生成一条评论对象  父评论为空是根评论,不为空则是子评论
        comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
        # 文章评论数更新
        models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)

    response = {}
    # create_time是一个datetime.datetime对象,在json序列化时不能对对象进行json序列化,必须进行strftime的转换
    response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
    # 评论人
    response["username"] = request.user.username
    # 内容
    response["content"] = content

    # 发送邮件
    from django.core.mail import send_mail
    from cnblog import settings
    # send_mail(
    #     "您的文章%s新增了一条评论内容" % article_obj.title,
    #     content,
    #     settings.EMAIL_HOST_USER,   # 发送方
    #     ["443614404@qq.com"]   # 接收方
    # )

    import threading

    t = threading.Thread(target=send_mail, args=(
        "您的文章%s新增了一条评论内容" % article_obj.title,
        content,
        settings.EMAIL_HOST_USER,  # 发送方
        ["443614404@qq.com"]  # 接收方
    ))
    t.start()


    # 父评论对象评论人和评论内容
    response["parent_username"] = comment_obj.parent_comment.user.username
    response["parent_content"] = comment_obj.parent_comment.content

    return JsonResponse(response)
comment视图函数

免责声明:文章转载自《文章详情页文章评论功能》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇转:oracle常见重要视图-v$sql,v$sql_plan,v$sqltext,v$sqlarea,v$sql_plan_statistcscentos7 svn在repository在的情况下重装恢复下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

elementui源码解析markdown处理

一些参考网址: markdown-it官网:markdown-it | markdown-it 中文文档 (docschina.org) markdown-it插件的分析和源码分析参考地址:https://zhuanlan.zhihu.com/p/64290806 参考例子:https://gitee.com/springliuliu/mdToHtm...

Jquery和JS获取ul中li标签

js 获取元素下面所有的li var content=document.getElementById("content"); var items=content.getElementsByTagName("ul"); var itemss=items[2].getElementsByTagName("li");//获取第二个li标签 或 var div=d...

PbootCMS用扩展标签定制一个每日一图

自PbootCmsV2.0.6开始,PbootCMS支持自定义标签,且升级不被覆盖。妈妈再也不用担心我的代码升级被覆盖啦。 于是就想到用这个功能定制一个每日一图。 这个文件位置在 home下ExtLabelController控制器。 我们先找图源。通过百度找到必应搜索的API。 地址是这个: https://www.bing.com/HPImageArc...

mybatis返回HashMap结果类型与映射

Xhtml代码  <!-- 返回HashMap结果 类型-->       <!-- 如果想返回JavaBean,只需将resultType设置为JavaBean的别名或全限定名 -->       <!-- TypeAliasRegistry类初始化时注册了一些常用的别名,如果忘记了别名可以在这里面查看 -->  ...

你真的会玩SQL吗?删除重复数据且只保留一条

在网上看过一些解决方法 我在此给出的方法适用于无唯一ID的情形 表:TB_MACVideoAndPicture 字段只有2个:mac,content mac作为ID,正常情况下mac数据是唯一的,由于操作失误导致数据插入多次,导致出现多个mac,content重复数据,现在只保留一条,删除多余的 大体思想是给重复数据一个自增ID,过滤出每组里面最小ID,删...

content: "f015";自带常见图标icon

简介:让你可以即时定制的可伸缩矢量图标;    公有302种图标任你选择;    图标的风格,如:大小、颜色、阴影可以通过css样式修改;     支持ie6、7;  example: <!DOCTYPE html> <html > <head> <meta charset="UTF-8" /> <m...