使用Flask搭建一个校园论坛8-帖子评论

2021-04-11 22:46:25 1157

简介

上一节的更新在一个月之前了,最近工作事情比较多, 而且生活中也有很多琐碎的事情,所以一直没有更新。在上一节中我们实现了帖子详情页面,论坛的主要目的就是给大家提供一个交流的平台,本节就讲述如何实现帖子评论系统。

1.功能简介

所有公开的论坛,都会提供用户评论以及用户回复的功能,本节讲述如何实现用户评论帖子、用户回复帖子以及一些其他的功能。

2.数据表设计

每篇帖子任何用户都可以在其下方留下评论,因此,帖子对于评论的关系是一个一对多的关系,如下图所示,这样当我们加载帖子的时候,就可以通过帖子的id获取其全部的评论。

在很多论坛中,我们可以看到用户可以在任何帖子下方回复任何评论,每条评论我都们需要发送评论的是谁,同时如果是回复某条评论,我们也需要知道回复的是谁,因此我们可以通过自关联的方式来实现,评论表的设计如下图所示

有了上面的思路,我们就可以设计评论表的,在bbs/models.py模块中加入下面的代码

class Comments(db.Model):
    __tablename__ = 't_comments'

    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
    body = db.Column(db.Text)
    timestamps = db.Column(db.DATETIME, default=datetime.datetime.now)

    replied_id = db.Column(db.INTEGER, db.ForeignKey('t_comments.id'))
    author_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))
    post_id = db.Column(db.INTEGER, db.ForeignKey('t_post.id'))
    delete_flag = db.Column(db.INTEGER, default=0, comment='is it delete? 0: no 1: yes')

    post = db.relationship('Post', back_populates='comments')
    author = db.relationship('User', back_populates='comments')
    replies = db.relationship('Comments', back_populates='replied', cascade='all')
    replied = db.relationship('Comments', back_populates='replies', remote_side=[id])

    def can_delete(self):
        return self.author_id == current_user.id

上面的代码中,我们定义了评论表的相关字段,其中delete_flag字段为当前评论的状态,如果等于1则表示当前评论已经被作者删除了。这里的删除是软删除,就是将其状态置为某个标志,并不是真正的在数据库中清除掉。同时我们定义了一个实例函数can_delete此函数的主要作用是用来判断当前评论删除的权限,如果当前评论的作者id等于当前登录用户的id,则可以删除此条评论,反之则不能删除。

3.评论帖子

帖子的评论的实现主要是前后端的配合,我们先来实现前端的评论输入。

a.评论输入框

用户需要评论帖子则需要一个评论的入口,所以我们需要在帖子详情页面下方加上用户输入评论的区域,打开bbs/templates/frontend/post/read-post.html,添加下面的关键代码

<div class="post-div post-comment">
    <p id="commentPosition"></p>
    {% if current_user.is_authenticated %}
        <div>
            <ul class="nav nav-pills " role="tablist">
                <li class="nav-item">
                    <a class="nav-link active" data-toggle="pill" href="#addComment"><i class="fa fa-commenting mr-2"></i>评论</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" onclick="getMarkDownData()" data-toggle="pill" href="#previewComment"><i class="fa fa-print mr-2"></i>预览</a>
                </li>
            </ul>
            <div class="tab-content">
                <div id="addComment" class="tab-pane active">
                    <textarea onkeydown="tab(this)" class="form-control mt-2 report-textarea" style="height: 150px!important;" id="commentContent" placeholder="请输入评论内容"></textarea>
                    <div class="d-flex mt-1">
                        <a class="mb-1 text-decoration-none mr-2 a-link" href="#" id="commentEmoji" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                            <i class="fa fa-smile-o mr-1"></i>表情
                        </a>
                        <div class="dropdown-menu" id="emoji-list" aria-labelledby="commentEmoji">
                            {% for emoji_url in emoji_urls %}
                                <div style="padding:3px">
                                    {% for emoji in emoji_url %}
                                        <button class="btn p-1">
                                            <img class="img-emoji"
                                                 src="{{ url_for('static', filename='emojis/'+emoji[0]) }}"
                                                 data-toggle="tooltip" data-placement="right"
                                                 title="{{ emoji.1 }}" alt="{{ emoji.1 }}"
                                                 data-emoji=":{{ emoji.1 }}:">
                                        </button>
                                    {% endfor %}
                                </div>
                            {% endfor %}
                        </div>
                        <a onclick="upload()" class="mb-1 text-decoration-none mr-2 a-link span-hand"><i class="fa fa-photo mr-1"></i>图片</a>
                        <input type="file" id="uploadInput" onchange="uploadImage()" accept=".png" hidden="hidden">
                        <a href="#" class="mb-1 text-decoration-none mr-2 a-link" data-toggle="modal" data-target="#markdownHelp"><i class="fa fa-file mr-1"></i>帮助</a>
                        <p class="flex-grow-1 text-right mb-1 p-error-hint">请输入评论内容!</p>
                    </div>
                    <div class="d-flex flex-row-reverse">
                        <button class="btn btn-info" id="commentBtn" onclick="postComment()">评论</button>
                        <button hidden="hidden" id="replyBtn" onclick="replyComment()" class="btn btn-success mt-2">回复</button>
                        <button hidden="hidden" id="cancleReplyBtn" onclick="cancleReply()" class="btn btn-danger mt-2 mr-2">取消</button>
                        <a id="replyUserP" hidden="hidden" class="p-reply flex-grow-1 text-decoration-none"></a>
                    </div>
                </div>
                <!-- 评论预览界面 -->
                <div id="previewComment" class="tab-pane fade">
                    <div id="previewHtml" class="mt-2" style="min-height: 50px">

                    </div>
                </div>
            </div>
        </div>
    {% else %}
        <div class="text-center">
            <div class="card-body text-center m-2 m-md-3 f-16" id="no-editor">
                <div>您尚未登录,
                    <a href="/auth/login/"><span class="badge badge-info">登录</span></a> 或
                    <a href="/auth/register/"><span class="badge badge-success">注册</span></a> 后评论
                </div>
            </div>
        </div>
    {% endif %}
</div>

在帖子的详情页页面,我们就可以看到如下所示的评论输入框

b.处理用户评论与回复

在用户在帖子详情页面输入了评论内容之后,点击评论按钮就要将相关参数信息发送到后台服务器中,将这些信息处理、存储在对应的数据库表中,大致流程如下图所示

打开bbs/frontend/post.py模块中加入下面的代码

@post_bp.route('/post-comment/', methods=['POST'])
@login_required
@statistic_traffic(db, CommentStatistic)
def post_comment():
    comment_content = request.form.get('commentContent')
    post_id = request.form.get('postId')
    post = Post.query.get_or_404(post_id)
    comment_content = to_html(comment_content)
    com = Comments(body=comment_content, post_id=post_id, author_id=current_user.id)
    # 如果评论帖子用户与发帖用户不为同一人则发送消息通知
    if current_user.id != post.author_id:
        notice = Notification(target_id=post_id, target_name=post.title, send_user=current_user.username,
                              receive_id=post.author_id, msg=comment_content)
        db.session.add(notice)
    post.update_time = datetime.datetime.now()
    db.session.add(com)
    db.session.commit()
    return jsonify({'tag': 1})

上面的代码逻辑基本上按照图上的流程所进行的,因为用户可以回复其他用户的评论,在代码中还加入通知被回复用户的逻辑。

c.提交评论回复

在写完后端逻辑之后,我们还需要在帖子详情页面中加入一些js代码来实现与后端的交互,打开bbs/templates/frontend/post/read-post.html文件,在JavaScript标签块中加入下面的代码

// 刷新页面清除保存在sessionStorage中的原始数据
$(function () {
    sessionStorage.setItem('md', '');
})

function cancleReply() {
    $("#commentBtn").removeAttr('hidden');
    $("#replyBtn").attr('hidden', 'hidden');
    $("#cancleReplyBtn").attr('hidden', 'hidden');
    $("#replyUserP").html('');
    $("#replyUserP").attr('hidden', 'hidden');
}

function replyComment() {
    comment = isEmpty();
    if (!comment){
        return false;
    }
    commentId = sessionStorage.getItem('commentId');
    commentUserId = sessionStorage.getItem('commentUserId');
    postId = $("#postTitle").data('id');
    $.ajax({
        type: "post",
        url: "/post/reply-comment/",
        data: {"post_id":postId, "comment_id": commentId, "comment_user_id": commentUserId, "comment": comment},
        success: function (res) {
            window.location.reload();
        }
    })
}

function reply(commentId, commentUser, commentUserId) {
    $("#commentBtn").attr('hidden', 'hidden');
    $("#replyBtn").removeAttr('hidden');
    $("#cancleReplyBtn").removeAttr('hidden');
    $("#replyUserP").removeAttr('hidden');
    $("#replyUserP").html('@'+commentUser);
    $("#replyUserP").attr('href', '/profile/user/'+commentUserId+'/');
    sessionStorage.setItem('commentId', commentId);
    sessionStorage.setItem('commentUserId', commentUserId);
    $('html,body').animate({ scrollTop: $("#commentPosition").offset().top - 100 }, 200)
}

// 提交评论
function postComment() {
    comment = isEmpty();
    if (!comment){
        return false;
    }
    let postId = $("#postTitle").data("id");
    $.ajax({
        type:"post",
        data: {"commentContent": comment, 'postId': postId},
        url: "/post/post-comment/",
        success: function (res) {
            window.location.reload();
        }
    })
}

上面的JavaScript代码主要是用来后端模块进行交互,这里就不进行详细解释,整个帖子的评论系统就完成了。

再次提醒!博客文章中的代码我只截取的关键代码不分,很多细节代码没有贴出来,如果想要在本地机器上运行调试可以访问我的github仓库或者gitee仓库仓库,如果想要看到实际效果请访问二狗学院