Flask Vue.js全栈开发|第9章:用户评论

  • 原创
  • Madman
  • /
  • /
  • 5
  • 2725 次阅读

flask vuejs 全栈开发-min.png

Synopsis: 用户在浏览你的博客文章后,可能会在底下评论,增加互动性。一篇文章允许有多条评论,一个用户可以在多篇文章下面发表评论,所以都是一对多关系。同时,评论支持多级回复,支持点赞功能

代码已上传到 https://github.com/wangy8961/flask-vuejs-madblog/tree/v0.9 ,欢迎star

1. 数据库

1.1 声明模型

Post 与 Comment 是一对多关系,同时 User 与 Comment 也是一对多关系。登录的用户除了可以在文章下面发表评论以外,还可以 回复 别人的评论,所以评论模型是 自引用的一对多 关系,同时支持 级联删除

修改 back-end/app/models.py,增加 Comment 数据模型:

class User(PaginatedAPIMixin, db.Model):
    ...
    comments = db.relationship('Comment', backref='author', lazy='dynamic',
                               cascade='all, delete-orphan')


class Post(PaginatedAPIMixin, db.Model):
    ...
    comments = db.relationship('Comment', backref='post', lazy='dynamic',
                               cascade='all, delete-orphan')


class Comment(PaginatedAPIMixin, db.Model):
    __tablename__ = 'comments'
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    mark_read = db.Column(db.Boolean, default=False)  # 文章作者会收到评论提醒,可以标为已读
    disabled = db.Column(db.Boolean, default=False)  # 屏蔽显示
    # 外键,评论作者的 id
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    # 外键,评论所属文章的 id
    post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
    # 自引用的多级评论实现
    parent_id = db.Column(db.Integer, db.ForeignKey('comments.id', ondelete='CASCADE'))
    # 级联删除的 cascade 必须定义在 "多" 的那一侧,所以不能这样定义: parent = db.relationship('Comment', backref='children', remote_side=[id], cascade='all, delete-orphan')
    parent = db.relationship('Comment', backref=db.backref('children', cascade='all, delete-orphan'), remote_side=[id])

    def __repr__(self):
        return '<Comment {}>'.format(self.id)

    def get_descendants(self):
        '''获取一级评论的所有子孙'''
        data = set()

        def descendants(comment):
            if comment.children:
                data.update(comment.children)
                for child in comment.children:
                    descendants(child)
        descendants(self)
        return data

1.2 迁移脚本

(venv) D:\python-code\flask-vuejs-madblog\back-end>flask db migrate -m "add comments table"
(venv) D:\python-code\flask-vuejs-madblog\back-end>flask db upgrade

2. RESTful API设计

没什么好说的,跟 Post 非常类似,创建 app/api/comments.py

from flask import request, jsonify, url_for, g, current_app
from app.api import bp
from app.api.auth import token_auth
from app.api.errors import error_response, bad_request
from app.extensions import db
from app.models import Post, Comment


@bp.route('/comments/', methods=['POST'])
@token_auth.login_required
def create_comment():
    '''在某篇博客文章下面发表新评论'''
    data = request.get_json()
    if not data:
        return bad_request('You must post JSON data.')
    if 'body' not in data or not data.get('body').strip():
        return bad_request('Body is required.')
    if 'post_id' not in data or not data.get('post_id'):
        return bad_request('Post id is required.')

    post = Post.query.get_or_404(int(data.get('post_id')))
    comment = Comment()
    comment.from_dict(data)
    comment.author = g.current_user
    comment.post = post
    db.session.add(comment)
    db.session.commit()
    response = jsonify(comment.to_dict())
    response.status_code = 201
    # HTTP协议要求201响应包含一个值为新资源URL的Location头部
    response.headers['Location'] = url_for('api.get_comment', id=comment.id)
    return response


@bp.route('/comments/', methods=['GET'])
@token_auth.login_required
def get_comments():
    '''返回评论集合,分页'''
    page = request.args.get('page', 1, type=int)
    per_page = min(
        request.args.get(
            'per_page', current_app.config['COMMENTS_PER_PAGE'], type=int), 100)
    data = Comment.to_collection_dict(
        Comment.query.order_by(Comment.timestamp.desc()), page, per_page,
        'api.get_comments')
    return jsonify(data)


@bp.route('/comments/<int:id>', methods=['GET'])
@token_auth.login_required
def get_comment(id):
    '''返回单个评论'''
    comment = Comment.query.get_or_404(id)
    return jsonify(comment.to_dict())


@bp.route('/comments/<int:id>', methods=['PUT'])
@token_auth.login_required
def update_comment(id):
    '''修改单个评论'''
    comment = Comment.query.get_or_404(id)
    if g.current_user != comment.author and g.current_user != comment.post.author:
        return error_response(403)
    data = request.get_json()
    if not data:
        return bad_request('You must post JSON data.')
    # if 'body' not in data or not data.get('body'):
    #     return bad_request('Body is required.')
    comment.from_dict(data)
    db.session.commit()
    return jsonify(comment.to_dict())


@bp.route('/comments/<int:id>', methods=['DELETE'])
@token_auth.login_required
def delete_comment(id):
    '''删除单个评论'''
    comment = Comment.query.get_or_404(id)
    if g.current_user != comment.author and g.current_user != comment.post.author:
        return error_response(403)
    db.session.delete(comment)
    db.session.commit()
    return '', 204

然后,修改 app/api/__init__.py 导入 comments.py

from flask import Blueprint

bp = Blueprint('api', __name__)

# 写在最后是为了防止循环导入,ping.py文件也会导入 bp
from app.api import ping, tokens, errors, users, posts, comments

3. 用户评论 CURD

3.1 多级评论展示

后端需要增加一个 API,只返回该文章下面的 顶层评论,每个 顶层评论 下面如果有子孙评论,则附加到 descendants 属性上,这是为了前端展示多级评论时,只显示两层,免得缩进太多就不好看了

修改back-end/api/posts.py

@bp.route('/posts/<int:id>/comments/', methods=['GET'])
def get_post_comments(id):
    '''返回当前文章下面的一级评论'''
    post = Post.query.get_or_404(id)
    page = request.args.get('page', 1, type=int)
    per_page = min(
        request.args.get(
            'per_page', current_app.config['COMMENTS_PER_PAGE'], type=int), 100)
    # 先获取一级评论
    
                                
                            
未经允许不得转载: LIFE & SHARE - 王颜公子 » Flask Vue.js全栈开发|第9章:用户评论

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

5 条评论

杨凯
杨凯

试一试

杨凯
杨凯 杨凯

二层楼

杨凯
杨凯 杨凯

自己跟自己回复

杨凯
杨凯 杨凯

继续回复

杨凯
杨凯

试一试

专题系列