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

时间:2021-12-22 16:11:22

一、子评论

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

定位输入框

focus

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

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

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

修改article_detail.html,增加一段回复的js

{% 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=""
value="{{ request.user.username }}"></p>
<div>
<textarea name="" id="comment_content" cols="" rows=""></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 %}

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

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

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

增加@评论用户

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

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

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

$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=""
value="{{ request.user.username }}"></p>
<div>
<textarea name="" id="comment_content" cols="" rows=""></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 %}

重新刷新页面,并点击回复,效果如下:

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

首页增加连接

修改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="" height=""
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>

效果如下:

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

点击文章标题,可以进入文章详情页。

提交子评论

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

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

为什么呢?因为前台和后端,没有做判断!

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=""
value="{{ request.user.username }}"></p>
<div>
<textarea name="" id="comment_content" cols="" rows=""></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 %}

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

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

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

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

正式提交子评论

修改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=""
value="{{ request.user.username }}"></p>
<div>
<textarea name="" id="comment_content" cols="" rows=""></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 %}

提交一条子评论

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

效果如下:

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

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

nid对应的是parent_comment_id

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

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

去掉@用户名

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

使用切片?比如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>

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

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

可以直接得到评论内容,说明是可行的!

修改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=""
value="{{ request.user.username }}"></p>
<div>
<textarea name="" id="comment_content" cols="" rows=""></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 %}

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

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

刷新网页,效果如下:

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

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

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

展示子评论效果

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

所以需要修改页面,判断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=""
value="{{ request.user.username }}"></p>
<div>
<textarea name="" id="comment_content" cols="" rows=""></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 %}

删除有@xiao的表记录

重新刷新页面,效果如下:

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

二、后台管理页面

主要做以下2部分:

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

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

修改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="" height=""
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>

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

那么它如何区分每一个用户呢?使用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),
]

修改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})

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

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

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

http://127.0.0.1:8000/backend/

效果如下:

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

三、富文本编辑器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),
]

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

def add_article(request):
return render(request, "backend/add_article.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"> <div>
<textarea name="content" id="article_content" cols="" rows=""></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 %}

访问增加文章页面

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

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

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

这还只是一个标题,如果有很多内容呢?用户会疯掉的!

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

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

点击HTML按钮,效果如下:

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

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

将字体变成红色

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

再次点击HTML

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

它会自动生成HTML代码

如果没有富文本编辑器,那么需要自己写html代码,太痛苦了!

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

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

kindeditor使用

进入kindeditor官网:

http://kindeditor.net/demo.php

点击右侧的下载:

http://kindeditor.net/down.php

下载最新版本

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

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

点击文档

http://kindeditor.net/doc.php

点击使用方法

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

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

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

修改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="" rows=""></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 %}

刷新页面,效果如下:

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

此时的编辑框,是可以拖动的。如果不想让它能拖动,怎么办呢?

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

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

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

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

拖动使用下面的参数

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

修改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="" rows=""></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 %}

刷新页面,效果如下:

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

这个参数,用来调整工具栏展示的。默认是展示所有,如果需要展示指定的,可以修改这个值。

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

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

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

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

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

上传图片

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

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

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

默认使用的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="" rows=""></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 %}

修改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),
]

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

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

刷新页面,上传一个图片

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

提示上传错误

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

为什么会返回403呢?因为被django的csrf组件阻止了。

由于请求是kindeditor发的,它没有带csrf_token,所以被django给拦截了!

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

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

修改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="" rows=""></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 %}

重新上线,提示

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

查看Pycharm控制台输出:

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

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

如果需要改动的话,增加一个参数filePostName

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

修改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="" rows=""></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 %}

修改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

在static目录下,创建目录upload

再次上传

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

效果如下:

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

点击图片,右键属性,可以调整像素

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

进入upload目录,可以看到一张图片

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

查看Html代码

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

这个图片路径,是可以直接访问的!

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

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

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

图片链接,可以直接访问

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

增加文章类别和分类

修改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="" rows=""></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 %}

修改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

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

刷新页面,添加一篇文章

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

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

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

desc bug

首页查看

http://127.0.0.1:8000/

发现文章描述都是html标签

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

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

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

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

四、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)

两个参数:第一个参数是要解析的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

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

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

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

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

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

数据安全

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

添加一篇文章

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

提交之后,刷新首页

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

点击文章标题

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

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

怎么解决这个问题呢?

应该入库的时候,就把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))

执行输出:

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

数据库删除刚才那篇文章

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

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

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

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

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

点击文章标题

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

发现没有弹框了,它将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. 不能直接访问后台页面,必须要登录。否则会有弹框提示!

后台效果如下:

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