• flask 之(六) --- API|RestfulApi


    接口概念

      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 - [*] :服务器内部发生错误

    资源(Resource)

      所谓“资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。

      它可以是一段文本,一张图片,一首歌曲,一种服务,总之就是一个具体的实例。

      你可以使用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。

      要获取这个资源,访问它的URI就可以了,因此URI就成了每一个资源的地址或独一无二的识别符。所谓“上网”就是与互联网上一系列的“资源”互动,调用它们的URI。

    表现层(Representation)

      “资源”是一种信息实体,它可以有多种外在表现形式。我们把“资源”具体呈现出来的形式,叫做它的”表现层“(Representation)。

      URI只代表资源的实体,不代表它的形式。严格地说,有些网站最后的”.html“后缀名是不必要的,因为这个后缀表示格式,属于”表现层“范畴,而URI应该只代表”资源“的位置。

      它的具体表现形式,应该在HTTP请求头的信息中使用Accept和Content-Type字段指定。

    状态转换(State Transfer)

      访问一个网站,就代表客户端和服务端的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

      互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务端。

      因此,如果客户端想要操作服务器,就必须通过某种手段,让服务器端发生”状态转换(State Transfer)“。

      而这种转换是建立在表现层之上的,所以就是”表现层状态转化“。

      客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议中,四个表示操作方式的动词:GET,POST,PUT,DELETE。

      它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可用于更新资源),PUT用来更新资源,DELETE用来删除资源

    总结 :

    1. 每一个URI代表一种资源
    2. 客户端和服务器之间,传递这种资源的某种表现层
    3. 客户端通过四个HTTP动词,对服务端资源进行操作,实现”表现层状态转换“
    4. 同一个url针对用户的不同的请求操作,表现出来的状态是不同的。表现出来的多种形式,就是表现层状态转换。
    • RESTful是软件架构设计思想。使用在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一样提供验证数据的功能
    • 框架会提供丰富的输出格式,自定义输出的结构

    框架注意:⚠️

    1. flask-restful 返回字典。框架内部自动转换为json。return {'channels':channels},200
    2. 如果在扩展文件ext.py实例化和注册路由资源,必须在加载时就注册好,不可以在调用函数懒加载时注册
    3. 根据请求的同名方法执行同名的视图函数,来完成不同的请求。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)
    非API非装饰器写法

    简单拆分格式:

    • 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()
    manage.py
    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)
    ext.py
    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,
            }
    models.py
    • __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_withfields一起完成自定义输出的功能,让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
    __init__.py
     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"))
    models.py
      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"}


     

    生如逆旅 一苇以航
  • 相关阅读:
    Spark、BulkLoad Hbase、单列、多列
    centos 根目录扩容
    VMware Workstation 添加磁盘 挂载目录(centos)
    maven+scala+idea 环境构建
    Spark 调优之ShuffleManager、Shuffle
    Spark 调优之数据倾斜
    SparkSession、SparkContext、SQLContext和HiveContext之间的区别。
    Spark 自定义函数(udf,udaf)
    树莓派环境下使用python将h264格式的视频转为mp4
    C# NanUI WinFormium监听页面加载开始结束
  • 原文地址:https://www.cnblogs.com/TMMM/p/11502716.html
Copyright © 2020-2023  润新知