接口概念
IOP:面向接口编程,不再关注具体的实现;只关注输入、输出。
http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html
服务器返回数据类型:
网页数据html,为浏览器使用
Json数据,ajax javascript发请求的一种方式;也可以使用request的python请求方式
为移动端编写接口关注:
接口地址是什么:/xxx/yyy/zzz
接口需要什么参数:参数根据业务场景
返回什么格式的数据:大多数是json数据
RestfulAPI:
一种软件架构风格、设计风格、而不是标准,只是提供了一组设计原则和约束条件。它主要用户客户端和服务器交互类的软件。
基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。REST全程是Representational State Transfer,表征性状态转移。
首次在2000年Roy Thomas Fielding的博士论文中出现,Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者,
Apache服务器软件的作者之一,Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了广泛的关注。
理解RESTful
介绍:https://github.com/RockTeach/PythonCourse/blob/master/web/flask/restful.md
要理解RESTful架构,最好的就是去理解它的单词 Representational State Transfer 到底是什么意思,它的每一个词到底要表达什么。
REST的释义,"(资源的)表现层状态转化",其实这省略了主语。“表现层”其实指的是“资源(Resource)”的“表现层”。
状态码
服务器向用户返回的状态码和提示信息,常见的有以下一些地方
- 200:OK - [GET]:服务器成功返回用户请求的数据
- 201:CREATED -[POST/PUT/PATCH]:用户新建或修改数据成功
- 202:Accepted - [*] :表示一个请求已经进入后台排队(异步任务)
- 204:NO CONTENT - [DELETE]:表示数据删除成功
- 400:INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误
- 401:Unauthorized - [*] :表示用户没有权限(令牌,用户名,密码错误)
- 403:Forbidden - [*]:表示用户得到授权,但是访问是被禁止的
- 404:NOT FOUND - [*]:用户发出的请求针对的是不存在的记录
- 406:Not Acceptable - [*]:用户请求格式不可得
- 410:Gone - [GET] :用户请求的资源被永久移除,且不会再得到的
- 422:Unprocesable entity -[POST/PUT/PATCH]:当创建一个对象时,发生一个验证错误
- 500:INTERNAL SERVER EROR - [*] :服务器内部发生错误
所谓“资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。
它可以是一段文本,一张图片,一首歌曲,一种服务,总之就是一个具体的实例。
你可以使用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。
要获取这个资源,访问它的URI就可以了,因此URI就成了每一个资源的地址或独一无二的识别符。所谓“上网”就是与互联网上一系列的“资源”互动,调用它们的URI。
“资源”是一种信息实体,它可以有多种外在表现形式。我们把“资源”具体呈现出来的形式,叫做它的”表现层“(Representation)。
URI只代表资源的实体,不代表它的形式。严格地说,有些网站最后的”.html“后缀名是不必要的,因为这个后缀表示格式,属于”表现层“范畴,而URI应该只代表”资源“的位置。
它的具体表现形式,应该在HTTP请求头的信息中使用Accept和Content-Type字段指定。
访问一个网站,就代表客户端和服务端的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务端。
因此,如果客户端想要操作服务器,就必须通过某种手段,让服务器端发生”状态转换(State Transfer)“。
而这种转换是建立在表现层之上的,所以就是”表现层状态转化“。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议中,四个表示操作方式的动词:GET,POST,PUT,DELETE。
它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可用于更新资源),PUT用来更新资源,DELETE用来删除资源
总结 :
- 每一个URI代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过四个HTTP动词,对服务端资源进行操作,实现”表现层状态转换“
- 同一个url针对用户的不同的请求操作,表现出来的状态是不同的。表现出来的多种形式,就是表现层状态转换。
-
CS,客户端和服务端这种架构模型中。
-
表现层状态转换
-
主语 (资源)
-
URI 每个URI代表一种资源
-
资源展现给我们的形式就叫做表现层
-
通过HTTP的请求谓词来实现表现层转换
-
-
重要概念
-
URI、HTTP请求谓词、JSON
注意:
postman/前端 在向服务器提交json数据时,需要声明提交的类型。在postman的请求的headers增加content-type:application/json。
flask 在确认请求数据是通过json提交后,会将json字符产转换成 字典。保存在request.json中
FBV简单体验:
- views.py
1 from flask import Blueprint, request 2 3 restful_bp = Blueprint("restful_bp",__name__) 4 """ 5 动作 URL 状态码 数据库操作 含义 6 GET /posts 200 SELECT 从 blog_posts 表中查询一组数据 7 POST /posts 201 INSERT 向 blog_posts 表中插入一条数据 8 9 GET /posts/123 200 SELECT 从 blog_posts 表中查询 id 为123 的记录 10 PUT /posts/123 200 UPDATE 更新 blog_posts 表中 id 为123 的记录(请求时提供全部字段的更新) 11 PATCH /posts/123 200 UPDATE 更新 blog_posts 表中 id 为123 的记录(请求时提供部分字段的更新) 12 DELETE /posts/123 204 DELETE 删除 blog_posts 表中 id 为123 的记录 13 """
14 @restful_bp.route("/posts",methods = ["POST","GET"]) 15 def post_list(): 16 if request.method == "POST": 17 return "向 blog_posts 表中插入一条数据",201 # 201 表示创建成功 18 elif request.method == "GET": 19 return "从 blog_posts 表中查询一组数据",200 20 21 22 @restful_bp.route("/posts/<post_id>",methods=["GET","POST","PUT","PATCH","DELETE"]) 23 def post_detail(post_id): 24 if request.method == "GET": 25 return "从 blog_posts 表中查询 id 为{} 的记录".format(post_id),200 26 elif request.method == "PUT": 27 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供全部字段的更新)".format(post_id),200 28 elif request.method == "PATCH": 29 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供部分字段的更新)".format(post_id),200 30 elif request.method == "DELETE": 31 return "删除 blog_posts 表中 id 为{} 的记录".format(post_id),204 # 204 表示删除成功
- models.py
1 from flask_sqlalchemy import SQLAlchemy 2 3 db = SQLAlchemy() 4 5 class Channel(db.Model): 6 __tablename__ = "channels" 7 8 id = db.Column(db.Integer,primary_key=True) 9 name = db.Column(db.String(16),unique=True,nullable=False) 10 sort = db.Column(db.Integer,nullable=False) 11 # 对象方法。将类的对象形式转化成字典形式 12 def to_dict(self): 13 return { 14 'id': self.id, 15 'name': self.name, 16 'sort': self.sort, 17 }
FBV实现:创建POST | 查询GET
- views.py
- POST:http://127.0.0.1:5000/channel_dict
- GET:http://127.0.0.1:5000/channel_dict
1 @restful_bp.route('/channel_dict/',methods=["GET","POST"]) 2 def channel_list(): 3 if request.method == 'POST': 4 # # 结果:b'{ "xe5x90x8dxe5xadx97":"xe7xa7x91xe6x8ax80", "sort":1 }' 5 # # 需在postman中设置一下请求头headers。Content-Type:application/json 6 # print(request.data) 7 # # 结果:None 8 # print(request.json) 9 if 'name' not in request.json or 'sort' not in request.json: 10 return abort(400) 11 12 channel = Channel() 13 channel.name = request.json['name'] 14 channel.sort = request.json['sort'] 15 16 db.session.add(channel) 17 db.session.commit() 18 19 return jsonify({'channel':channel.to_dict()}),201 20 21 elif request.method == "GET": 22 # 数据库查询的返回的是list类型的数据 23 _channels = Channel.query.all() 24 # 将数据库中查询出的对象 转化成 字典形式 25 channels = [channel.to_dict() for channel in _channels] 26 # flask视图函数只能返回 str 或 response 对象。可以将数据库查询出来的转化成json,以满足flask视图函数的返回要求 27 ret = { 28 'channels':channels 29 } 30 # import json 31 # json_str = json.dumps(ret) 32 # return json_str 33 return jsonify(ret)
FBV实现:GET | PUT | PATCH | DELETE
- views.py:http://127.0.0.1:5000/channels/2
1 @restful_bp.route("/channels/<channel_id>",methods=["GET","POST","PUT","PATCH","DELETE"]) 2 def channel_detail(channel_id):
# 根据条件查询对象 3 channel = Channel.query.get(channel_id) 4 5 # 获取信息:http://127.0.0.1:5000/channel 6 if request.method == "GET": 7 return jsonify({'channel':channel.to_dict()}),200 8 9 # 更新数据:全量更新 10 elif request.method == "PUT": 11 if 'name' not in request.json or 'sort' not in request.json: 12 return abort(400) 13 channel.name = request.json['name'] 14 channel.sort = request.json['sort'] 15 db.session.add(channel) 16 db.session.commit() 17 return jsonify({'channel':channel.to_dict()}),200 18 19 # 更新数据:差量更新 20 elif request.method == 'PATCH': 21 # name = channel.name 22 # if 'name' in request.json: 23 # name = request.json['name'] 24 # channel.name = name 25 # 等价于 上四行代码。如果有数据更新就更新{先从json中获取数据,如果有就赋值给channel.name},没有数据更新就用原来的当做默认值 26 channel.name = request.json.get('name',channel.name) 27 channel.sort = request.json.get('sort',channel.sort) 28 db.session.commit() 29 return jsonify({'channel':channel.to_dict()}),200 30 31 # 删除数据。删除成功后返回的是空数据。 32 elif request.method == "DELETE": 33 db.session.delete(channel) 34 db.session.commit() 35 return "",204
Flask-RestfulApi
框架优点:
- 会让代码逻辑更加清晰,不会再有过多的if-else语句
- 框架会提供参数的验证。像form一样提供验证数据的功能
- 框架会提供丰富的输出格式,自定义输出的结构
框架注意:⚠️
- flask-restful 返回字典。框架内部自动转换为json。return {'channels':channels},200
- 如果在扩展文件ext.py实例化和注册路由资源,必须在加载时就注册好,不可以在调用函数懒加载时注册
- 根据请求的同名方法执行同名的视图函数,来完成不同的请求。request.method.lower() 获取请求方式然后将其变成小写
CBV简单体验:pip install Flask-RESTful
# method = request.method # method = lower(method) # 获取请求方式后将其转化为小写,然后去类中匹配,如果有同名的函数,就执行相应的请求方法函数 # Resource 是一个资源类,其实就是一个url # Resource 的父类MethodView是flask自己的views中的一个类,不同Resource我们自己也可以完成各种请求。 # Resource 的父类MethodView也是根据不同的请求方法执行不同的请求函数,来完成各种请求操作 # 源码变小写 meth = getattr(self, request.method.lower(), None)。getattr是从一个实例中将类中的一个方法取出来赋值给一个变量,执行这个变量就是执行此函数方法
CBV类视图
1 from flask import Flask 2 from flask_restful import Api, Resource 3 4 app = Flask(__name__) 5 api = Api(app)
6 """ 7 动作 URL 状态码 数据库操作 含义 8 GET /posts 200 SELECT 从 blog_posts 表中查询一组数据 9 POST /posts 201 INSERT 向 blog_posts 表中插入一条数据 10 """ 11 class PostList(Resource): 12 """ 13 flask-restful 的所有请求是通过 类 来处理的。继承自Resource类。 14 flask-restful 框架内部会自动根据请求的HTTP METHOD调用同名的实例方法 15 GET: /posts -> def get(self): pass 16 POST:/posts -> def post(self): pass 17 """ 18 def get(self): 19 return "从 blog_posts 表中查询一组数据",200 20 21 def post(self): 22 return "向 blog_posts 表中插入一条数据",201 23 24 25 """ 26 动作 URL 状态码 数据库操作 含义 27 GET /posts/123 200 SELECT 从 blog_posts 表中查询 id 为123 的记录 28 PUT /posts/123 200 UPDATE 更新 blog_posts 表中 id 为123 的记录(请求时提供全部字段的更新) 29 PATCH /posts/123 200 UPDATE 更新 blog_posts 表中 id 为123 的记录(请求时提供部分字段的更新) 30 DELETE /posts/123 204 DELETE 删除 blog_posts 表中 id 为123 的记录 31 """ 32 class PostDetail(Resource): 33 """ 34 flask-restful 框架内部也支持 URL 上配置的路由参数,路由参数会传递到具体处理请求的实例方法中 35 """ 36 def get(self,post_id): 37 return "从 blog_posts 表中查询 id 为{} 的记录".format(post_id),200 38 39 def put(self,post_id): 40 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供全部字段的更新)".format(post_id),200 41 42 def patch(self,post_id): 43 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供部分字段的更新)".format(post_id),200 44 45 def delete(self,post_id): 46 return "删除 blog_posts 表中 id 为{} 的记录".format(post_id),204 47 48 49 # 路由配置。参数[前两个参数必填]:(resource类(资源类)、访问的url地址、endpoint="相当于一个路由名称 如:blue.login。可以在url_for中使用") 50 api.add_resource(PostList,'/posts',endpoint='post_list') # 如果不定义endpoint参数,默认endpoint的值是 类名 51 api.add_resource(PostDetail,'/posts/<int:post_id>',endpoint='post_detail') 52 53 if __name__ == '__main__': 54 app.run(debug=True)
1 # 非restfulAIPI中不用装饰器的写法 2 # @app.route("/") 3 def index(): 4 return "Index" 5 6 # 参数:路由、描述、资源 7 app.add_url_rule("/index/","index",index) 8 9 if __name__ == '__main__': 10 app.run(debug=True)
简单拆分格式:
- manage.py、ext.py、models.py
from flask_script import Manager from flask_migrate import MigrateCommand from app import create_app app = create_app() manager = Manager(app) manager.add_command("db",MigrateCommand) if __name__ == '__main__': manager.run()
import os from flask_migrate import Migrate from app.models import db migrate = Migrate() def init_db(app): app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.root_path, 'sqlite3.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app=app) def init_migrate(app): migrate.init_app(app=app,db=db)
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class Channel(db.Model): __tablename__ = "channels" id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(16),unique=True,nullable=False) sort = db.Column(db.Integer,nullable=False) # 对象方法。将类的对象形式转化成字典形式 def to_dict(self): return { 'id': self.id, 'name': self.name, 'sort': self.sort, }
- __innit__.py
1 from flask import Flask 2 from flask_restful import Api 3 from app import ext 4 from app.apis import ChannelList, ChannelDetail 5 from app.views import restful_bp 6 7 def create_app(): 8 app = Flask(__name__) 9 10 ext.init_db(app) 11 ext.init_migrate(app) 12 13 # 注册实例化api扩展 14 api = Api(app) 15 # 路由注册 16 api.add_resource(ChannelList, '/api/ChannelList', endpoint='ChannelList') 17 api.add_resource(ChannelDetail, '/api/ChannelDetail/<int:id>',endpoint='ChannelDetail') 18 # 蓝图注册 19 app.register_blueprint(blueprint=restful_bp) 20 return app
- api.py 初级[在models.py中定义函数用来字段转字典格式]
1 from flask_restful import Resource,reqparse 2 from app.models import Channel, db 3 4 # 输出格式化。字段自定义验证函数。自定义验证完成后,必须把验证通过后的数值返回出去以方便后续保存操作 5 def validate_channel_name(value): 7 if Channel.query.filter_by(name=value).count() > 0: 8 raise ValueError("频道名重复") 9 return value 10 11 # 输入的验证。flask-restful通过reqparse包中的RequestParser类提供客户端请求的数据参数(通常是json)验证校验功能 12 # 验证需要的参数(客户端提交的需要验证的字段名称,required=True表示必填项,类型,help="错误提示信息") 13 channel_parser = reqparse.RequestParser() 14 channel_parser.add_argument('name',required=True,type=validate_channel_name) # type='自定义验证函数名' 15 channel_parser.add_argument('sort',required=True,type=int,help='sort字段为为必填项,int类型') 16 17 # http://127.0.0.1:5000/api/ChannelList [get请求、post请求] 18 class ChannelList(Resource): 19 """ 20 GET /channels 21 POST /channels 22 """ 23 def get(self): 24 # 数据库查询返回的是list类型的数据。需要将从数据库查询到的数据转为字典形式 25 _channels = Channel.query.all() 26 channels = [channel.to_dict() for channel in _channels] 27 # flask中视图函数只能返回str或者response对象。 28 # flask-restful可以将数据库中查询出来的数据转换成json;以满足flask视图函数的返回要求 29 # flask-restful返回字典,框架内部自动转换为json。return {'channels':channels},200 30 res = { 31 'channels':channels 32 } 33 return res,200 34 35 def post(self): 36 # 通过RequestParser的实例对象 验证前端页面传递来的数据。如果验证不通过返回400错误 37 args = channel_parser.parse_args() 38 39 channel = Channel() 40 channel.name = args['name'] 41 channel.sort = args['sort'] 42 db.session.add(channel) 43 db.session.commit() 44 # flask-restful 的类视图方法只能返回字典格式 45 return {'channel':channel.to_dict()},201
- api.py 高级[使用装饰器转字典]
1 from flask_restful import Resource, reqparse, fields, marshal_with, abort 2 from app.models import Channel, db 3 5 def validate_channel_name(value): 6 if Channel.query.filter_by(name=value).count() > 0: 7 raise ValueError("频道名重复") 8 return value
11 channel_parser = reqparse.RequestParser() 12 channel_parser.add_argument('name',required=True,type=validate_channel_name) 13 channel_parser.add_argument('sort',required=True,type=int,help='sort字段为为必填项,int类型') 14 16 # 通过marshal_with和fields一起完成自定义输出的功能,让api接口可以返回对象,系统内部将对象根据fields的定义格式转换成字段类型 18 channel_fields = { 19 "id":fields.Integer, 20 # url链接。"url": "http://127.0.0.1:5000/api/ChannelDetail/3", 21 "url":fields.Url(endpoint="ChannelDetail",absolute=True), # absolute=True 表示加上前缀http://127.0.0.1:5000 22 "name":fields.String, 23 "sort":fields.Integer, 24 }
1 # http://127.0.0.1:5000/api/channels 可以get、post请求 2 class ChannelList(Resource): 3 4 @marshal_with(fields=channel_fields) 5 def get(self): 6 _channels = Channel.query.all() 7 return _channels, 200 8 # 原来写法 9 # channels = [channel.to_dict() for channel in _channels] 10 # res = { 11 # 'channels':channels 12 # } 13 # return res,200 14 15 # 此装饰器接收一个参数(以什么格式)。有了这个装饰器,在返回的时候对象会按照参数同名所设置的方式变成一个字典返回 16 @marshal_with(fields=channel_fields) 17 def post(self): 18 # 验证前端页面传递来的数据。如果验证不通过返回400错误 19 args = channel_parser.parse_args() 20 21 channel = Channel() 22 channel.name = args['name'] 23 channel.sort = args['sort'] 24 db.session.add(channel) 25 db.session.commit() 26 # 原来返回方式 27 # return {'channel':channel.to_dict()},201 28 # flask-restful只能返回字典格式类型。添加装饰器marshal_with可以定制输出格式。 29 return channel,201
1 # http://127.0.0.1:5000/api/ChannelDetail/3 2 class ChannelDetail(Resource): 3 """ 4 GET /channels/123 5 PUT /channels/234 6 PATCH /channels/123 7 DELETE /channels/123 8 """ 9 def get_object(self,id): 10 channel = Channel.query.get(id) 11 if channel is None: 12 return abort(404,message="找不到对象") 13 return channel 14 15 @marshal_with(fields=channel_fields) 16 def get(self,id): 17 channel = self.get_object(id) 18 return channel,200 19 20 @marshal_with(fields=channel_fields) 21 def put(self,id): 22 channel = self.get_object(id) 23 args = channel_parser.parse_args() # 字段验证 24 25 channel.name = args.get("name",channel.name) 26 channel.sort = args.get("sort",channel.sort) 27 db.session.commit() 28 return channel,200 29 30 def patch(self,id): 31 self.put(id) 32 33 def delete(self,id): 34 channel = self.get_object(id) 35 db.session.delete(channel) 36 db.session.commit() 37 return "",204
API 1:N 模型
坑:'/api/ArticleDetails/<int:id>' 此id必须和数据库中的相应字段同名,否则匹配不上;.Nested:表示将一个比较复杂的数据对象拆分开,方便转为字典格式
1 from flask import Flask 2 from flask_restful import Api 3 from app import ext 4 from app.apis import ChannelList, ChannelDetail, ArticleList, ArticleDetail 5 from app.views import restful_bp 6 7 def create_app(): 8 app = Flask(__name__) 9 10 ext.init_db(app) 11 ext.init_migrate(app) 12 13 # 注册实例化api扩展 14 api = Api(app) 15 # 注册频道路由 16 api.add_resource(ChannelList, '/api/ChannelLists', endpoint='ChannelLists') 17 api.add_resource(ChannelDetail, '/api/ChannelDetails/<int:id>',endpoint='ChannelDetails') 18 # 注册文章路由 19 api.add_resource(ArticleList, '/api/Articles', endpoint='Articles') 20 api.add_resource(ArticleDetail, '/api/ArticleDetails/<int:id>',endpoint='ArticleDetails') 21 22 # 蓝图 23 app.register_blueprint(blueprint=restful_bp) 24 return app
1 import datetime 2 from flask_sqlalchemy import SQLAlchemy 3 4 db = SQLAlchemy() 5 6 # 频道 1 7 class Channel(db.Model): 8 __tablename__ = "channels" 9 10 id = db.Column(db.Integer,primary_key=True) 11 name = db.Column(db.String(16),unique=True,nullable=False) 12 sort = db.Column(db.Integer,nullable=False) 13 14 articles = db.relationship('Article',backref='channel',lazy='dynamic') 15 16 # 文章 N 17 class Article(db.Model): 18 __tablename__ = "articles" 19 20 id = db.Column(db.Integer,primary_key=True) 21 created_at = db.Column(db.DateTime,default=datetime.datetime.now()) 22 updated_at = db.Column(db.DateTime,default=datetime.datetime.now(),onupdate=datetime.datetime.now()) 23 title = db.Column(db.String(256),nullable=False) 24 content = db.Column(db.String(5000),nullable=False) 25 26 channel_id = db.Column(db.Integer,db.ForeignKey("channels.id"))
1 import datetime 2 from flask_restful import Resource, reqparse, fields, marshal_with, abort 3 from app.models import Channel, db, Article 4 5 # ============================ N =================================== 6 # 自定义一个类,用于时间格式化 7 class MyDTFmt(fields.Raw): 8 def format(self, value): 9 return datetime.datetime.strftime(value,'%Y-%m-%d %H:%M:%S') 10 11 # 定义参数验证格式 12 article_parser = reqparse.RequestParser() 13 article_parser.add_argument('title',required=True,type=str,help="标题必填") 14 article_parser.add_argument('content',required=True,type=str,help="正文必填") 15 article_parser.add_argument('channel_id',required=True,type=int,help="频道必填") 16 17 # 定义返回输出格式 18 article_fields = { 19 'id':fields.Integer, 20 'url':fields.Url(endpoint='ArticleDetails',absolute=True), 21 'title':fields.String, 22 'content':fields.String, 23 # 等同于:'channel':fields.Nested(channel_fields), 24 'channel':fields.Nested({ # 通过Nested将对象解开 25 'name':fields.String, 26 'url':fields.Url(endpoint="ChannelDetails",absolute=True), 27 "sort": fields.Integer, 28 }), 29 'created_at':MyDTFmt, # 进行自定义时间格式化 30 'updated_at':fields.DateTime(dt_format="iso8601") 31 32 } 33 34 # 文章模块。get、post 35 class ArticleList(Resource): 36 37 @marshal_with(fields=article_fields) 38 def get(self): 39 articles = Article.query.all() 40 return articles,200 41 42 @marshal_with(fields=article_fields) 43 def post(self): 44 args = article_parser.parse_args() 45 46 article = Article() 47 article.title = args.get('title') 48 article.content = args.get('content') 49 article.channel_id = args.get('channel_id') 50 51 db.session.add(article) 52 db.session.commit() 53 return article,201 54 55 # 文章模块。get、put、patch、delete 56 class ArticleDetail(Resource): 57 def get_object(self,id): 58 article = Article.query.get(id) 59 if article is None: 60 return abort(404,message="找不到对象") 61 return article 62 63 @marshal_with(fields=article_fields) 64 def get(self,id): 65 article = self.get_object(id) 66 return article,200 67 68 def put(self,id): 69 pass 70 def patch(self,id): 71 pass 72 def delete(self,id): 73 pass 74 75 76 # ================================== 1 ========================================= 77 # 自定义字段验证函数。 自定义验证后,必须把验证通过后的数值返回出去 78 def validate_channel_name(value): 79 if Channel.query.filter_by(name=value).count() > 0: 80 raise ValueError("频道名重复") 81 return value 82 83 channel_parser = reqparse.RequestParser() 84 channel_parser.add_argument('name',required=True,type=validate_channel_name) # type=自定义验证函数名 85 channel_parser.add_argument('sort',required=True,type=int,help='sort字段为为必填项,int类型') 86 87 channel_fields = { 88 "id":fields.Integer, 89 "url":fields.Url(endpoint="ChannelDetails",absolute=True), 90 "name":fields.String, 91 "sort":fields.Integer, 92 } 93 94 channel_article_fields = { 95 "id": fields.Integer, 96 "url": fields.Url(endpoint="ChannelDetails",absolute=True), 97 "name": fields.String, 98 "articles": fields.List(fields.Nested(article_fields)) # 列表是一个articles对象,通过Nested将其解开,输出出去。 99 } 100 101 102 # 频道模块。get、post 103 class ChannelList(Resource): 104 105 @marshal_with(fields=channel_fields) 106 def get(self): 107 _channels = Channel.query.all() 108 return _channels, 200 109 110 # 此装饰器接收一个参数(以什么格式)。有了这个装饰器,在返回的时候对象会按照同名参数设置的方式变成一个字典返回 111 @marshal_with(fields=channel_fields) 112 def post(self): 113 # 验证前端页面传递来的数据。如果验证不通过返回400错误 114 args = channel_parser.parse_args() 115 116 channel = Channel() 117 channel.name = args['name'] 118 channel.sort = args['sort'] 119 db.session.add(channel) 120 db.session.commit() 121 return channel,201 122 123 124 # 频道模块。get、put、patch、delete 125 class ChannelDetail(Resource): 126 """ 127 GET /channels/123 128 PUT /channels/234 129 PATCH /channels/123 130 DELETE /channels/123 131 """ 132 def get_object(self,id): 133 channel = Channel.query.get(id) 134 if channel is None: 135 return abort(404,message="找不到对象") 136 return channel 137 138 @marshal_with(fields=channel_article_fields) 139 def get(self,id): 140 channel = self.get_object(id) # channel.articles 代表了当前频道下文章列表 141 return channel,200 142 143 @marshal_with(fields=channel_fields) 144 def put(self,id): 145 channel = self.get_object(id) 146 args = channel_parser.parse_args() # 字段验证 147 148 channel.name = args.get("name",channel.name) 149 channel.sort = args.get("sort",channel.sort) 150 db.session.commit() 151 return channel,200 152 153 def patch(self,id): 154 self.put(id) 155 156 def delete(self,id): 157 channel = self.get_object(id) 158 db.session.delete(channel) 159 db.session.commit() 160 return "",204
高级拆分格式:
- manage.py
1 """ manage.py """ 2 3 import os 4 from flask_migrate import MigrateCommand 5 from flask_script import Manager 6 from day05_chaiAdvanced import create_app 7 8 # 从系统环境中获取参数FLASK_ENV的值给env,判断是什么环境下的服务器 9 # 避免外人修改代码,方便代码放在什么环境服务器下,就在什么环境下运行 10 # 在系统终端 vim .bashrc 编写系统环境变量:#FLASK_ENV。export FLASK_ENV = "develop" 保存退出 11 env = os.environ.get("FLASK_ENV") or 'default' 12 13 # 首先创建一个flask对象、加载配置、加载扩展库、初始化路由 14 app = create_app(env) 15 16 # flask-scripy扩展 17 manager = Manager(app) 18 manager.add_command("db",MigrateCommand) 19 20 if __name__ == '__main__': 21 manager.run()
- __init__.py
1 ''' 启动项目的__init__.py ''' 2 from flask import Flask 3 4 from day05_chaiAdvanced.settings import Config, envs 5 from day05_chaiAdvanced.extension import init_ext 6 from day05_chaiAdvanced.views import init_api 7 8 def create_app(env): 9 10 # 创建Flask对象 11 app = Flask(__name__,template_folder="../templates") 12 13 # 加载初始化配置。 从类对象中加载。env参数确定在什么环境下的 14 app.config.from_object(envs.get(env)) 15 16 # 加载初始化扩展库。通过懒加载的方式加载(调用函数时参数的传递) 17 init_ext(app) 18 19 # 加载初始化api路由器。通过懒加载的方式加载(调用函数时参数的传递) 20 init_api(app) 21 22 return app
- views.py
1 from Book.route import books_api 2 3 def init_api(app): 4 books_api.init_app(app)
- books/route.py
1 from flask_restful import Api 2 3 from Book.views import BooksResource 4 5 books_api = Api() 6 7 books_api.add_resource(BooksResource, "/books/")
- books/views.py
1 from flask_restful import Resource 2 3 class BooksResource(Resource): 4 5 def get(self): 6 return {"msg": "book ok"}