python 全栈开发,Day83(博客系统子评论,后台管理,富文本编辑器kindeditor,bs4模块)

1、子评论

必须点击回复,才是子评论!不然是根评论
点击回复以后,定位到输入框,同时加入@评论者的用户名javascript

定位输入框

focus

focus:获取对象焦点触发事件php

先作样式。点击回复以后,定位到输入框,加入被评论的用户名css

给回复的a标签加一个class=reply_btn,关闭a标签的跳转,使用javascript:void(0)html

修改article_detail.html,增长一段回复的jsjava

{% extends "base.html" %} {% block content %} <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content"> {{ article_obj.content|safe }} </div>
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group"> {% for comment in comment_list %} <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp; <a href="">{{ comment.user.username }}</a>
                            <a href="javascript:void(0)" class="pull-right reply_btn"><span>回复</span></a>

                        </div>
                        <div>
                            <p>{{ comment.content }}</p>
                        </div>
                    </li> {% endfor %} </ul>
            <p>发表评论</p>
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div> {% csrf_token %} <script>
        // 点赞和踩灭 $(".action").click(function () { {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit"); {#获取提示的span标签#}
            var _this = $(this).children("span"); {#判断是否登陆#}
            if ("{{ request.user.username }}") { $.ajax({ url: "/digg/", type: "post", data: { is_up: is_up, article_id: "{{ article_obj.pk }}", csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); console.log(typeof data); if (data.state) { //提交成功 var val = _this.text();  //获取text值 //在原有的基础上加1。注意:必定要进行类型转换 _this.text(parseInt(val) + 1) } else { // 重复提交 var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("")  //清空提示文字 }, 1000) } } }) } else { location.href = "/login/"; } }) // 提交评论 $(".comment_btn").click(function () { {#评论内容#}
            var content = $("#comment_content").val(); {#默认为空#}
            var pid = ""; $.ajax({ url: "/comment/", type: "post", data: { content: content, article_id: "{{ article_obj.pk }}", pid: pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); {#获取3个值#}
                    var comment_time = data.timer; var comment_content = data.content; var comment_user = data.user; {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>&nbsp;&nbsp; <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`; {#追加到评论列表中#}
                    $(".comment_list").append($li); // 清空输入框的内容 $("#comment_content").val("") } }) }) // 回复按钮事件 $(".reply_btn").click(function () { {#获取到textarea输入框光标#}
            $("#comment_content").focus(); }) </script> {% endblock %}
View Code

换一个zhang用户登陆,访问一篇有回复的博客,点击回复,效果以下:python

它会自动定位到输入框的位置jquery

 

增长@评论用户

注意:这里的评论用户是这一条评论的用户,好比上面的xiaogit

那么js如何获取评论人的用户名呢?使用render渲染就能够了!github

给回复的a标签添加一个自定义属性usernameajax

$this能直接获取这个a标签。

修改article_detail.html

{% extends "base.html" %} {% block content %} <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content"> {{ article_obj.content|safe }} </div>
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group"> {% for comment in comment_list %} <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp; <a href="">{{ comment.user.username }}</a>
                            <a href="javascript:void(0)" class="pull-right reply_btn" username="{{ comment.user.username }}"><span>回复</span></a>

                        </div>
                        <div>
                            <p>{{ comment.content }}</p>
                        </div>
                    </li> {% endfor %} </ul>
            <p>发表评论</p>
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div> {% csrf_token %} <script>
        // 点赞和踩灭 $(".action").click(function () { {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit"); {#获取提示的span标签#}
            var _this = $(this).children("span"); {#判断是否登陆#}
            if ("{{ request.user.username }}") { $.ajax({ url: "/digg/", type: "post", data: { is_up: is_up, article_id: "{{ article_obj.pk }}", csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); console.log(typeof data); if (data.state) { //提交成功 var val = _this.text();  //获取text值 //在原有的基础上加1。注意:必定要进行类型转换 _this.text(parseInt(val) + 1) } else { // 重复提交 var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("")  //清空提示文字 }, 1000) } } }) } else { location.href = "/login/"; } }) // 提交评论 $(".comment_btn").click(function () { {#评论内容#}
            var content = $("#comment_content").val(); {#默认为空#}
            var pid = ""; $.ajax({ url: "/comment/", type: "post", data: { content: content, article_id: "{{ article_obj.pk }}", pid: pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); {#获取3个值#}
                    var comment_time = data.timer; var comment_content = data.content; var comment_user = data.user; {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>&nbsp;&nbsp; <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`; {#追加到评论列表中#}
                    $(".comment_list").append($li); // 清空输入框的内容 $("#comment_content").val("") } }) }) // 回复按钮事件 $(".reply_btn").click(function () { {#获取到textarea输入框光标#}
            $("#comment_content").focus(); {#增长@+用户名+换行符#}
            var val = "@" + $(this).attr("username") + "\n"; {#修改输入框的值#}
            $("#comment_content").val(val); }) </script> {% endblock %}
View Code

从新刷新页面,并点击回复,效果以下:

首页增长链接

修改index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css">
    <script src="/static/js/jquery.js"></script>
    <script src="/static/bootstrap/js/bootstrap.js"></script>
    <style> .desc { text-align: justify; } .info { margin-top: 10px; } h5 a { color: #105cb6;
            font-size: 14px; font-weight: bold; text-decoration: underline; } .diggit { float: left; margin-right: 20px; width: 46px; height: 52px; background-image: url('/static/img/upup.gif'); } .diggnum { position: relative; top: 6px; left: 20px; } </style>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">博客园</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">新闻 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">博问</a></li>

            </ul>

            <ul class="nav navbar-nav navbar-right"> {% if request.user.username %} <li><a href="#"><span class="glyphicon glyphicon-user"></span>&nbsp;{{ request.user.username }}</a>
                    </li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#">修改密码</a></li>
                            <li><a href="#">我的信息</a></li>
                            <li><a href="/logout/">注销</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="#">Separated link</a></li>
                        </ul>
                    </li> {% else %} <li><a href="/login/">登录</a></li>
                    <li><a href="#">注册</a></li> {% endif %} </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>

            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>
        </div>
        <div class="col-md-6">

            <div class="article_list"> {% for article in article_list %} <div class="diggit">
                        <span class="diggnum">0</span>
                    </div>
                    <div class="article_item">

                        <h5><a href="/{{ article.user.username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5>
                        <div>
                            <span class="media-left"><a href="/{{ article.user.username }}"><img width="48" height="48" src="https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=1758343206,1224786249&fm=58&bpow=1024&bpoh=1536" alt=""></a></span>

                            <span class="media-right small desc "> {{ article.desc }} </span>

                        </div>
                        <div class="info small">
                            <span><a href="/{{ article.user.username }}">{{ article.user.username }}</a></span> &nbsp; 发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp; <img src="/static/img/icon_comment.gif" alt=""><a href="/{{ article.user.username }}/articles/{{ article.pk }}">评论({{ article.comment_count }})</a>&nbsp;&nbsp; <span class="glyphicon glyphicon-thumbs-up"></span><a href="/{{ article.user.username }}/articles/{{ article.pk }}">点赞({{ article.up_count }})</a>
                        </div>
                    </div>
                    <hr> {% endfor %} </div>


        </div>
        <div class="col-md-3">

            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>
            <div class="panel panel-success">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>

        </div>
    </div>
</div>

</body>
</html>
View Code

效果以下:

点击文章标题,能够进入文章详情页。

 

提交子评论

若是是下面这种状态,直接回复后,就是一个根评论

为何呢?由于前台和后端,没有作判断!

ajax提交数据时,pid的变量不该该为空,应该发送一个父评论id才行!

何时,对pid赋值呢?
正确作法是:点击回复时,将pid赋值。
怎么拿到父评论的id呢?使用render渲染,给a标签加一个子定义属性comment_id
而后利用js获取,进行赋值便可

获取父评论id,并赋值

修改article_detail.html

{% extends "base.html" %} {% block content %} <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content"> {{ article_obj.content|safe }} </div>
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group"> {% for comment in comment_list %} <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp; <a href="">{{ comment.user.username }}</a>
                            <a href="javascript:void(0)" class="pull-right reply_btn" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}"><span>回复</span></a>

                        </div>
                        <div>
                            <p>{{ comment.content }}</p>
                        </div>
                    </li> {% endfor %} </ul>
            <p>发表评论</p>
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div> {% csrf_token %} <script>
        // 点赞和踩灭 $(".action").click(function () { {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit"); {#获取提示的span标签#}
            var _this = $(this).children("span"); {#判断是否登陆#}
            if ("{{ request.user.username }}") { $.ajax({ url: "/digg/", type: "post", data: { is_up: is_up, article_id: "{{ article_obj.pk }}", csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); console.log(typeof data); if (data.state) { //提交成功 var val = _this.text();  //获取text值 //在原有的基础上加1。注意:必定要进行类型转换 _this.text(parseInt(val) + 1) } else { // 重复提交 var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("")  //清空提示文字 }, 1000) } } }) } else { location.href = "/login/"; } }) // 提交评论 $(".comment_btn").click(function () { {#评论内容#}
            var content = $("#comment_content").val(); {#默认为空#}
            var pid = ""; $.ajax({ url: "/comment/", type: "post", data: { content: content, article_id: "{{ article_obj.pk }}", pid: pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); {#获取3个值#}
                    var comment_time = data.timer; var comment_content = data.content; var comment_user = data.user; {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>&nbsp;&nbsp; <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`; {#追加到评论列表中#}
                    $(".comment_list").append($li); // 清空输入框的内容 $("#comment_content").val("") } }) }) // 回复按钮事件 $(".reply_btn").click(function () { {#获取到textarea输入框光标#}
            $("#comment_content").focus(); {#增长@+用户名+换行符#}
            var val = "@" + $(this).attr("username") + "\n"; {#修改输入框的值#}
            $("#comment_content").val(val); {#pid赋值#}
            pid=$(this).attr("comment_id"); console.log(pid); }) </script> {% endblock %}
View Code

刷新网页,发现自定义属性comment_id以及被渲染出来了

点击回复,pid就能打印出来

正式提交子评论

修改article_detail.html,修改pid变量的位置

{% extends "base.html" %} {% block content %} <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content"> {{ article_obj.content|safe }} </div>
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group"> {% for comment in comment_list %} <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp; <a href="">{{ comment.user.username }}</a>
                            <a href="javascript:void(0)" class="pull-right reply_btn" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}"><span>回复</span></a>

                        </div>
                        <div>
                            <p>{{ comment.content }}</p>
                        </div>
                    </li> {% endfor %} </ul>
            <p>发表评论</p>
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div> {% csrf_token %} <script>
        // 点赞和踩灭 $(".action").click(function () { {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit"); {#获取提示的span标签#}
            var _this = $(this).children("span"); {#判断是否登陆#}
            if ("{{ request.user.username }}") { $.ajax({ url: "/digg/", type: "post", data: { is_up: is_up, article_id: "{{ article_obj.pk }}", csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); console.log(typeof data); if (data.state) { //提交成功 var val = _this.text();  //获取text值 //在原有的基础上加1。注意:必定要进行类型转换 _this.text(parseInt(val) + 1) } else { // 重复提交 var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("")  //清空提示文字 }, 1000) } } }) } else { location.href = "/login/"; } }) // 提交评论 var pid="";  //默认为空,表示父评论 $(".comment_btn").click(function () { {#评论内容#}
            var content = $("#comment_content").val(); $.ajax({ url: "/comment/", type: "post", data: { content: content, article_id: "{{ article_obj.pk }}", pid: pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); {#获取3个值#}
                    var comment_time = data.timer; var comment_content = data.content; var comment_user = data.user; {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>&nbsp;&nbsp; <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`; {#追加到评论列表中#}
                    $(".comment_list").append($li); // 清空输入框的内容 $("#comment_content").val("") } }) }) // 回复按钮事件 $(".reply_btn").click(function () { {#获取到textarea输入框光标#}
            $("#comment_content").focus(); {#增长@+用户名+换行符#}
            var val = "@" + $(this).attr("username") + "\n"; {#修改输入框的值#}
            $("#comment_content").val(val); {#pid赋值#}
            pid=$(this).attr("comment_id"); console.log(pid); }) </script> {% endblock %}
View Code

提交一条子评论

效果以下:

查看blog_comment表记录,多了一条记录

nid对应的是parent_comment_id

可是评论内容有些不妥,它加了@xiao

去掉@用户名

怎么去掉@用户名呢?使用正则匹配?太麻烦了!

使用切片?好比s.slice(1),貌似很差作!

观察一下规律,在输入框中。第一行,永远是@用户名。第二行,才是用户真正的评论内容。

indexOf()

indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

若是没有找到匹配的字符串则返回 -1

 

思路:先找到换行符,使用indexOf。再加1,表示下一行,就能够获得真正的评论内容

先来测试一下,在templates目录中,新建一个test.html,内容以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script> var aa = "@xiao\n34324234"; var index = aa.indexOf("\n"); content = aa.slice(index+1); console.log(content); </script>
</body>
</html>
View Code

直接用谷歌浏览器打开,注意:这已经脱离了django,是直接访问的。

能够直接获得评论内容,说明是可行的!

修改article_detail.html

{% extends "base.html" %} {% block content %} <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content"> {{ article_obj.content|safe }} </div>
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group"> {% for comment in comment_list %} <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp; <a href="">{{ comment.user.username }}</a>
                            <a href="javascript:void(0)" class="pull-right reply_btn" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}"><span>回复</span></a>

                        </div>
                        <div>
                            <p>{{ comment.content }}</p>
                        </div>
                    </li> {% endfor %} </ul>
            <p>发表评论</p>
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div> {% csrf_token %} <script>
        // 点赞和踩灭 $(".action").click(function () { {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit"); {#获取提示的span标签#}
            var _this = $(this).children("span"); {#判断是否登陆#}
            if ("{{ request.user.username }}") { $.ajax({ url: "/digg/", type: "post", data: { is_up: is_up, article_id: "{{ article_obj.pk }}", csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); console.log(typeof data); if (data.state) { //提交成功 var val = _this.text();  //获取text值 //在原有的基础上加1。注意:必定要进行类型转换 _this.text(parseInt(val) + 1) } else { // 重复提交 var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("")  //清空提示文字 }, 1000) } } }) } else { location.href = "/login/"; } }) // 提交评论 var pid = "";  //默认为空,表示父评论 $(".comment_btn").click(function () { {#评论内容#}
            var content = $("#comment_content").val(); if (pid) {  //判断pid不为空时 var index = content.indexOf("\n"); //获取评论内容,注意:此时content变量已经被替换掉了 content = content.slice(index + 1) } $.ajax({ url: "/comment/", type: "post", data: { content: content, article_id: "{{ article_obj.pk }}", pid: pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); {#获取3个值#}
                    var comment_time = data.timer; var comment_content = data.content; var comment_user = data.user; {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>&nbsp;&nbsp; <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`; {#追加到评论列表中#}
                    $(".comment_list").append($li); // 清空输入框的内容 $("#comment_content").val("") } }) }) // 回复按钮事件 $(".reply_btn").click(function () { {#获取到textarea输入框光标#}
            $("#comment_content").focus(); {#增长@+用户名+换行符#}
            var val = "@" + $(this).attr("username") + "\n"; {#修改输入框的值#}
            $("#comment_content").val(val); {#pid赋值#}
            pid = $(this).attr("comment_id"); console.log(pid); }) </script> {% endblock %}
View Code

刷新网页,从新提交一次评论内容

刷新网页,效果以下:

查看blog_comment表记录,多了一条记录

展现子评论效果

上面已经把评论入库了,可是网页,看不出来,哪一条是子评论

因此须要修改页面,判断parent_comment_id不为空时,加一个div样式

修改article_detail.html

{% extends "base.html" %} {% block content %} <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content"> {{ article_obj.content|safe }} </div>
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group"> {% for comment in comment_list %} <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp; <a href="">{{ comment.user.username }}</a>
                            <a href="javascript:void(0)" class="pull-right reply_btn" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}"><span>回复</span></a>

                        </div> {#判断parent_comment_id是否为空#}
                        {% if comment.parent_comment_id %} {#增长div样式#}
                            <div class="parent_comment_info well">
                                <p> {#comment.parent_comment.user表示父评论的用户名#}
 @{{ comment.parent_comment.user }}: {{ comment.parent_comment.content }} </p>
                            </div> {% endif %} {#评论内容#}
                        <div>
                            <p>{{ comment.content }}</p>
                        </div>
                    </li> {% endfor %} </ul>
            <p>发表评论</p>
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div> {% csrf_token %} <script>
        // 点赞和踩灭 $(".action").click(function () { {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit"); {#获取提示的span标签#}
            var _this = $(this).children("span"); {#判断是否登陆#}
            if ("{{ request.user.username }}") { $.ajax({ url: "/digg/", type: "post", data: { is_up: is_up, article_id: "{{ article_obj.pk }}", csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); console.log(typeof data); if (data.state) { //提交成功 var val = _this.text();  //获取text值 //在原有的基础上加1。注意:必定要进行类型转换 _this.text(parseInt(val) + 1) } else { // 重复提交 var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("")  //清空提示文字 }, 1000) } } }) } else { location.href = "/login/"; } }) // 提交评论 var pid = "";  //默认为空,表示父评论 $(".comment_btn").click(function () { {#评论内容#}
            var content = $("#comment_content").val(); if (pid) {  //判断pid不为空时 var index = content.indexOf("\n"); //获取评论内容,注意:此时content变量已经被替换掉了 content = content.slice(index + 1) } $.ajax({ url: "/comment/", type: "post", data: { content: content, article_id: "{{ article_obj.pk }}", pid: pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); {#获取3个值#}
                    var comment_time = data.timer; var comment_content = data.content; var comment_user = data.user; {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>&nbsp;&nbsp; <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`; {#追加到评论列表中#}
                    $(".comment_list").append($li); // 清空输入框的内容 $("#comment_content").val("") } }) }) // 回复按钮事件 $(".reply_btn").click(function () { {#获取到textarea输入框光标#}
            $("#comment_content").focus(); {#增长@+用户名+换行符#}
            var val = "@" + $(this).attr("username") + "\n"; {#修改输入框的值#}
            $("#comment_content").val(val); {#pid赋值#}
            pid = $(this).attr("comment_id"); console.log(pid); }) </script> {% endblock %}
View Code

删除有@xiao的表记录

从新刷新页面,效果以下:

 

2、后台管理页面

主要作如下2部分:

添加文章以及展现文章,下图是博客园的效果:

修改index.html,增长一个下来选项,用来进入后台管理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css">
    <script src="/static/js/jquery.js"></script>
    <script src="/static/bootstrap/js/bootstrap.js"></script>
    <style> .desc { text-align: justify; } .info { margin-top: 10px; } h5 a { color: #105cb6;
            font-size: 14px; font-weight: bold; text-decoration: underline; } .diggit { float: left; margin-right: 20px; width: 46px; height: 52px; background-image: url('/static/img/upup.gif'); } .diggnum { position: relative; top: 6px; left: 20px; } </style>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">博客园</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">新闻 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">博问</a></li>

            </ul>

            <ul class="nav navbar-nav navbar-right"> {% if request.user.username %} <li><a href="#"><span class="glyphicon glyphicon-user"></span>&nbsp;{{ request.user.username }}</a>
                    </li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">管理<span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#">修改密码</a></li>
                            <li><a href="#">我的信息</a></li>
                            <li><a href="/backend/">后台管理</a></li>
                            <li><a href="/logout/">注销</a></li>
                            <li role="separator" class="divider"></li>
                        </ul>
                    </li> {% else %} <li><a href="/login/">登录</a></li>
                    <li><a href="#">注册</a></li> {% endif %} </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>

            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>
        </div>
        <div class="col-md-6">

            <div class="article_list"> {% for article in article_list %} <div class="diggit">
                        <span class="diggnum">0</span>
                    </div>
                    <div class="article_item">

                        <h5><a href="/{{ article.user.username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5>
                        <div>
                            <span class="media-left"><a href="/{{ article.user.username }}"><img width="48" height="48" src="https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=1758343206,1224786249&fm=58&bpow=1024&bpoh=1536" alt=""></a></span>

                            <span class="media-right small desc "> {{ article.desc }} </span>

                        </div>
                        <div class="info small">
                            <span><a href="/{{ article.user.username }}">{{ article.user.username }}</a></span> &nbsp; 发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp; <img src="/static/img/icon_comment.gif" alt=""><a href="/{{ article.user.username }}/articles/{{ article.pk }}">评论({{ article.comment_count }})</a>&nbsp;&nbsp; <span class="glyphicon glyphicon-thumbs-up"></span><a href="/{{ article.user.username }}/articles/{{ article.pk }}">点赞({{ article.up_count }})</a>
                        </div>
                    </div>
                    <hr> {% endfor %} </div>


        </div>
        <div class="col-md-3">

            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>
            <div class="panel panel-success">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body"> Panel content </div>
            </div>

        </div>
    </div>
</div>

</body>
</html>
View Code

注意:连接不须要增长用户名,看博客园的后台管理的连接,每一个人都是同样的。

那么它如何区分每个用户呢?使用cookie和session来验证!

修改urls.py,增长路径backend

urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('', views.index), #点赞或者踩灭
    path('digg/', views.digg), # 评论
    path('comment/', views.comment), # 后台管理
    path('backend/', views.backend), #文章详情
    re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail), # 跳转
    re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite), # 我的站点
    re_path('(?P<username>\w+)/$', views.homesite), ]
View Code

修改views.py,增长视图函数backend

def backend(request): user = request.user #当前用户文章列表
    article_list = Article.objects.filter(user=user) # 由于是在templates的下一层,因此须要指定目录backend
    return render(request, "backend/backend.html", {"user":user,"article_list":article_list})
View Code

将文件夹backend拷贝到templates目录下

将backend.css拷贝到static-->css目录下

切换一个用户,进入后台管理页面

http://127.0.0.1:8000/backend/

效果以下:

3、富文本编辑器kindeditor

富文本编辑器,Rich Text Editor, 简称 RTE, 它提供相似于 Microsoft Word 的编辑功能,容易被不会编写 HTML 的用户并须要设置各类文本格式的用户所喜好。

修改urls.py,增长路径

urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('', views.index), #点赞或者踩灭
    path('digg/', views.digg), # 评论
    path('comment/', views.comment), # 后台管理
    path('backend/', views.backend), # 添加文章
    path('backend/add_article/', views.add_article), #文章详情
    re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail), # 跳转
    re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite), # 我的站点
    re_path('(?P<username>\w+)/$', views.homesite), ]
View Code

修改views.py,增长add_article视图函数

def add_article(request): return render(request, "backend/add_article.html")
View Code

修改add_article.html

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
         <div class="alert-success text-center">添加文章</div>

         <div class="add_article_region">
              <div class="title form-group">
                 <label for="">标题</label>
                 <div>
                     <input type="text" name="title">
                 </div>
             </div>

             <div class="content form-group">

                 <div>
                     <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                 </div>
             </div>
             <div>
                 <ul> {% for cate in cate_list %} <li>{{ cate.title }}<input type="radio" name="cate" value="{{ cate.pk }}"></li> {% endfor %} </ul>
                 <hr>
                <ul> {% for tag in tags %} <li>{{ tag.title }} <input type="checkbox" name="tags" value="{{ tag.pk }}"></li> {% endfor %} </ul>
             </div>
             <input type="submit" class="btn btn-default">

         </div>

    </div>
    </form> {% endblock %}
View Code

访问增长文章页面

http://127.0.0.1:8000/backend/add_article/

假设用户要添加一个标题一,那么必需要输入以下内容:

这还只是一个标题,若是有不少内容呢?用户会疯掉的!

查看博客园的编辑器,输入一个标题一

 

点击HTML按钮,效果以下:

它会自动生成h1标签,这才是数据库真正存储的内容。

将字体变成红色

再次点击HTML

它会自动生成HTML代码

若是没有富文本编辑器,那么须要本身写html代码,太痛苦了!

那么利用富文本编辑器提供的这些标签,用户不须要本身写html代码,它就能自动生成相应的html标签以及样式

博客园使用的是TinyMCE编辑器,接下来咱们使用的是kindeditor编辑器

kindeditor使用

进入kindeditor官网:

http://kindeditor.net/demo.php

点击右侧的下载:

http://kindeditor.net/down.php

下载最新版本

下载后,解压压缩包,将kindeditor文件夹放到static目录

点击文档

http://kindeditor.net/doc.php

点击使用方法

http://kindeditor.net/docs/usage.html

修改add_article.html,复制上面的js代码,修改一下路径

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">标题</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>
                <div>
                    <ul> {% for cate in cate_list %} <li>{{ cate.title }}<input type="radio" name="cate" value="{{ cate.pk }}"></li> {% endfor %} </ul>
                    <hr>
                    <ul> {% for tag in tags %} <li>{{ tag.title }} <input type="checkbox" name="tags" value="{{ tag.pk }}"></li> {% endfor %} </ul>
                </div>
                <input type="submit" class="btn btn-default">

            </div>

        </div>
    </form> {#引入kindeditor编辑器#}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
    <script> KindEditor.ready(function (K) { {#create里面的值是textarea标签的id值#}
            window.editor = K.create('#article_content'); }); </script> {% endblock %}
View Code

刷新页面,效果以下:

此时的编辑框,是能够拖动的。若是不想让它能拖动,怎么办呢?

查看编辑器初始化参数文档

http://kindeditor.net/docs/option.html

固定宽高使用下面2个参数

拖动使用下面的参数

修改add_article.html

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">标题</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>
                <div>
                    <ul> {% for cate in cate_list %} <li>{{ cate.title }}<input type="radio" name="cate" value="{{ cate.pk }}"></li> {% endfor %} </ul>
                    <hr>
                    <ul> {% for tag in tags %} <li>{{ tag.title }} <input type="checkbox" name="tags" value="{{ tag.pk }}"></li> {% endfor %} </ul>
                </div>
                <input type="submit" class="btn btn-default">

            </div>

        </div>
    </form> {#引入kindeditor编辑器#}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
    <script> KindEditor.ready(function (K) { {#create里面的值是textarea标签的id值#}
            window.editor = K.create('#article_content', { {#固定宽高#}
                width: '100%', height: '600px', {#禁止拖动#}
 resizeType: 0, }); }); </script> {% endblock %}
View Code

刷新页面,效果以下:

这个参数,用来调整工具栏展现的。默认是展现全部,若是须要展现指定的,能够修改这个值。

好比博客园的评论框,只有6个按钮

功能对应表,在文档中都有,好比:

上传图片

默认点击上传图片后,它会一直转圈,提示正在上传。注意:等待是无心义的,它须要服务器返回一个json才会中止。

须要设置上传服务器的url才行。

默认使用的php,它提供了一些示例代码,好比php目录下的upload_json.php

还有其余语言的demo,好比asp、asp.net、jsp

由于没有提供python的,因此须要本身写了!

修改add_article.html,增长参数uploadJson,指定上传的请求url为upload

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">标题</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>
                <div>
                    <ul> {% for cate in cate_list %} <li>{{ cate.title }}<input type="radio" name="cate" value="{{ cate.pk }}"></li> {% endfor %} </ul>
                    <hr>
                    <ul> {% for tag in tags %} <li>{{ tag.title }} <input type="checkbox" name="tags" value="{{ tag.pk }}"></li> {% endfor %} </ul>
                </div>
                <input type="submit" class="btn btn-default">

            </div>

        </div>
    </form> {#引入kindeditor编辑器#}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
    <script> KindEditor.ready(function (K) { {#create里面的值是textarea标签的id值#}
            window.editor = K.create('#article_content', { {#固定宽高#}
                width: '100%', height: '600px', {#禁止拖动#}
 resizeType: 0, {#上传文件请求地址#}
                uploadJson:"/upload/", }); }); </script> {% endblock %}
View Code

修改urls.py,增长路径upload

"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """
from django.contrib import admin from django.urls import path,re_path from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('', views.index), #点赞或者踩灭
    path('digg/', views.digg), # 评论
    path('comment/', views.comment), # 后台管理
    path('backend/', views.backend), # 添加文章
    path('backend/add_article/', views.add_article), # 上传文件
    path('upload/', views.upload), #文章详情
    re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail), # 跳转
    re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite), # 我的站点
    re_path('(?P<username>\w+)/$', views.homesite), ]
View Code

修改views.py,增长视图函数upload

def upload(request): print(request.FILES)  # 打印文件对象
    return HttpResponse("ok")
View Code

刷新页面,上传一个图片

提示上传错误

为何会返回403呢?由于被django的csrf组件阻止了。

因为请求是kindeditor发的,它没有带csrf_token,因此被django给拦截了!

为了预防这种状况发生,kindeditor提供了extraFileUploadParams参数

修改add_article.html,在页面中的任意位置增长{% csrf_token %}

修改js代码

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">标题</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>
                <div>
                    <ul> {% for cate in cate_list %} <li>{{ cate.title }}<input type="radio" name="cate" value="{{ cate.pk }}"></li> {% endfor %} </ul>
                    <hr>
                    <ul> {% for tag in tags %} <li>{{ tag.title }} <input type="checkbox" name="tags" value="{{ tag.pk }}"></li> {% endfor %} </ul>
                </div>
                <input type="submit" class="btn btn-default">

            </div>

        </div>
    </form> {% csrf_token %} {#引入kindeditor编辑器#}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
    <script> KindEditor.ready(function (K) { {#create里面的值是textarea标签的id值#}
            window.editor = K.create('#article_content', { {#固定宽高#}
                width: '100%', height: '600px', {#禁止拖动#}
 resizeType: 0, {#上传文件请求地址#}
                uploadJson: "/upload/", {#添加别的参数csrfmiddlewaretoken#}
 extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, }); }); </script> {% endblock %}
View Code

从新上线,提示

查看Pycharm控制台输出:

<MultiValueDict: {'imgFile': [<InMemoryUploadedFile: 161022vkhyigaq4si947qv.jpg (image/jpeg)>]}>

它的key为imgFile,这个是kindeditor默认定义的form名称。

若是须要改动的话,增长一个参数filePostName

 

修改add_article.html

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">标题</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>
                <div>
                    <ul> {% for cate in cate_list %} <li>{{ cate.title }}<input type="radio" name="cate" value="{{ cate.pk }}"></li> {% endfor %} </ul>
                    <hr>
                    <ul> {% for tag in tags %} <li>{{ tag.title }} <input type="checkbox" name="tags" value="{{ tag.pk }}"></li> {% endfor %} </ul>
                </div>
                <input type="submit" class="btn btn-default">

            </div>

        </div>
    </form> {% csrf_token %} {#引入kindeditor编辑器#}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
    <script> KindEditor.ready(function (K) { {#create里面的值是textarea标签的id值#}
            window.editor = K.create('#article_content', { {#固定宽高#}
                width: '100%', height: '600px', {#禁止拖动#}
 resizeType: 0, {#上传文件请求地址#}
                uploadJson: "/upload/", {#添加别的参数csrfmiddlewaretoken#}
 extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, {#指定上传文件form名称#}
                filePostName:"upload_img" }); }); </script> {% endblock %}
View Code

修改upload视图函数,存储图片。注意导入os模块以及settings,完整代码以下:

from django.shortcuts import render, HttpResponse, redirect from django.contrib import auth from blog.models import Article, UserInfo, Blog, Category, Tag, ArticleUpDown, Comment from django.db.models import Sum, Avg, Max, Min, Count from django.db.models import F import json from django.http import JsonResponse from django.db import transaction import os from cnblog import settings  # 导入settings。注意:cnblog为项目名


# Create your views here.
def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") # 用户验证成功,返回user对象,不然返回None
        user = auth.authenticate(username=user, password=pwd) if user: # 登陆,注册session
            # 全局变量 request.user=当前登录对象(session中)
 auth.login(request, user) return redirect("/index/") return render(request, "login.html") def index(request): article_list = Article.objects.all() return render(request, "index.html", {"article_list": article_list}) def logout(request):  # 注销
 auth.logout(request) return redirect("/index/") def query_current_site(request, username):  # 查询当前站点的博客标题
    # 查询当前站点的用户对象
    user = UserInfo.objects.filter(username=username).first() if not user: return render(request, "not_found.html") # 查询当前站点对象
    blog = user.blog return blog def homesite(request, username, **kwargs):  # 我的站点主页
    print("kwargs", kwargs) blog = query_current_site(request, username) # 查询当前用户发布的全部文章
    if not kwargs: article_list = Article.objects.filter(user__username=username) else: condition = kwargs.get("condition") params = kwargs.get("params") # 判断分类、随笔、归档
        if condition == "category": article_list = Article.objects.filter(user__username=username).filter(category__title=params) elif condition == "tag": article_list = Article.objects.filter(user__username=username).filter(tags__title=params) else: year, month = params.split("/") article_list = Article.objects.filter(user__username=username).filter(create_time__year=year, create_time__month=month) return render(request, "homesite.html", {"blog": blog, "username": username, "article_list": article_list}) def article_detail(request,username,article_id): blog = query_current_site(request,username) #查询指定id的文章
    article_obj = Article.objects.filter(pk=article_id).first() user_id = UserInfo.objects.filter(username=username).first().nid comment_list = Comment.objects.filter(article_id=article_id) dict = {"blog":blog, "username":username, 'article_obj':article_obj, "user_id":user_id, "comment_list":comment_list, } return render(request,'article_detail.html',dict) def digg(request): print(request.POST) if request.method == "POST": # ajax发送的过来的true和false是字符串,使用json反序列化获得布尔值
        is_up = json.loads(request.POST.get("is_up")) article_id = request.POST.get("article_id") user_id = request.user.pk response = {"state": True, "msg": None}  # 初始状态
        # 判断当前登陆用户是否对这篇文章作过点赞或者踩灭操做
        obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first() if obj: response["state"] = False  # 更改状态
            response["handled"] = obj.is_up  # 获取以前的操做,返回true或者false
            print(obj.is_up) else: with transaction.atomic(): # 插入一条记录
                new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) if is_up:  # 判断为推荐
                    Article.objects.filter(pk=article_id).update(up_count=F("up_count") + 1) else:  # 反对
                    Article.objects.filter(pk=article_id).update(down_count=F("down_count") + 1) return JsonResponse(response) else: return HttpResponse("非法请求") def comment(request): print(request.POST) if request.method == "POST": # 获取数据
        user_id = request.user.pk article_id = request.POST.get("article_id") content = request.POST.get("content") pid = request.POST.get("pid") # 生成评论对象
        with transaction.atomic():  # 增长事务
            # 评论表增长一条记录
            comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 当前文章的评论数加1
            Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1) response = {"state": False}  # 初始状态

        if comment.user_id:  # 判断返回值
            response = {"state": True} # 响应体增长3个变量
        response["timer"] = comment.create_time.strftime("%Y-%m-%d %X") response["content"] = comment.content response["user"] = request.user.username return JsonResponse(response)  # 返回json对象

    else: return HttpResponse("非法请求") def backend(request): user = request.user #当前用户文章列表
    article_list = Article.objects.filter(user=user) # 由于是在templates的下一层,因此须要指定目录backend
    return render(request, "backend/backend.html", {"user":user,"article_list":article_list}) def add_article(request): return render(request, "backend/add_article.html") def upload(request): print(request.FILES) obj = request.FILES.get("upload_img")  # 获取文件对象
    name = obj.name  # 文件名
    #文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", name) with open(path, "wb") as f: for line in obj:  # 遍历文件对象
            f.write(line)  # 写入文件

    #必须返回这2个key
    res = { # 为0表示没有错误,若是有错误,设置为1。增长一个key为message,用来显示指定的错误
        "error": 0, # 图片访问路径,必须可以直接访问到
        "url": "/static/upload/" + name } return HttpResponse(json.dumps(res))  # 必须返回Json
View Code

在static目录下,建立目录upload

再次上传

效果以下:

 

点击图片,右键属性,能够调整像素

进入upload目录,能够看到一张图片

查看Html代码

这个图片路径,是能够直接访问的!

 

那么博客园也是这么作的,上传一个图片以后,查看html代码, 就能够直接访问图片。

 

图片连接,能够直接访问

 

增长文章类别和分类

修改add_article.html

{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">标题</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>
                <h4>分类</h4>
                <div class="form-group">
                <div class="radio"> {% for cate in cate_list %} <label><input type="radio" name="cate" value="{{ cate.pk }}">{{ cate.title }}</label> {% endfor %} </div>
                </div>
                <hr>
                <h4>标签</h4>
                <div class="checkbox"> {% for tag in tags %} <label>
                    <input type="checkbox" name="tags" value="{{ tag.pk }}">{{ tag.title }} </label> {% endfor %} </div>
                <hr>

                <input type="submit" class="btn btn-default">

            </div>

        </div>
    </form> {% csrf_token %} {#引入kindeditor编辑器#}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
    <script> KindEditor.ready(function (K) { {#create里面的值是textarea标签的id值#}
            window.editor = K.create('#article_content', { {#固定宽高#}
                width: '100%', height: '600px', {#禁止拖动#}
 resizeType: 0, {#上传文件请求地址#}
                uploadJson: "/upload/", {#添加别的参数csrfmiddlewaretoken#}
 extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, {#指定上传文件form名称#}
                filePostName:"upload_img" }); }); </script> {% endblock %}
View Code

修改add_article视图函数

from django.shortcuts import render,HttpResponse,redirect from django.contrib import auth from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown,Comment,Article2Tag from django.db.models import Sum,Avg,Max,Min,Count from django.db.models import F import json from django.http import JsonResponse from django.db import transaction from cnblog import settings  # 导入settings。注意:cnblog为项目名
import os # Create your views here.
def login(request): if request.method=="POST": user=request.POST.get("user") pwd=request.POST.get("pwd") # 用户验证成功,返回user对象,不然返回None
        user=auth.authenticate(username=user,password=pwd) if user: # 登陆,注册session
            # 全局变量 request.user=当前登录对象(session中)
 auth.login(request,user) return redirect("/index/") return render(request,"login.html") def index(request): article_list=Article.objects.all() return render(request,"index.html",{"article_list":article_list}) def logout(request):  # 注销
 auth.logout(request) return redirect("/index/") def query_current_site(request,username):  # 查询当前站点的博客标题
    # 查询当前站点的用户对象
    user = UserInfo.objects.filter(username=username).first() if not user: return render(request, "not_found.html") # 查询当前站点对象
    blog = user.blog return blog def homesite(request,username,**kwargs):  # 我的站点主页
    print("kwargs", kwargs) blog = query_current_site(request,username) # 查询当前用户发布的全部文章
    if not kwargs: article_list = Article.objects.filter(user__username=username) else: condition = kwargs.get("condition") params = kwargs.get("params") #判断分类、随笔、归档
        if condition == "category": article_list = Article.objects.filter(user__username=username).filter(category__title=params) elif condition == "tag": article_list = Article.objects.filter(user__username=username).filter(tags__title=params) else: year, month = params.split("/") article_list = Article.objects.filter(user__username=username).filter(create_time__year=year, create_time__month=month) return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list}) def article_detail(request,username,article_id): blog = query_current_site(request,username) #查询指定id的文章
    article_obj = Article.objects.filter(pk=article_id).first() user_id = UserInfo.objects.filter(username=username).first().nid comment_list = Comment.objects.filter(article_id=article_id) dict = {"blog":blog, "username":username, 'article_obj':article_obj, "user_id":user_id, "comment_list":comment_list, } return render(request,'article_detail.html',dict) def digg(request): print(request.POST) if request.method == "POST": #ajax发送的过来的true和false是字符串,使用json反序列化获得布尔值
        is_up = json.loads(request.POST.get("is_up")) article_id = request.POST.get("article_id") user_id = request.user.pk response = {"state": True, "msg": None}  # 初始状态
        #判断当前登陆用户是否对这篇文章作过点赞或者踩灭操做
        obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first() if obj: response["state"] = False  # 更改状态
            response["handled"] = obj.is_up  # 获取以前的操做,返回true或者false
            print(obj.is_up) else: with transaction.atomic(): #插入一条记录
                new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) if is_up: # 判断为推荐
                    Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1) else: # 反对
                    Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1) return JsonResponse(response) else: return HttpResponse("非法请求") def comment(request): print(request.POST) if request.method == "POST": # 获取数据
        user_id = request.user.pk article_id = request.POST.get("article_id") content = request.POST.get("content") pid = request.POST.get("pid") # 生成评论对象
        with transaction.atomic():  # 增长事务
            # 评论表增长一条记录
            comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 当前文章的评论数加1
            Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1) response = {"state": False}  # 初始状态

        if comment.user_id:  # 判断返回值
            response = {"state": True} #响应体增长3个变量
        response["timer"] = comment.create_time.strftime("%Y-%m-%d %X") response["content"] = comment.content response["user"] = request.user.username return JsonResponse(response)  # 返回json对象

    else: return HttpResponse("非法请求") def backend(request):  # 后台管理
    user = request.user #当前用户文章列表
    article_list = Article.objects.filter(user=user) # 由于是在templates的下一层,因此须要指定目录backend
    return render(request, "backend/backend.html", {"user":user,"article_list":article_list}) def add_article(request): if request.method=="POST": title=request.POST.get("title") content=request.POST.get("content") user=request.user cate_pk=request.POST.get("cate") tags_pk_list=request.POST.getlist("tags") # 切片文章文本
        desc=content[0:150] #文章描述
        #插入到Article表
        article_obj=Article.objects.create(title=title,content=content,user=user,category_id=cate_pk,desc=desc) for tag_pk in tags_pk_list:  #插入关系表
            #因为是中间模型,只能安装普通表查询才行
            Article2Tag.objects.create(article_id=article_obj.pk,tag_id=tag_pk) return redirect("/backend/")  # 跳转后台首页
    
    else: blog = request.user.blog cate_list = Category.objects.filter(blog=blog) tags = Tag.objects.filter(blog=blog) dict = { "blog":blog, "cate_list":cate_list, "tags":tags, } return render(request, "backend/add_article.html",dict) def upload(request): print(request.FILES) obj = request.FILES.get("upload_img")  # 获取文件对象
    name = obj.name  # 文件名
    #文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", name) with open(path, "wb") as f: for line in obj:  # 遍历文件对象
            f.write(line)  # 写入文件

    #必须返回这2个key
    res = { # 为0表示没有错误,若是有错误,设置为1。增长一个key为message,用来显示指定的错误
        "error": 0, # 图片访问路径,必须可以直接访问到
        "url": "/static/upload/" + name } return HttpResponse(json.dumps(res))  # 必须返回Json
View Code

注意:中间模型表Article2Tag不能直接用add,必须当作普通表处理才行!

 

刷新页面,添加一篇文章

 

提交以后,跳转后台管理页面

desc bug

首页查看

http://127.0.0.1:8000/

发现文章描述都是html标签

这是为何呢?由于desc是从文本框内容截取前150个字符串,因为内容都是html标签。因此就形成了上面的现象!

注意:index.html中的 {{article.desc}} 不能加sale,不然页面会错乱!

 

那么如何去掉html标签呢?使用bs4模块

4、bs4模块

bs4全名BeautifulSoup,是编写python爬虫经常使用库之一,主要用来解析html标签。

安装bs4,使用下面的命令安装

pip3 install bs4

新建一个test.py文件,内容以下:

from bs4 import BeautifulSoup s="<div><div>Hello</div><p>Yuan</p></div><a>click</a><script>alert(123)</script>" soup=BeautifulSoup(s,"html.parser") print(soup.text)
View Code

两个参数:第一个参数是要解析的html文本,第二个参数是使用那种解析器,对于HTML来说就是html.parser,这个是bs4自带的解析器。

执行输出:

HelloYuanclickalert(123)

 

经过bs4,就能够解析html了,而后截图150个字符串,就没有问题了!

修改add_article视图函数,注意导入BeautifulSoup模块

 

from django.shortcuts import render, HttpResponse, redirect from django.contrib import auth from blog.models import Article, UserInfo, Blog, Category, Tag, ArticleUpDown, Comment, Article2Tag from django.db.models import Sum, Avg, Max, Min, Count from django.db.models import F import json from django.http import JsonResponse from django.db import transaction from cnblog import settings  # 导入settings。注意:cnblog为项目名
import os from bs4 import BeautifulSoup # Create your views here.
def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") # 用户验证成功,返回user对象,不然返回None
        user = auth.authenticate(username=user, password=pwd) if user: # 登陆,注册session
            # 全局变量 request.user=当前登录对象(session中)
 auth.login(request, user) return redirect("/index/") return render(request, "login.html") def index(request): article_list = Article.objects.all() return render(request, "index.html", {"article_list": article_list}) def logout(request):  # 注销
 auth.logout(request) return redirect("/index/") def query_current_site(request, username):  # 查询当前站点的博客标题
    # 查询当前站点的用户对象
    user = UserInfo.objects.filter(username=username).first() if not user: return render(request, "not_found.html") # 查询当前站点对象
    blog = user.blog return blog def homesite(request, username, **kwargs):  # 我的站点主页
    print("kwargs", kwargs) blog = query_current_site(request, username) # 查询当前用户发布的全部文章
    if not kwargs: article_list = Article.objects.filter(user__username=username) else: condition = kwargs.get("condition") params = kwargs.get("params") # 判断分类、随笔、归档
        if condition == "category": article_list = Article.objects.filter(user__username=username).filter(category__title=params) elif condition == "tag": article_list = Article.objects.filter(user__username=username).filter(tags__title=params) else: year, month = params.split("/") article_list = Article.objects.filter(user__username=username).filter(create_time__year=year, create_time__month=month) return render(request, "homesite.html", {"blog": blog, "username": username, "article_list": article_list}) def article_detail(request, username, article_id): blog = query_current_site(request, username) # 查询指定id的文章
    article_obj = Article.objects.filter(pk=article_id).first() user_id = UserInfo.objects.filter(username=username).first().nid comment_list = Comment.objects.filter(article_id=article_id) dict = {"blog": blog, "username": username, 'article_obj': article_obj, "user_id": user_id, "comment_list": comment_list, } return render(request, 'article_detail.html', dict) def digg(request): print(request.POST) if request.method == "POST": # ajax发送的过来的true和false是字符串,使用json反序列化获得布尔值
        is_up = json.loads(request.POST.get("is_up")) article_id = request.POST.get("article_id") user_id = request.user.pk response = {"state": True, "msg": None}  # 初始状态
        # 判断当前登陆用户是否对这篇文章作过点赞或者踩灭操做
        obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first() if obj: response["state"] = False  # 更改状态
            response["handled"] = obj.is_up  # 获取以前的操做,返回true或者false
            print(obj.is_up) else: with transaction.atomic(): # 插入一条记录
                new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) if is_up:  # 判断为推荐
                    Article.objects.filter(pk=article_id).update(up_count=F("up_count") + 1) else:  # 反对
                    Article.objects.filter(pk=article_id).update(down_count=F("down_count") + 1) return JsonResponse(response) else: return HttpResponse("非法请求") def comment(request): print(request.POST) if request.method == "POST": # 获取数据
        user_id = request.user.pk article_id = request.POST.get("article_id") content = request.POST.get("content") pid = request.POST.get("pid") # 生成评论对象
        with transaction.atomic():  # 增长事务
            # 评论表增长一条记录
            comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 当前文章的评论数加1
            Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1) response = {"state": False}  # 初始状态

        if comment.user_id:  # 判断返回值
            response = {"state": True} # 响应体增长3个变量
        response["timer"] = comment.create_time.strftime("%Y-%m-%d %X") response["content"] = comment.content response["user"] = request.user.username return JsonResponse(response)  # 返回json对象

    else: return HttpResponse("非法请求") def backend(request):  # 后台管理
    user = request.user # 当前用户文章列表
    article_list = Article.objects.filter(user=user) # 由于是在templates的下一层,因此须要指定目录backend
    return render(request, "backend/backend.html", {"user": user, "article_list": article_list}) def add_article(request): if request.method=="POST": title=request.POST.get("title") content=request.POST.get("content") user=request.user cate_pk=request.POST.get("cate") tags_pk_list=request.POST.getlist("tags") #使用BeautifulSoup过滤html标签
        soup = BeautifulSoup(content, "html.parser") # 切片文章文本
        desc=soup.text[0:150] #文章描述
        #插入到Article表
        article_obj=Article.objects.create(title=title,content=content,user=user,category_id=cate_pk,desc=desc) for tag_pk in tags_pk_list:  #插入关系表
            #因为是中间模型,只能安装普通表查询才行
            Article2Tag.objects.create(article_id=article_obj.pk,tag_id=tag_pk) return redirect("/backend/")  # 跳转后台首页

    else: blog = request.user.blog cate_list = Category.objects.filter(blog=blog) tags = Tag.objects.filter(blog=blog) dict = { "blog":blog, "cate_list":cate_list, "tags":tags, } return render(request, "backend/add_article.html",dict) def upload(request): print(request.FILES) obj = request.FILES.get("upload_img")  # 获取文件对象
    name = obj.name  # 文件名
    # 文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", name) with open(path, "wb") as f: for line in obj:  # 遍历文件对象
            f.write(line)  # 写入文件

    # 必须返回这2个key
    res = { # 为0表示没有错误,若是有错误,设置为1。增长一个key为message,用来显示指定的错误
        "error": 0, # 图片访问路径,必须可以直接访问到
        "url": "/static/upload/" + name } return HttpResponse(json.dumps(res))  # 必须返回Json
View Code

 

删除刚才那篇文章,注意:使用admin后台删除,直接删除表记录,有外键关联!

刷新页面,从新添加一篇文章

刷新首页,查看最后一篇文章

数据安全

上面添加一篇文件,看着貌似没啥问题。那是由于没有包含js代码,若是有xss攻击呢?

添加一篇文章

提交以后,刷新首页

点击文章标题

发现直接弹框了,这是不安全的!

 

怎么解决这个问题呢?

应该入库的时候,就把script标签直接删掉!

BeautifulSoup还有一个功能,能够获得html的标签名,好比下面一段python代码:

decompose()方法将当前节点移除文档树并彻底销毁

from bs4 import BeautifulSoup s="<div><div>Hello</div><p>Yuan</p></div><a>click</a><script>alert(123)</script>" soup=BeautifulSoup(s,"html.parser")  # 解析Html

for tag in soup.find_all(): print(tag.name)  # 打印标签名

    if tag.name=="script":  # 判断标签名为script
        tag.decompose()  # 移出标签

print(str(soup))
View Code

执行输出:

div
div
p
a
script
<div><div>Hello</div><p>Yuan</p></div><a>click</a>

能够看到最后的script标签被移除掉了!

 

修改add_article视图函数,完整代码以下:

from django.shortcuts import render,HttpResponse,redirect from django.contrib import auth from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown,Comment,Article2Tag from django.db.models import Sum,Avg,Max,Min,Count from django.db.models import F import json from django.http import JsonResponse from django.db import transaction from cnblog import settings  # 导入settings。注意:cnblog为项目名
import os from bs4 import BeautifulSoup # Create your views here.
def login(request): if request.method=="POST": user=request.POST.get("user") pwd=request.POST.get("pwd") # 用户验证成功,返回user对象,不然返回None
        user=auth.authenticate(username=user,password=pwd) if user: # 登陆,注册session
            # 全局变量 request.user=当前登录对象(session中)
 auth.login(request,user) return redirect("/index/") return render(request,"login.html") def index(request): article_list=Article.objects.all() return render(request,"index.html",{"article_list":article_list}) def logout(request):  # 注销
 auth.logout(request) return redirect("/index/") def query_current_site(request,username):  # 查询当前站点的博客标题
    # 查询当前站点的用户对象
    user = UserInfo.objects.filter(username=username).first() if not user: return render(request, "not_found.html") # 查询当前站点对象
    blog = user.blog return blog def homesite(request,username,**kwargs):  # 我的站点主页
    print("kwargs", kwargs) blog = query_current_site(request,username) # 查询当前用户发布的全部文章
    if not kwargs: article_list = Article.objects.filter(user__username=username) else: condition = kwargs.get("condition") params = kwargs.get("params") #判断分类、随笔、归档
        if condition == "category": article_list = Article.objects.filter(user__username=username).filter(category__title=params) elif condition == "tag": article_list = Article.objects.filter(user__username=username).filter(tags__title=params) else: year, month = params.split("/") article_list = Article.objects.filter(user__username=username).filter(create_time__year=year, create_time__month=month) return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list}) def article_detail(request,username,article_id): blog = query_current_site(request,username) #查询指定id的文章
    article_obj = Article.objects.filter(pk=article_id).first() user_id = UserInfo.objects.filter(username=username).first().nid comment_list = Comment.objects.filter(article_id=article_id) dict = {"blog":blog, "username":username, 'article_obj':article_obj, "user_id":user_id, "comment_list":comment_list, } return render(request,'article_detail.html',dict) def digg(request): print(request.POST) if request.method == "POST": #ajax发送的过来的true和false是字符串,使用json反序列化获得布尔值
        is_up = json.loads(request.POST.get("is_up")) article_id = request.POST.get("article_id") user_id = request.user.pk response = {"state": True, "msg": None}  # 初始状态
        #判断当前登陆用户是否对这篇文章作过点赞或者踩灭操做
        obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first() if obj: response["state"] = False  # 更改状态
            response["handled"] = obj.is_up  # 获取以前的操做,返回true或者false
            print(obj.is_up) else: with transaction.atomic(): #插入一条记录
                new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) if is_up: # 判断为推荐
                    Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1) else: # 反对
                    Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1) return JsonResponse(response) else: return HttpResponse("非法请求") def comment(request): print(request.POST) if request.method == "POST": # 获取数据
        user_id = request.user.pk article_id = request.POST.get("article_id") content = request.POST.get("content") pid = request.POST.get("pid") # 生成评论对象
        with transaction.atomic():  # 增长事务
            # 评论表增长一条记录
            comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 当前文章的评论数加1
            Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1) response = {"state": False}  # 初始状态

        if comment.user_id:  # 判断返回值
            response = {"state": True} #响应体增长3个变量
        response["timer"] = comment.create_time.strftime("%Y-%m-%d %X") response["content"] = comment.content response["user"] = request.user.username return JsonResponse(response)  # 返回json对象

    else: return HttpResponse("非法请求") def backend(request):  # 后台管理
    user = request.user #当前用户文章列表
    article_list = Article.objects.filter(user=user) # 由于是在templates的下一层,因此须要指定目录backend
    return render(request, "backend/backend.html", {"user":user,"article_list":article_list}) def add_article(request): if request.method=="POST": title=request.POST.get("title") content=request.POST.get("content") user=request.user cate_pk=request.POST.get("cate") tags_pk_list=request.POST.getlist("tags") #使用BeautifulSoup过滤html标签
        soup = BeautifulSoup(content, "html.parser") # 文章过滤:
        for tag in soup.find_all(): # print(tag.name)
            if tag.name in ["script", ]:  # 包含script标签时
 tag.decompose() # 切片文章文本
        desc=soup.text[0:150] #文章描述
        #插入到Article表,注意content=str(soup)
        article_obj=Article.objects.create(title=title,content=str(soup),user=user,category_id=cate_pk,desc=desc) for tag_pk in tags_pk_list:  #插入关系表
            #因为是中间模型,只能安装普通表查询才行
            Article2Tag.objects.create(article_id=article_obj.pk,tag_id=tag_pk) return redirect("/backend/")  # 跳转后台首页

    else: blog = request.user.blog cate_list = Category.objects.filter(blog=blog) tags = Tag.objects.filter(blog=blog) dict = { "blog":blog, "cate_list":cate_list, "tags":tags, } return render(request, "backend/add_article.html",dict) def upload(request): print(request.FILES) obj = request.FILES.get("upload_img")  # 获取文件对象
    name = obj.name  # 文件名
    #文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", name) with open(path, "wb") as f: for line in obj:  # 遍历文件对象
            f.write(line)  # 写入文件

    #必须返回这2个key
    res = { # 为0表示没有错误,若是有错误,设置为1。增长一个key为message,用来显示指定的错误
        "error": 0, # 图片访问路径,必须可以直接访问到
        "url": "/static/upload/" + name } return HttpResponse(json.dumps(res))  # 必须返回Json
View Code

数据库删除刚才那篇文章

刷新页面,从新添加一篇文章

注意:第一行是一个script标签

 

提交以后,查看这一篇文章

点击文章标题

发现没有弹框了,它将script标签转义了

 

做业:

1 系统首页的文章渲染 2 我的站点的查询和跳转 3 文章详情页的渲染 4 点赞 5 处理根评论 6 后台管理 --- 首页 --- 添加文章 ----利用BS模块防护xss攻击 延伸: 1 基于Ajax的登录 2 基于AJax注册功能(文件上传) --- 预备:基于ajax上传文件

 

答案:

完整代码已上传至github,地址以下:

https://github.com/py3study/cnblog

实现的功能以下

1. 注册页面 /zhuce/  基于ajax+form组件 2. 登陆页面 /login/ 基于ajax 3. 后台页面 /backend/
4. 后台能实现文章、分类、标签的增删改查,部分功能基于ajax 5. 登陆以后,页面右上角有进入后台,注销等功能 6. 不能直接访问后台页面,必需要登陆。不然会有弹框提示!

后台效果以下:

相关文章
相关标签/搜索