Flask Vue.js全栈开发|第6章:博客文章CURD与Markdown
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 是一对多关系,示意图如下:
修改 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.
13 条评论
评论者的用户名
评论时间doit2025
2019-11-20T02:15:02Z赞一个,照着作者代码写,跑到这里没有出现任何问题(本人是java全栈转Python)
Madman doit2025 Author
2019-11-20T02:20:31Z多谢您的意见,每一章主题我都是先写代码验证通过后,再写文章重写代码提交到git仓库的
Doyus
2020-01-07T06:25:48Z您好~有个小问题~这一节跑的时候一直报这个错误,对于源代码未曾改动,有什么建议么
capricorn_bu Doyus
2020-04-24T03:44:11Z同问这个问题,求教这个是啥问题。
CastielQAQ
2020-03-07T08:08:32Z请问下,bootstrap-markdown编辑器中的预览无法正确加载出加粗格式,请问是什么原因呢
Madman CastielQAQ Author
2020-03-07T10:51:57Z加粗的语法是
**haha**
检查插件的版本bhb603
2020-03-15T12:21:58Z能解析HTML 吗
bhb603
2020-03-15T12:23:56Z<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
2020-03-15T12:44:54Z测试测试测试
cuojue
2020-09-02T03:15:41Z大佬,请问编辑器想添加图片应该怎么改进呢
cuojue cuojue
2020-09-02T03:16:40Z本地图片
Madman cuojue Author
2020-09-02T03:27:26Zmarkdown中图片都是链接,可以使用微博/七牛云等图床。或者后端提供静态文件上传和访问接口,将图片上传后保存在本地指定目录下,返回一个访问URL,再在markdown中使用这个URL
这种是手动的,可以查看markdown编辑器的文档,实现点击上传图片按钮时自动调你的后端接口,从而自动生成类似
![示例图片](http://127.0.0.1:5000/api/v1/files/demo.jpg)
cuojue Madman
2020-09-02T04:23:09Z多谢大佬