DAY14 新闻评论的实现
新闻评论
页面评论框显示
今天我们来实现新闻评论,评论只有登录用户可以进行,当用户没有登录时,是不能进行的。
第一步获取用户登录状态并进行评论框不同的展示
当用户未登录时评论框显示的是下面的内容,当用户点击时弹出登录框提示用户进行登录

当用户登录时,这时用户就可以进行评论的操作:

这个展示判断的条件还是用户登录状态的判断,且页面在新闻详情页,我们之前就进行过用户状态获取的判断,并传入了前台页面的数据user 进行判断
这里直接改前端就好了。只是评论框的修改,评论的内容展示放在后面
前端对应模块的修改:

完成之后的测试就用户登录和退出是否展示对应的评论框就可以了
评论数据后台代码
这是个新的接口所以我们按照接口设计来写新的视图,还是写在news模块的views里
,收藏的后面
接口设计图:

首先还是建立对应的视图函数,指定请求方式为post,和文档注释模块功能的描述

完成还后还是进行分析并在注释里分步骤写上我们思路。先简单写下思路吧。
首先评论是用户登录后才能进行,首先还是对用户登录状态进行判断,然后是接收参数
news_id: 新闻的id,用来维持评论和新闻的关系,也就是确认评论在哪条新闻中进行
comment : 评论,我们需要将用户输入的评论保存到对应的评论表中,并加入news_id的外键关联和用户ID的关联,评论是用户在对应的新闻完成的操作。
parent_id:父评论,存在评论表里的字段,只有当用户在已有的评论里进行评论时,那么存入表中的评论就会加上这个字段,也就是获取已有评论的id并将它加入到父评论字段里面来建立关系
前端获取到后来进行对应的展示。
数据的校验,是否传齐,数据类型是否正确,查询数据库对应的数据是否存在等
校验完成后往表中存入评论,分为有父评和没有父评的情况
最后返回结果,根据我们的结果来进行回调函数的使用
完整步骤图:

第一步
# 1. 获取用户登录状态
user = g.user
if not user:
return jsonify(errno=RET.SESSIONERR,errmsg="用户未登录")
通过装饰器来进行user的获取,也就是用户的登录状态和数据查询。并进行判断返回符合接口规范的json数据
第二步
# 2. 接收参数 (news_id,comment,parent_id)
news_id = request.json.get("news_id")
comment = request.json.get("comment")
parent_id = request.json.get("parent_id")
传入的是json数据,安装对应的格式编写代码就可以了
第三步
# 3.校验参数
# 3.1 校验参数是否齐全
if not all([news_id,comment]):
return jsonify(errno=RET.PARAMERR,errmsg="参数缺失")
# 3.2 校验 news_id ,parent_id数据类型
try:
news_id = int(news_id)
if parent_id:
parent_id = int(parent_id)
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR,errmsg="参数错误")
# 3.3 校验新闻是否存在
try:
news = News.query.filter_by(id = news_id).first()
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="数据读取失败")
if not news :
return jsonify(errno=RET.NODATA,errmsg="新闻不存在")
# 3.4 校验父评论是否存在
if parent_id : #只有当传了parent_id,也就是有值时进行判断
try:
parent_comment = Comment.query.filter_by(id = parent_id).first()
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="数据读取失败")
if not parent_comment : #对查询结果进行判断
return jsonify(errno=RET.NODATA,errmsg="评论未找到")
首先判断接口中必须要传的参数是否齐全,然后进行数据类型的校验,通过在异常捕捉try里面强转来进行,并对异常作出处理
将需要查询数据库的查询操作放在最后,首先进行查询,最后再对查询的结果进行判断,不要忘记对结果进行判断,第三方数据库查询必须进行
异常捕捉。
第四步
# 4. 对评论进行操作
new_comment = Comment() #实例化新的评论对象
new_comment.user_id = user.id #添加评论用户
new_comment.news_id = news.id #添加对应的新闻
new_comment.content = comment #添加评论内容
if parent_id : #父评只有当有值时进行
new_comment.parent_id = parent_id
try:
db.session.add(new_comment)
db.session.commit()
except Exception as e :
current_app.logger.error(e)
db.session.rollback() #事物回滚
return jsonify(errno=RET.DBERR,errmsg="评论失败")
我们需要先从评论类里实例化新的评论对象,然后进行数据的添加,这里建议将评论类里的所有字段复制过来,一一查看并作出添加,就不会出现有字段遗忘没有添加的情况
这里我我们需要将发表评论的用户和对应的新闻ID进行添加,还有评论的内容,父评只有当有值时进行,操作数据库进行异常捕捉,添加数据进行事物回滚
第五步
# 5.返回数据
return jsonify(errno=RET.OK,errmsg="成功",data = new_comment.to_dict()) #返回评论的字典数据
这里除了返回json数据外我们还需要将评论的字典数据进行返回,让前端进行相关内容的评价展示(后面会补上前端代码),建议以后都创建一个将对象属性转换为字典数据的函数
对应的函数代码格式:

前端代码
前端js代码
没有父评
// 评论提交
$(".comment_form").submit(function (e) {
e.preventDefault();
var news_id = $(this).attr('data-newsid')
var news_comment = $(".comment_input").val();
if (!news_comment) {
alert('请输入评论内容');
return
}
var params = {
"news_id": news_id,
"comment": news_comment #没有parent_id
};
$.ajax({
url: "/news/news_comment",
type: "post",
contentType: "application/json",
headers: {
"X-CSRFToken": getCookie("csrf_token")
},
data: JSON.stringify(params),
success: function (resp) {
if (resp.errno == '0') {
var comment = resp.data #传的字典数据
// 拼接内容
var comment_html = ''
comment_html += '<div class="comment_list">'
comment_html += '<div class="person_pic fl">'
if (comment.user.avatar_url) {
comment_html += '<img src="' + comment.user.avatar_url + '" alt="用户图标">'
}else {
comment_html += '<img src="../../static/news/images/person01.png" alt="用户图标">'
}
comment_html += '</div>'
comment_html += '<div class="user_name fl">' + comment.user.nick_name + '</div>'
comment_html += '<div class="comment_text fl">'
comment_html += comment.content
comment_html += '</div>'
comment_html += '<div class="comment_time fl">' + comment.create_time + '</div>'
comment_html += '<a href="javascript:;" class="comment_up fr" data-commentid="' + comment.id + '" data-newsid="' + comment.news_id + '">赞</a>'
comment_html += '<a href="javascript:;" class="comment_reply fr">回复</a>'
comment_html += '<form class="reply_form fl" data-commentid="' + comment.id + '" data-newsid="' + news_id + '">'
comment_html += '<textarea class="reply_input"></textarea>'
comment_html += '<input type="button" value="回复" class="reply_sub fr">'
comment_html += '<input type="reset" name="" value="取消" class="reply_cancel fr">'
comment_html += '</form>'
comment_html += '</div>'
// 拼接到内容的前面
$(".comment_list_con").prepend(comment_html)
// 让comment_sub 失去焦点
$('.comment_sub').blur();
// 清空输入框内容
$(".comment_input").val("")
// 更新评论条数
updateCommentCount()
}else {
alert(resp.errmsg)
}
}
})
});
$('.comment_list_con').delegate('a,input','click',function(){
var sHandler = $(this).prop('class');
if(sHandler.indexOf('comment_reply')>=0)
{
$(this).next().toggle();
}
if(sHandler.indexOf('reply_cancel')>=0)
{
$(this).parent().toggle();
}
有父评(在已有的评论里进行评论)
// TODO : 回复评论
if(sHandler.indexOf('reply_sub')>=0)
{
var $this = $(this)
var news_id = $this.parent().attr('data-newsid')
var parent_id = $this.parent().attr('data-commentid')
var comment = $this.prev().val()
if (!comment) {
alert('请输入评论内容')
return
}
var params = {
"news_id": news_id,
"comment": comment,
"parent_id": parent_id #有parent_id
}
$.ajax({
url: "/news/news_comment",
type: "post",
contentType: "application/json",
headers: {
"X-CSRFToken": getCookie("csrf_token")
},
data: JSON.stringify(params),
success: function (resp) {
if (resp.errno == "0") {
var comment = resp.data #我们传入的字典数据
// 拼接内容
var comment_html = ""
comment_html += '<div class="comment_list">'
comment_html += '<div class="person_pic fl">'
if (comment.user.avatar_url) {
comment_html += '<img src="' + comment.user.avatar_url + '" alt="用户图标">'
}else {
comment_html += '<img src="../../static/news/images/person01.png" alt="用户图标">'
}
comment_html += '</div>'
comment_html += '<div class="user_name fl">' + comment.user.nick_name + '</div>'
comment_html += '<div class="comment_text fl">'
comment_html += comment.content
comment_html += '</div>'
comment_html += '<div class="reply_text_con fl">'
comment_html += '<div class="user_name2">' + comment.parent.user.nick_name + '</div>'
comment_html += '<div class="reply_text">'
comment_html += comment.parent.content
comment_html += '</div>'
comment_html += '</div>'
comment_html += '<div class="comment_time fl">' + comment.create_time + '</div>'
comment_html += '<a href="javascript:;" class="comment_up fr" data-commentid="' + comment.id + '" data-newsid="' + comment.news_id + '">赞</a>'
comment_html += '<a href="javascript:;" class="comment_reply fr">回复</a>'
comment_html += '<form class="reply_form fl" data-commentid="' + comment.id + '" data-newsid="' + news_id + '">'
comment_html += '<textarea class="reply_input"></textarea>'
comment_html += '<input type="button" value="回复" class="reply_sub fr">'
comment_html += '<input type="reset" name="" value="取消" class="reply_cancel fr">'
comment_html += '</form>'
comment_html += '</div>'
$(".comment_list_con").prepend(comment_html)
// 请空输入框
$this.prev().val('')
// 关闭
$this.parent().hide()
// 更新评论条数
updateCommentCount()
}else {
alert(resp.errmsg)
}
}
})
}
})
页面评论展示
后台注释和代码:

#5.评论相关展示
comments = []
try:
comments = Comment.query.filter_by(news_id=news.id).order_by(Comment.create_time.desc()).all()
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="评论读取异常")
comments = [comment.to_dict() for comment in comments] #将对象进行字典数据的转换
# 生成保存用户变量的上下文并传到前台进行相关判断处理
context = {
"user": user,
"news_click_list": news_click_list,
"news":news.to_dict(),
"is_collected":is_collected,
"comments" : comments
}
这里就讲一下查询,其他的和之前的大致是一样的
我们需要通过新闻的id 来进行查询,查询评论中所有新闻id一样的评论来进行展示,并且展示的内容是通过时间的顺序来进行的。所有的评论执行器是all(),最新的在最上面也就是降序(desc()).
前端HTML文件:
<div class="comment_count">
{{ news.comments_count }}条评论 #新闻的关联查询字段,来获取评论数
</div>
<div class="comment_list_con">
{% for comment in comments %}
<div class="comment_list">
<div class="person_pic fl">
<img src="{% if comment.user.avatar_url %}
{{ comment.user.avatar_url }}
{% else %}
../../static/news/images/person01.png
{% endif %}" alt="用户图标">
</div>
<div class="user_name fl">{{ comment.user.nick_name }}</div>
<div class="comment_text fl">{{ comment.content }}</div>
{% if comment.parent %}
<div class="reply_text_con fl">
<div class="user_name2">{{ comment.parent.user.nick_name }}</div>
<div class="reply_text">
{{ comment.parent.content }}
</div>
</div>
{% endif %}
<div class="comment_time fl">{{ comment.create_time }}</div>
{# <a href="javascript:;" class="comment_up {% if comment.is_like %}has_comment_up{% endif %} fr" data-commentid="{{ comment.id }}" data-newsid="{{ comment.news_id }}">赞</a>#}
<a href="javascript:;" class="comment_up
{% if comment.is_like %}
has_comment_up
{% endif %} fr"
data-commentid="{{ comment.id }}"
data-likecount="{{ comment.like_count }}"
data-newsid="{{ news.id }}">
{% if comment.like_count > 0 %}
{{ comment.like_count }}
{% else %}
赞
{% endif %}
</a>
<a href="javascript:;" class="comment_reply fr">回复</a>
<form class="reply_form fl" data-commentid="{{ comment.id }}" data-newsid="{{ news.id }}">
<textarea class="reply_input"></textarea>
<input type="button" value="回复" class="reply_sub fr">
<input type="reset" name="" value="取消" class="reply_cancel fr">
</form>
</div>
{% endfor %}
</div>
新闻类关联字段,新闻查询时可以查到所有的评论,数量在转换字典中进行查询展示
字段

数量(.count())

后台完整代码:
@news_blue.route("/news_comment",methods = ["POST"])
@user_login_data
def news_comment():
"""
新闻评论
1. 获取用户登录状态
2. 接收参数 (news_id,comment,parent_id)
3.校验参数
3.1 校验参数是否齐全
3.2 校验 news_id ,parent_id)数据类型
3.3 校验新闻和父评论是否存在
3.4 校验父评论是否存在
4. 创建评论数据
5.返回数据
:return:
"""
# 1. 获取用户登录状态
user = g.user
if not user:
return jsonify(errno=RET.SESSIONERR,errmsg="用户未登录")
# 2. 接收参数 (news_id,comment,parent_id)
news_id = request.json.get("news_id")
comment = request.json.get("comment")
parent_id = request.json.get("parent_id")
# 3.校验参数
# 3.1 校验参数是否齐全
if not all([news_id,comment]):
return jsonify(errno=RET.PARAMERR,errmsg="参数缺失")
# 3.2 校验 news_id ,parent_id数据类型
try:
news_id = int(news_id)
if parent_id:
parent_id = int(parent_id)
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR,errmsg="参数错误")
# 3.3 校验新闻是否存在
try:
news = News.query.filter_by(id = news_id).first()
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="数据读取失败")
if not news :
return jsonify(errno=RET.NODATA,errmsg="新闻不存在")
# 3.4 校验父评论是否存在
if parent_id :
try:
parent_comment = Comment.query.filter_by(id = parent_id).first()
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="数据读取失败")
if not parent_comment :
return jsonify(errno=RET.NODATA,errmsg="评论未找到")
# 4. 对评论进行操作
new_comment = Comment()
new_comment.user_id = user.id
new_comment.news_id = news.id
new_comment.content = comment
if parent_id :
new_comment.parent_id = parent_id
try:
db.session.add(new_comment)
db.session.commit()
except Exception as e :
current_app.logger.error(e)
db.session.rollback()
return jsonify(errno=RET.DBERR,errmsg="评论失败")
# 5.返回数据
return jsonify(errno=RET.OK,errmsg="成功",data = new_comment.to_dict())

浙公网安备 33010602011771号