Flask Vue.js全栈开发|第6章:博客文章CURD与Markdown

  • 原创
  • Madman
  • /
  • /
  • 13
  • 18982 次阅读

flask vuejs 全栈开发-min.png

Synopsis: 介绍了 SQLAlchemy 一对多关系以及如何实现级联删除,Post API 设计跟 User 基本类似。前端要支持 Markdown 的话,首先需要给用户提供一个编辑器,这里使用 bootstrap-markdown 插件;渲染也由前端完成,使用 vue-markdown,代码语法高亮使用 highlight.js 插件。博客 CURD 的实现,修改时使用 vue-sweetalert2 弹出确认框,分页栏的生成请查看代码

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

1. 数据库

1.1 声明模型

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

class Post(PaginatedAPIMixin, db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255))
    summary = db.Column(db.Text)
    body = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    views = db.Column(db.Integer, default=0)

    def __repr__(self):
        return '<Post {}>'.format(self.title)

1.2 一对多(one-to-many)关系

每个用户可以发布多篇博客,User 与 Post 是一对多关系,示意图如下:

1 User与Post一对多

修改 back-end/app/models.py,在 User 类中增加:

class User(PaginatedAPIMixin, db.Model):
    ...
    # 反向引用,直接查询出当前用户的所有博客文章; 同时,Post实例中会有 author 属性
    # cascade 用于级联删除,当删除user时,该user下面的所有posts都会被级联删除
    posts = db.relationship('Post', backref='author', lazy='dynamic',
                            cascade='all, delete-orphan')

在 Post 类中增加:

class Post(PaginatedAPIMixin, db.Model):
    ...
    # 外键, 直接操纵数据库当user下面有posts时不允许删除user,下面仅仅是 ORM-level “delete” cascade
    # db.ForeignKey('users.id', ondelete='CASCADE') 会同时在数据库中指定 FOREIGN KEY level “ON DELETE” cascade
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))

迁移数据库:

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

参数说明:

db.relationship()posts 属性指向 Post 类并加载多篇博客,实现一对多关系;更多关系 API 请参考: https://docs.sqlalchemy.org/en/latest/orm/relationship_api.html

backref='author' 会在 Post 类上声明一个新的属性 author,即可以使用 post.author 来获取该博客的作者对象

lazy 决定了 SQLAlchemy 什么时候从数据库中加载数据:

  • select: (默认值)SQLAlchemy 会使用一个标准的 select 语句必要时一次性加载数据,即 user.posts 会直接返回包含该用户的所有博客对象的列表
(venv) D:\python-code\flask-vuejs-madblog\back-end>flask shell
>>> user = User.query.filter_by(username='wangy8961').first()
>>> user
<User wangy8961>
>>> user.posts
[<Post 1>, <Post 2>, <Post 3>]
  • joined: 告诉 SQLAlchemy 使用 JOIN 语句作为父级在同一查询中来加载关系
  • subquery: 类似 joined ,但是 SQLAlchemy 会使用子查询
  • dynamic: 在有多条数据的时候是特别有用,它不是直接加载这些数据,SQLAlchemy 会返回一个查询对象 BaseQuery,在加载数据前您可以过滤(提取)它们
(venv) D:\python-code\flask-vuejs-madblog\back-end>flask shell
>>> user = User.query.filter_by(username='wangy8961').first()
>>> user.posts
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x04567E70>

cascade='all, delete-orphan' 用于级联删除,当删除user时,该user下面的所有posts都会被级联删除,详情参考: https://docs.sqlalchemy.org/en/latest/orm/cascades.html

2. RESTful API设计

我们的 博客文章资源 将提供以下几个 API:

HTTP方法 资源URL 说明
GET /api/posts 返回所有文章的集合
POST /api/posts 添加一篇新文章
GET /api/posts/<id> 返回一篇文章
PUT /api/posts/<id> 修改一篇文章
DELETE /api/posts/<id> 删除一篇文章

创建 app/api/posts.py

from flask import request, jsonify, url_for, g
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


@bp.route('/posts', methods=['POST'])
@token_auth.login_required
def create_post():
    '''添加一篇新文章'''
    pass

@bp.route('/posts', methods=['GET'])
def get_posts():
    '''返回文章集合,分页'''
    pass

@bp.route('/posts/<int:id>', methods=['GET'])
def get_post(id):
    '''返回一篇文章'''
    pass

@bp.route('/posts/<int:id>', methods=['PUT'])
@token_auth.login_required
def update_post(id):
    '''修改一篇文章'''
    pass

@bp.route('/posts/<int:id>', methods=['DELETE'])
@token_auth.login_required
def delete_post(id):
    '''删除一篇文章'''
    pass

3.1 添加文章

只有通过 Token 认证的用户才能发表文章:

@bp.route('/posts', methods=['POST'])
@token_auth.login_required
def create_post():
    '''添加一篇新文章'''
    data = request.get_json()
    if not data:
        return bad_request('You must post JSON data.')
    message = {}
    if 'title' not in data or not data.get('title'):
        message['title'] = 'Title is required.'
    elif len(data.get('title')) > 255:
        message['title'] = 'Title must less than 255 characters.'
    if 'body' not in data or not data.get('body'):
        message['body'] = 'Body is required.'
    if message:
        return bad_request(message)

    post = Post()
    post.from_dict(data)
    post.author = g.current_user  # 通过 auth.py 中 verify_token() 传递过来的(同一个request中,需要先进行 Token 认证)
    db.session.add(post)
    db.session.commit()
    response = jsonify(post.
                                
                            
  • 113479570
  • 126039858
  • zhuyulin
  • ygren
  • wulvtutu
  • helloworld2000
  • sunny
  • 99857443
  • nickzxhfjsm
  • que-ai2023
  • anthony-577518830
  • team12
  • zhangsan
  • reinxd
  • 11757580
  • qiu-chen-100
  • 61450478
  • 19979360
  • 93176860
  • 96768625
  • luohuai1q84
  • binrr
  • 102157531
  • jjzzxx000
未经允许不得转载: LIFE & SHARE - 王颜公子 » Flask Vue.js全栈开发|第6章:博客文章CURD与Markdown

分享

作者

作者头像

Madman

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

13 条评论

doit2025
doit2025

赞一个,照着作者代码写,跑到这里没有出现任何问题(本人是java全栈转Python)

Madman
Madman doit2025 Author

多谢您的意见,每一章主题我都是先写代码验证通过后,再写文章重写代码提交到git仓库的

Doyus
Doyus
(flaskWeb) C:\Users\Administrator\Desktop\flask-vuejs-madblog-0.6\back-end>flask db upgrade
d:\wxx\flaskweb\lib\site-packages\flask_sqlalchemy\__init__.py:774: UserWarning: Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. Defaulting SQLALCHEMY_DATABASE_URI to "sq
lite:///:memory:".
  'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. '
d:\wxx\flaskweb\lib\site-packages\flask_sqlalchemy\__init__.py:794: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in
the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 5dfdc31936de, add posts table

您好~有个小问题~这一节跑的时候一直报这个错误,对于源代码未曾改动,有什么建议么

capricorn_bu
capricorn_bu Doyus

同问这个问题,求教这个是啥问题。

CastielQAQ
CastielQAQ

请问下,bootstrap-markdown编辑器中的预览无法正确加载出加粗格式,请问是什么原因呢

Madman
Madman CastielQAQ Author

加粗的语法是 **haha** 检查插件的版本

bhb603
bhb603

能解析HTML 吗

bhb603
bhb603

<meta charset="UTF-8"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" name="viewport"> <meta content="ie=edge" http-equiv="X-UA-Compatible"> <title>Document</title>

看看能不能解析出来

bhb603
bhb603
<meta charset="utf-8"> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <link href="<%= BASE_URL %>favicon.ico" rel="icon"> <title><%= webpackConfig.name %></title> <noscript> We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue. </noscript>

测试测试测试

cuojue
cuojue

大佬,请问编辑器想添加图片应该怎么改进呢

cuojue
cuojue cuojue

本地图片

Madman
Madman cuojue Author

markdown中图片都是链接,可以使用微博/七牛云等图床。或者后端提供静态文件上传和访问接口,将图片上传后保存在本地指定目录下,返回一个访问URL,再在markdown中使用这个URL

这种是手动的,可以查看markdown编辑器的文档,实现点击上传图片按钮时自动调你的后端接口,从而自动生成类似 ![示例图片](http://127.0.0.1:5000/api/v1/files/demo.jpg)

cuojue
cuojue Madman

多谢大佬

专题系列