13-flask博客项目之restful api详解2-使用

 

13-flask博客项目之restful api详解1-概念 

13-flask博客项目之restful api详解1-概念 

Flask-RESTful学习网站

英文:https://flask-restful.readthedocs.io/en/latest/

中文:http://www.pythondoc.com/Flask-RESTful/quickstart.html

一个英文一个中文

13-flask博客项目之restful api详解2-使用插图

 

 安装

 Flask-RESTful安装

使用 pip 安装 Flask-RESTful:

pip install flask-restful

开发的版本可以从 GitHub 上的页面 下载

git clone https://github.com/twilio/flask-restful.git
cd flask-restful
python setup.py develop

Flask-RESTful 有如下的依赖包(如果你使用 pip,依赖包会自动地安装):

  • Flask 版本 0.8 或者更高

Flask-RESTful 要求 Python 版本为 2.6, 2.7, 或者 3.3。

我这里装的0.3.8

postman安装

 调试程序需要用到postman

postman使用:https://blog.csdn.net/fxbin123/article/details/80428216/

一、Postman背景介绍

用户在开发或者调试网络程序或者是网页B/S模式的程序的时候是需要一些方法来跟踪网页请求的,用户可以使用一些网络的监视工具比如著名的Firebug等网页调试工具。今天给大家介绍的这款网页调试工具不仅可以调试简单的css、html、脚本等简单的网页基本信息,它还可以发送几乎所有类型的HTTP请求!Postman在发送网络HTTP请求方面可以说是Chrome插件类产品中的代表产品之一。

1> 、postman下载地址:

window 64位

https://www.getpostman.com/apps

postman基本使用

 13-flask博客项目之restful api详解2-使用插图1

 

 

13-flask博客项目之restful api详解2-使用插图2

 

 使用它访问一下百度

13-flask博客项目之restful api详解2-使用插图3

 

13-flask博客项目之restful api详解2-使用插图4

 

13-flask博客项目之restful api详解2-使用插图5

 

 我们看下面的请求地址,请求方式

13-flask博客项目之restful api详解2-使用插图6

 

 请求结果

13-flask博客项目之restful api详解2-使用插图7

 

 用post发送geti请求,对这个url

13-flask博客项目之restful api详解2-使用插图8

 

 上面请求200,我们还可以看请求头

13-flask博客项目之restful api详解2-使用插图9

 

 13-flask博客项目之restful api详解2-使用插图10

 

 保存是将请求结果保存到一个文件中

13-flask博客项目之restful api详解2-使用插图11

 

 我们随便填个信息登录,可以看到是发送了ajax的请求,类型是xhr。当滑动验证的时候,也是发送了verify的ajax请求

13-flask博客项目之restful api详解2-使用插图12

 

 我们可以看他登录的请求地址和类型。我们可以看到,手机上提交的form表单都是post请求

13-flask博客项目之restful api详解2-使用插图13

 

 我们可以看到表单数据携带邮箱密码

13-flask博客项目之restful api详解2-使用插图14

 

 第一个api

环境准备

13-flask博客项目之restful api详解2-使用插图15

我们这里用的是英文的,导入的模块路径不同,应该是版本不同吧

13-flask博客项目之restful api详解2-使用插图16

from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
    app.run(debug=True)

我们需要将restful这个第三方组件加入到我们的项目中,它是跟db加进来是一样的

实例化api对象

13-flask博客项目之restful api详解2-使用插图17

 

 api对象初始化app

13-flask博客项目之restful api详解2-使用插图18

 

 定义api蓝图,并添加api前缀

13-flask博客项目之restful api详解2-使用插图19

 

 注册蓝图

13-flask博客项目之restful api详解2-使用插图20

 

 创建并修改数据库。其它不用变

13-flask博客项目之restful api详解2-使用插图21

 

 app改成这个样子的

13-flask博客项目之restful api详解2-使用插图22

 

 我们导入资源

13-flask博客项目之restful api详解2-使用插图23

 

 导入之后,我们点击resource进入查看,它就有一个dispatch_request方法

13-flask博客项目之restful api详解2-使用插图24

 

 Resource类继承了MethodView类,我们点进去看下这个类

13-flask博客项目之restful api详解2-使用插图25

 

 MethodView里面也有dispatch_request这个方法。它有继承了使用元类。我们可以看到下面写着要定义get post等方法

13-flask博客项目之restful api详解2-使用插图26

蓝图总写一个简单不完善的api示例

13-flask博客项目之restful api详解2-使用插图2713-flask博客项目之restful api详解2-使用插图28

from flask import Blueprint
from flask_restful import Resource, marshal_with, fields
from exts import api
user_bp = Blueprint('user', __name__, url_prefix='/api')
from apps.user.model import User

user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'password': fields.String,
    'udatetime': fields.DateTime
}

# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    @marshal_with(user_fields)
    def get(self):
        users = User.query.all()
        # userList = []
        # for user in users:
        #     userList.append(user.__dict__)
        return users

    # post
    def post(self):
        return {'msg': '------>post'}

    # put
    def put(self):
        return {'msg': '------>put'}

    # delete
    def delete(self):
        return {'msg': '------>delete'}

api.add_resource(UserResource, '/user')

api示例

 我们在蓝图中如下操作:

从组件中导入资源,定义视图类,然后让它继承资源类,在自己定义的视图类下面定义了get ,post,put,delete四个方法,分别对于查增改删四个功能。

给我们的视图类添加路由,通过调用之前在exts目录init文件中从组件导入的api类生成的api对象,然后api对象点增加资源方法 。将我们定义的视图类加进去,后面是路由字符串。这样就给我们定义的视图类增添了路由映射了。

from flask import Blueprint
from flask_restful import Resource
from exts import api
user_bp = Blueprint('user', __name__, url_prefix='/api')

# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    def get(self):
        return {'msg': '------>get'}

    # post
    def post(self):
        return {'msg': '------>post'}

    # put
    def put(self):
        return {'msg': '------>put'}

    # delete
    def delete(self):
        return {'msg': '------>delete'}

api.add_resource(UserResource, '/user')

启动程序,我们从浏览器访问一下,当我们get请求路由的时候,找的这个视图类,然后找到get请求,执行get方法,响应这个字典。前端接收到并在浏览器页面显示粗来。这里添加了api前缀,但是这里不需要添加api前缀,就能访问到这里的视图类,如果添加上了反而无法访问到,可能是因为没有其它蓝图中有相同的路由的原因吧,所以用不上

13-flask博客项目之restful api详解2-使用插图29

 

 我们在postman中也是可以访问

13-flask博客项目之restful api详解2-使用插图30

 

 我们四个请求方式都使用了,这里目前都是可以请求到对应方式的t数据

13-flask博客项目之restful api详解2-使用插图31

 

 当请求一个未在视图类中定义的请求方法时,报错

13-flask博客项目之restful api详解2-使用插图32

 

 我们先定义一个模型,然后迁移数据

13-flask博客项目之restful api详解2-使用插图33

 

 添加两个用户,

13-flask博客项目之restful api详解2-使用插图34

 

 我们在用户查询方法里面查出这个用户对象列表,然后返回这个用户对象列表。再看postman请求

13-flask博客项目之restful api详解2-使用插图35

 

 此时,不能直接返回用户对象列表。报错没有json序列化

13-flask博客项目之restful api详解2-使用插图36

 

 我们改成这样还是不行

13-flask博客项目之restful api详解2-使用插图37

 

 这样也不行

13-flask博客项目之restful api详解2-使用插图38

 

 官网是这样做的

13-flask博客项目之restful api详解2-使用插图39

 

 当我们添加marshal_with装饰器,装饰器传参用户字典,用户字段字典里面定义用户表表中有的字段,每个字段的类型是什么,这样再遇到执行视图装饰器装饰下的get方法时,返回用户对象,就能将用户对象的信息像用户字段字典定义的格式查出来并响应给客户端。这里只响应一个用户对象,返回的是个字典。

13-flask博客项目之restful api详解2-使用插图40

 

 当我去掉所以,返回用户对象列表后,响应结果是每一个用户对象一个字典,所有用户对象字典组成一个用户对象列表。每个用户对象返回的信息都是安装用户字段设置的格式返回的字段数据,从表中查出数据。这里字段名字和表字段名字保持一致的。字段类型也需要设置。从数据库中查询出的数据,会渲染到这些相同字段名称下。也就是。用户字段前面自己定义,后面应该必须是_fields命名。然后需要返回数据库对象的方法前面添加marshal_with装饰器,并把要响应的格式用户字段传进去,这样装饰器中会帮我们把用户对象列表中的每个用户对象按照user_fiels来生成响应数据。从而生成响应数据列表。只有一个用户对象,请求结果是一个字典,有多个用户时就是多个字典在一个列表里面

13-flask博客项目之restful api详解2-使用插图41

 

 

from flask import Blueprint
from flask_restful import Resource, marshal_with, fields
from exts import api
user_bp = Blueprint('user', __name__, url_prefix='/api')
from apps.user.model import User

user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'password': fields.String,
    'udatetime': fields.DateTime
}

# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    @marshal_with(user_fields)
    def get(self):
        users = User.query.all()
        # userList = []
        # for user in users:
        #     userList.append(user.__dict__)
        return users

    # post
    def post(self):
        return {'msg': '------>post'}

    # put
    def put(self):
        return {'msg': '------>put'}

    # delete
    def delete(self):
        return {'msg': '------>delete'}

api.add_resource(UserResource, '/user')

如果在字段里面只定义了一个,那么get获取的数据也只有这一个。不会因为你响应的数据的字段多而变多。我们的数据库字段是比较多的,当我们想要只返回一两个字段时,那么只需要在xxfields里面定义指定要返回的字段就可以。

13-flask博客项目之restful api详解2-使用插图42

 

 

总结

什么是RESTful架构:

(1)每一个URI代表一种资源;

(2)客户端和服务器之间,传递这种资源的某种表现层;

(3)客户端通过四个HTTP动词(GET,POST,PUT,DELETE,[PATCH]),对服务器端资源进行操作,实现"表现层状态转化"。

Postman

前后端分离:
前端: app,小程序,pc页面

后端: 没有页面,mtv: 模型模板视图  去掉了t模板。
      mv:模型 视图
      模型的使用:跟原来的用法相同
      视图: api构建视图
  步骤:
     1. pip3 install flask-restful

     2.创建api对象
      api = Api(app=app)
      api = Api(app=蓝图对象)
     3.
      定义类视图:
      from flask_restful import Resource
      class xxxApi(Resource):
        def get(self):
            pass

        def post(self):
            pass

        def put(self):
            pass

        def delete(self):
            pass
      4. 绑定

      api.add_resource(xxxApi,'/user')

 flask restful官网目录汇总

中文的可以用来学习,可能包路径是不一样的,跟中文的

13-flask博客项目之restful api详解2-使用插图43

 

restful api资源路由

上面写了一个接口,这个接口返回的就是一个json数据,提供前端请求使用,

一个视图函数,按照继承的类是资源,我们可以称之为一个视图函数就是一个资源吧,而视图函数的请求路径的字符串映射,也就是路由,按官网翻译就是资源路由

jquery使用地址(ajax使用地址) :https://jquery.cuishifeng.cn/

我们在前面的基础上再添加一个类视图。上面哪个是对所有用户的操作,下面这个是对单个用户的操作,对单个用户的操作需要指定用户的id。也就是说当是同一张表时,我们或许可以根据前端需求,给同一个表添加多个不同的类视图,以返回不同的响应数据。这里就是所有用户和单个用户是分开的,也就是还要单独添加路由,这个路由是需要传参的。至于是否可以合并 ,看情况考虑。

13-flask博客项目之restful api详解2-使用插图44

 

 我们需要添加单个用户的类视图,添加单个用户的增删改查四个功能,添加单个用户的类视图的访问路由。需要将单个 用户资源类视图作为添加路由的参数,然后后面填写映射的路径字符串。因为单个用户是需要传递用户id参数的,这里可以使用这种方式传参,但是类视图中每个方法需要定义接收这个传参的形参的

13-flask博客项目之restful api详解2-使用插图45

 

 我们根据传进的用户id,将用户对象查出来然后响应回去,但是我们需要的响应的数据需要是json格式的。

13-flask博客项目之restful api详解2-使用插图46

 

 因此我们需要给它添加marshal,将用户对象转成一个序列号对象,以之前定义的用户字段格式来响应。

13-flask博客项目之restful api详解2-使用插图47

 

 然后 我们测试。可以发现,当我们不接参数的时候,访问的是上面返回所有用户的

13-flask博客项目之restful api详解2-使用插图48

 

 当我们接上参数的时候,返回的是单个用户的

13-flask博客项目之restful api详解2-使用插图49

from flask import Blueprint
from flask_restful import Resource, marshal_with, fields
from exts import api
user_bp = Blueprint('user', __name__, url_prefix='/api')
from apps.user.model import User

user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'password': fields.String,
    'udatetime': fields.DateTime
}

# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    @marshal_with(user_fields)
    def get(self):
        users = User.query.all()
        # userList = []
        # for user in users:
        #     userList.append(user.__dict__)
        return users
    # post
    def post(self):
        return {'msg': '------>post'}
    # put
    def put(self):
        return {'msg': '------>put'}
    # delete
    def delete(self):
        return {'msg': '------>delete'}

class UserSimpleResource(Resource):
    @marshal_with(user_fields)  # user转成一个序列化对象,
    def get(self, id):
        user = User.query.get(id)
        return user  # 不是str,list,int,。。。

    def put(self, id):
        pass

    def delete(self, id):
        pass

api.add_resource(UserResource, '/user')
api.add_resource(UserSimpleResource, '/user/')

一个网址,后端是同样的,但是前端可以有不同的,如pc端,小程序,手机app等等。前端需要什么数据,后端需要前端传什么参数,需要前后端开发人员协商好

13-flask博客项目之restful api详解2-使用插图50

 

 我们除了上面那种传参,还可以用问号方式传参

13-flask博客项目之restful api详解2-使用插图51

 

 endpoints

我们在这里打印一下url_map,就能打印出路由的信息

13-flask博客项目之restful api详解2-使用插图52

 

 当我们请求一条路由的时候,就打印出路由信息

13-flask博客项目之restful api详解2-使用插图53

 

 当我们不添加endpoint时,它用的时视图类名称小写做的endpoint,

13-flask博客项目之restful api详解2-使用插图54

 

 当我们添加了endpoint之后,就叫我们修改后的那个名称,它指代的就是那条路由,可以使用endpoint来做反向解析

13-flask博客项目之restful api详解2-使用插图55

 

 我们在单个用户的视图类put方法下打印一下all_user这个endpoint的反向解析,

13-flask博客项目之restful api详解2-使用插图56

 

 postman请求正常

13-flask博客项目之restful api详解2-使用插图57

 

 然后我们可以看到通过endpoint名称,可以解析出它对应的路由。也就是在别的地方,我们需要使用这个路由,就可以使用endpoint去做反向解析出它的路由来了

13-flask博客项目之restful api详解2-使用插图58

 

 参数解析

13-flask博客项目之restful api详解2-使用插图2713-flask博客项目之restful api详解2-使用插图28

from flask import Blueprint, url_for
from flask_restful import Resource, marshal_with, fields,reqparse
from exts import api
user_bp = Blueprint('user', __name__, url_prefix='/api')
from apps.user.model import User
from exts import db
user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'password': fields.String,
    'udatetime': fields.DateTime
}

# 参数解析
parser = reqparse.RequestParser()  # 解析对象
parser.add_argument('username', type=str, required=True, help='必须输入用户名')
parser.add_argument('password', type=str , required=True, help='必须输入密码',
                    location=['form'])
parser.add_argument('phone', type=str)
# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    @marshal_with(user_fields)
    def get(self):
        users = User.query.all()
        # userList = []
        # for user in users:
        #     userList.append(user.__dict__)
        return users

    @marshal_with(user_fields)
    def post(self):
        # 获取数据
        args = parser.parse_args()

        username = args.get('username')
        password = args.get('password')
        phone = args.get('phone')
        # 创建user对象
        user = User()
        user.username = username
        user.password = password
        if phone:
            user.phone = phone
        db.session.add(user)
        db.session.commit()

        return user
    # put
    def put(self):
        return {'msg': '------>put'}
    # delete
    def delete(self):
        return {'msg': '------>delete'}

class UserSimpleResource(Resource):
    @marshal_with(user_fields)  # user转成一个序列化对象,
    def get(self, id):
        user = User.query.get(id)
        return user  # 不是str,list,int,。。。

    def put(self, id):
        print('endpoint的使用:', url_for('all_user'))
        return {'msg': 'ok'}

    def delete(self, id):
        pass

api.add_resource(UserResource, '/user',endpoint='all_user')
api.add_resource(UserSimpleResource, '/user/')

View Code

我们想要请求这个路径的时候这个方式去携带很多个值

13-flask博客项目之restful api详解2-使用插图59

 

 我们提交数据时带着数据

13-flask博客项目之restful api详解2-使用插图60

 

 需要用到

13-flask博客项目之restful api详解2-使用插图61

 

 我们在用户字段下面创建解析对象,然后添加参数。我们可以对前端的传参进行解析,进行校验。相当于form类的作用

13-flask博客项目之restful api详解2-使用插图62

 

 我们可以看到,添加参数是请求解析类中的一个方法。

13-flask博客项目之restful api详解2-使用插图63

 

 我们看下请求解析这个类的init方法,里面有些参数,我们没有设置的时候用的是这些默认参数。trim就是默认不做空字符的去除,bundle_errors就是,如果enabled,当第一个错误出来的时候,不会中止。继续往下校验所有的字段

13-flask博客项目之restful api详解2-使用插图64

 

 给解析器添加参数

13-flask博客项目之restful api详解2-使用插图65

 

 我们按照下面添加参数,如果前端有电话,后端没有电话参数,是不可以的,是不能多给的

13-flask博客项目之restful api详解2-使用插图66

 

 添加参数,限制传参的类型,指明传参类型必须是整型。默认是字符串类型

13-flask博客项目之restful api详解2-使用插图67

 

 比如我们的分页,必须是整型,这时就需要前端传递过来的必须是整型,所以我们可以像上面那样添加数据类型必须是整型才能成功提交,才能校验通过

13-flask博客项目之restful api详解2-使用插图68

 

  我们给模型添加一个电话字段,迁移数据

13-flask博客项目之restful api详解2-使用插图69

 

13-flask博客项目之restful api详解2-使用插图70

 

 然后添加参数,指定参数名称,类型,必填,帮助信息,就是报错信息

13-flask博客项目之restful api详解2-使用插图71

 

 我们写好之后,前端就可以传数据了。后端视图函数中需要取值,就从parse_args里面取

13-flask博客项目之restful api详解2-使用插图72

 

 我们导入reqpares和数据库

13-flask博客项目之restful api详解2-使用插图73

 

 我们实例化解析对象,添加三个参数

13-flask博客项目之restful api详解2-使用插图74

 

 当前端发送post请求,将三个数据传递到后端,我们在post中从解析器里面取数据

需要先调用解析参数方法,然后从这个对象中获取请求过来的form数据,将他们保存到数据库,再将这个对象返回给前端,因为需要返回给前端,所以需要是json序列化过的,需要添加上marshal_with装饰器。指定按照用户字段这个格式返回数据

13-flask博客项目之restful api详解2-使用插图75

 

 我们在postman中添加数据,如果是文本的就用这个就行,如果是有文件的,选择form-data。这里是post请求,我们应该在body里面添加数据,而不是在params里面添加数据。这里添加键值对,就相当于你在form表单里面写入了数据。

13-flask博客项目之restful api详解2-使用插图76

 

 当我们输入键值对,点击发送post请求之后。我们从解析器中获取到用户传过来的键值对,然后保存到数据库中,数据库中已经增加了这条数据了。post方法里面返回这个添加的用户,添加装饰器和指定返回的字段格式后,我们在postman中就接收到了保存下来的数据信息。

13-flask博客项目之restful api详解2-使用插图77

 我们在params里面添加键值对,这样来发送post请求。也是可以接收到数据的,从而往下执行保存进入数据库。毕竟有的地方form表单post请求就是可以接问号拼接传参的,只要取得键值对对应上就行

13-flask博客项目之restful api详解2-使用插图78

 

 我们在location参数里面添加form时,这样就限制了,表示form提交的数据里必须有这些字段,而我们是从params里面写的,那么相当于每天在body里面填写form数据,所以请求到达解析器就对数据做了校验,相当于没有填写username,就把错误信息返回给请求客户端了。这样前端可以根据这个判断,如果有错误信息就在前端渲染,否则就是用用户信息做啥渲染的。或者其它操作等等

13-flask博客项目之restful api详解2-使用插图79

 我们导入input,里面使用正则校验,这样没有通过校验的字段,就会将帮助信息,也就是未校验通过的错误信息响应给客户端。其它字段,包括密码我们都可以设置校验。除了正则校验还可以使用其它校验方法。密码的正则校验,可以通过正则限制个数,这里是6到12位,我们提示信息也修改完整一点,提示是需要6到12位的数字

13-flask博客项目之restful api详解2-使用插图80

 

13-flask博客项目之restful api详解2-使用插图81

我们将手机号让它通过校验,我们可以看到能成功提交数据。

13-flask博客项目之restful api详解2-使用插图82

 

 我们再get请求5号用户,将body键值对取消勾选,然后点击发送。我们就能得到5号用户的数据

13-flask博客项目之restful api详解2-使用插图83

 

 类似于复选框功能的数据添加

 

 

 复选框,我们需要添加action 是append

13-flask博客项目之restful api详解2-使用插图84

 

 在post方法中我们get这个爱好。打印一下,这里没有添加数据库这个字段,只是看一下请求时这里接收的数据是怎样的

13-flask博客项目之restful api详解2-使用插图85

 

 我们发送post请求,添加上多个值,键是一样的。

13-flask博客项目之restful api详解2-使用插图86

 

 我们解析器定义接收的字段行为是追加,所以我们从参数解析器里面取的这个字段,是postman里面添加的多个值组成的一个列表。

13-flask博客项目之restful api详解2-使用插图87

 

 换名字

13-flask博客项目之restful api详解2-使用插图88

 

 前端location  文件上传,头像上传

添加头像字段,迁移数据

13-flask博客项目之restful api详解2-使用插图89

location有多种类型,

13-flask博客项目之restful api详解2-使用插图90

 如果是文件上传,类型必须得填文件存储,

13-flask博客项目之restful api详解2-使用插图91

 他们就对应我们从不同里面取值是一样的。像下面电话里面,是可以填写多个location的,这样支持多种数据提交

13-flask博客项目之restful api详解2-使用插图92

看下文件存储类,它里面也是用了之前我们使用的存储验证码图片用的字节io对象。

13-flask博客项目之restful api详解2-使用插图93

 

 因为我们需要上传文件了,所以需要将数据修改位formdata。我们点击后面bulk edit

13-flask博客项目之restful api详解2-使用插图94

 

 复制粘贴键值对到form-data中

13-flask博客项目之restful api详解2-使用插图95

 

 再点击一下,粘贴到formdata里面键值对编辑。

13-flask博客项目之restful api详解2-使用插图96

 

 这样就将键值对复制过来了

13-flask博客项目之restful api详解2-使用插图97

 

 选中所有,点击块编辑

13-flask博客项目之restful api详解2-使用插图98

 

 可以看到,没有双斜线//了,这说明没有选中使用的键值对是//这种注释,没有注释//的就是被选中需要使用的键值对。每个键值对都是冒号隔开,多个键值对换行分隔,这样我们就能批量添加,修改和删除键值对了。所以这个地方叫块编辑。而返回到之前的状态就是键值对编辑。描述信息在这里是怎么定义的一会看,我试了一下,键值对描述信息在块编辑里面不显示

13-flask博客项目之restful api详解2-使用插图99

我们添加文件存储参数,

from werkzeug.datastructures import FileStorage

parser.add_argument('icon', type=FileStorage, location=['files'])

post请求里面再获取头像文件

13-flask博客项目之restful api详解2-使用插图100

 

 我们在后端post请求逻辑中,根据头像字段名称是从参数解析器里面获取头像文件,这是个文件存储对象。如果或者到头像文件对象,那么就保存在服务器上,然后将图片的相对路径写入到数据库中。

而前端是用postman,使用form data方式添加字段,发送post请求。将key修改为file类型。然后点击value里就可以将文件加进来,点击发送,就会请求到后端。最终走到post请求这个方法里,然后保存文件,保存文件路径到数据库,并返回这个用户对象的信息,按照之前定好的用户对象字段格式。

13-flask博客项目之restful api详解2-使用插图101

 

 13-flask博客项目之restful api详解2-使用插图102

 

 因为是按照这里定义的格式返回的数据,所以并没有返回图片字段的信息

13-flask博客项目之restful api详解2-使用插图103

 参数继承,新建一个继承其它写好的参数解析器,然后在原基础上做增删改等

参数继承

 13-flask博客项目之restful api详解2-使用插图104

from flask_restful import reqparse

parser = reqparse.RequestParser()
parser.add_argument('foo', type=int)

parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)

# parser_copy has both 'foo' and 'bar'

parser_copy.replace_argument('foo', required=True, location='json')
# 'foo' is now a required str located in json, not an int as defined
#  by original parser

parser_copy.remove_argument('foo')
# parser_copy no longer has 'foo' argument

 错误信息处理

如果添加bundle_errors=True,那么响应数据,会将所有字段都校验完,将校验结果返回给客户端。而如果没有添加的话,那么只会返回第一个校验失败的消息,其它错误消息不会返回,甚至可能都没有往下进行校验

from flask_restful import reqparse

parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('foo', type=int, required=True)
parser.add_argument('bar', type=int, required=True)

# If a request comes in not containing both 'foo' and 'bar', the error that
# will come back will look something like this.

{
    "message":  {
        "foo": "foo error message",
        "bar": "bar error message"
    }
}

# The default behavior would only return the first error

parser = RequestParser()
parser.add_argument('foo', type=int, required=True)
parser.add_argument('bar', type=int, required=True)

{
    "message":  {
        "foo": "foo error message"
    }
}

13-flask博客项目之restful api详解2-使用插图105

 

 错误消息

from flask_restful import reqparse

parser = reqparse.RequestParser()
parser.add_argument(
    'foo',
    choices=('one', 'two'),
    help='Bad choice: {error_msg}'
)

# If a request comes in with a value of "three" for foo:

{
    "message":  {
        "foo": "Bad choice: three is not a valid choice",
    }
}

 

输出字段

输出字段可以设置复杂结构

13-flask博客项目之restful api详解2-使用插图106

 

 

输出字段

Flask-RESTful 提供了一个简单的方式来控制在你的响应中实际呈现什么数据。使用 fields 模块,你可以使用在你的资源里的任意对象(ORM 模型、定制的类等等)并且 fields 让你格式化和过滤响应,因此您不必担心暴露内部数据结构。

当查询你的代码的时候,哪些数据会被呈现以及它们如何被格式化是很清楚的。

 

也就是我在这里减少一个字段,那么用户就看不到这个字段,如果添加上那么用户就能看到这个字段

13-flask博客项目之restful api详解2-使用插图107

基本用法

你可以定义一个字典或者 fields 的 OrderedDict 类型,OrderedDict 类型是指键名是要呈现的对象的属性或键的名称,键值是一个类,该类格式化和返回的该字段的值。这个例子有三个字段,两个是字符串(Strings)以及一个是日期时间(DateTime),格式为 RFC 822 日期字符串(同样也支持 ISO 8601)

from flask_restful import Resource, fields, marshal_with

resource_fields = {
    'name': fields.String,
    'address': fields.String,
    'date_updated': fields.DateTime(dt_format='rfc822'),
}

class Todo(Resource):
    @marshal_with(resource_fields, envelope='resource')
    def get(self, **kwargs):
        return db_get_todo()  # Some function that queries the db

这个例子假设你有一个自定义的数据库对象(todo),它具有属性:name, address, 以及 date_updated。该对象上任何其它的属性可以被认为是私有的不会在输出中呈现出来。一个可选的 envelope 关键字参数被指定为封装结果输出。

装饰器 marshal_with 是真正接受你的数据对象并且过滤字段。marshal_with 能够在单个对象,字典,或者列表对象上工作。

注意:marshal_with 是一个很便捷的装饰器,在功能上等效于如下的 return marshal(db_get_todo(), resource_fields), 200。这个明确的表达式能用于返回 200 以及其它的 HTTP 状态码作为成功响应(错误响应见 abort)。

13-flask博客项目之restful api详解2-使用插图108

 

 给字段多加个括号,里面加上东西,看着没区别啊

13-flask博客项目之restful api详解2-使用插图109

重命名属性

很多时候你面向公众的字段名称是不同于内部的属性名。使用 attribute 可以配置这种映射。

fields = {
    'name': fields.String(attribute='private_name'),
    'address': fields.String,
}

没添加之前

13-flask博客项目之restful api详解2-使用插图110

 

 添加之后

13-flask博客项目之restful api详解2-使用插图111

 

 点进去

13-flask博客项目之restful api详解2-使用插图112

 

 string和integer这些继承Raw

13-flask博客项目之restful api详解2-使用插图113

 

 raw里面添加了attribute了,还有个默认,那么我们添加一个默认

13-flask博客项目之restful api详解2-使用插图114

 

 这样实现了字段值的隐匿性。也实现了重命名字段值

13-flask博客项目之restful api详解2-使用插图115

 

 我们也可以修改前端展示的字段名字。上面是没有修改,字段值跟模型的字段值名字是一样,所以前端展示的也是跟模型的字段值一样是username;下面我们就让前端看到的和模型字段名字不同,这样客户端请求后就不能获取到我们数据库真实的字段名称了,数据库也相对更安全,做法如下:用户字段这里修改为前端能看到的字段名称private_name,然后在字符串对象里面添加attribute属性,属性值使用这个字段对应的模型中字段的名称,这样将二者关联起来,后面数据就会使用这个字段的数据库的值了,后面还有个默认值,也就是没有值的话,就会用到这个默认值。

我们看了两个字段类型都是继承Raw这个类,没看过是否所有都继承Raw,不过猜测大部分都是继承Raw的,那么继承了的就会都可以添加attribute 和default两个参数,那么它们就都可以修改给前端的展示字段,隐匿数据库字段名,给请求客户端展示的都是我这里定义的字段值,而非数据库字段值。也就是前端看到的不一定就是数据库字段名

13-flask博客项目之restful api详解2-使用插图116

如下,如果不适应attribute来修改前端显示字段名,那么字段名默认是药和数据库字段名是保持一致的。不然是找不到数据的,找不到就显示null了

13-flask博客项目之restful api详解2-使用插图117

 

 总结:

1.需要定义字典,字典的格式就是给客户端看的格式
user_fields = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'pwd': fields.String(attribute='password'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

客户端能看到的是: id,username,pwd,udatetime这四个key
默认key的名字是跟model中的模型属性名一致,如果不想让前端看到命名,则可以修改
但是必须结合attribute='模型的字段名'

lambda 也能在 attribute 中使用

fields = {
    'name': fields.String(attribute=lambda x: x._private_name),
    'address': fields.String,
}

也可以使用属性访问嵌套属性

fields = {
    'name': fields.String(attribute='people_list.0.person_dictionary.name'),
    'address': fields.String,
}

默认值

如果由于某种原因你的数据对象中并没有你定义的字段列表中的属性,你可以指定一个默认值而不是返回 None

fields = {
    'name': fields.String(default='Anonymous User'),
    'address': fields.String,
}

自定义字段&多个值

有时候你有你自己定义格式的需求。你可以继承 fields.Raw 类并且实现格式化函数。当一个属性存储多条信息的时候是特别有用的。例如,一个位域(bit-field)各位代表不同的值。你可以使用 fields 复用一个单一的属性到多个输出值(一个属性在不同情况下输出不同的结果)。

这个例子假设在 flags 属性的第一位标志着一个“正常”或者“迫切”项,第二位标志着“读”与“未读”。这些项可能很容易存储在一个位字段,但是可读性不高。转换它们使得具有良好的可读性是很容易的。

自定义字段输出,需要继承Raw,然后重写format方法

class UrgentItem(fields.Raw):
    def format(self, value):
        return "Urgent" if value & 0x01 else "Normal"

class UnreadItem(fields.Raw):
    def format(self, value):
        return "Unread" if value & 0x02 else "Read"

fields = {
    'name': fields.String,
    'priority': UrgentItem(attribute='flags'),
    'status': UnreadItem(attribute='flags'),
}

我们在模型里面添加一个布尔类型的数据

13-flask博客项目之restful api详解2-使用插图118

 我们给1 5 7 设置为已经被删除的,其它默认就是未删除就是0状态

13-flask博客项目之restful api详解2-使用插图119

 我们添加一个查询时将这个字段展示给前端。我们可以看到数据库中存储的是布尔值类型的,是0和1数据。客户端请求时时显示true和false。这里给前端展示的是用大写的D。我们想要让前端展示的内容根据数据库值不同显示其它的可读信息。0展示未删除,1展示已删除。这样我们需要添加一个判断

13-flask博客项目之restful api详解2-使用插图120

 我们如官网样例,添加判断。

13-flask博客项目之restful api详解2-使用插图121

 

 所以我们可以自己定义输出字段 。我们前面用的string和integer这些字段都是继承Raw类的,我们自己创建一个自定义输出字段,也需要继承Raw类,然后重写format方法,里面放一个value形参。然后在输出字段字典里添加一个展示给客户的字段,也是让它关联上isdelete模型字段。但是用的不是组件里面的输出字段类,而是我们自己写的isDelete类,因为它继承Raw类,所以我们也可以传参属性attribute,让我们定义的能跟isdelete模型字段关联起来。

再看看我们定义的输出字段类,里面的value就是模型中关联字段的值,存入的是0 1 布尔类型,所以打印出来是true和false,我们做了个判断,如果true返回什么字符串,否则返回什么字符串,这样就将输出修改掉了。数据库中的值是固定的两个,这里就能根据值来返回我们想要给前端展示的可读性好的一个字符串值。返回内容定制化

13-flask博客项目之restful api详解2-使用插图122

Url & 其它具体字段

  • 用于xx列表展示页和某个xx详情页
  • 需要定义两个视图类,一个是所有xx,响应所有xx字段格式,里面不包含xx所有字段,但包含每个xx的详情页访问地址,使用详情页路由的endpoint
  • 另一个是单个xx详情视图类,路由和方法上都要有单个xx的模型id,路由上传的id要和模型id名称一致

Flask-RESTful 包含一个特别的字段,fields.Url,即为所请求的资源合成一个 uri。这也是一个好示例,它展示了如何添加并不真正在你的数据对象中存在的数据到你的响应中。

class RandomNumber(fields.Raw):
    def output(self, key, obj):
        return random.random()

fields = {
    'name': fields.String,
    # todo_resource is the endpoint name when you called api.add_resource()
    'uri': fields.Url('todo_resource'),
    'random': RandomNumber,
}

默认情况下,fields.Url 返回一个相对的 uri。为了生成包含协议(scheme),主机名以及端口的绝对 uri,需要在字段声明的时候传入 absolute=True。传入 scheme 关键字参数可以覆盖默认的协议(scheme):

fields = {
    'uri': fields.Url('todo_resource', absolute=True)
    'https_uri': fields.Url('todo_resource', absolute=True, scheme='https')
}

就是生成一个访问地址,点击一下就能够访问它

13-flask博客项目之restful api详解2-使用插图2713-flask博客项目之restful api详解2-使用插图28

from flask import Blueprint, url_for
from flask_restful import Resource, marshal_with, fields,reqparse,inputs
from exts import api
from werkzeug.datastructures import FileStorage
from settings import Config
import os
user_bp = Blueprint('user', __name__, url_prefix='/api')
from apps.user.model import User
from exts import db

class IsDelete(fields.Raw):
    def format(self, value):
        print('------------------>', value)
        return '删除' if value else '未删除'
user_fields_1 = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'uri': fields.Url('single_user', absolute=True)
}

user_fields = {
    'id': fields.Integer,
    'private_name': fields.String(attribute='username',default='匿名'),
    'password': fields.String,
    'isDelete': fields.Boolean(attribute='isdelete'),
    'isDelete1': IsDelete(attribute='isdelete'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

# 参数解析
parser = reqparse.RequestParser(bundle_errors=True)  # 解析对象
# a783789893hf     request.form.get()  | request.args.get() | request.cookies.get() | request.headers.get()
parser.add_argument('username', type=str, required=True, help='必须输入用户名', location=['form'])
parser.add_argument('password', type=inputs.regex(r'^d{6,12}$'), required=True, help='必须输入6~12位数字密码',
                    location=['form'])
parser.add_argument('phone', type=inputs.regex(r'^1[356789]d{9}$'), location=['form'], help='手机号码格式错误')
parser.add_argument('hobby', action='append')  # ['篮球', '游戏', '旅游']
parser.add_argument('icon', type=FileStorage, location=['files'])

# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    @marshal_with(user_fields_1)
    def get(self):
        users = User.query.all()
        # userList = []
        # for user in users:
        #     userList.append(user.__dict__)
        return users

    @marshal_with(user_fields)
    def post(self):
        # 获取数据
        args = parser.parse_args()

        username = args.get('username')
        password = args.get('password')
        phone = args.get('phone')
        bobby = args.get('hobby')
        print(bobby)
        icon = args.get('icon')
        print(icon)
        # 创建user对象
        user = User()
        user.username = username
        user.password = password
        if icon:
            upload_path = os.path.join(Config.UPLOAD_ICON_DIR, icon.filename)
            icon.save(upload_path)
            # 保存路径个
            user.icon = os.path.join('upload/icon', icon.filename)
        if phone:
            user.phone = phone
        db.session.add(user)
        db.session.commit()

        return user
    # put
    def put(self):
        return {'msg': '------>put'}
    # delete
    def delete(self):
        return {'msg': '------>delete'}

class UserSimpleResource(Resource):
    @marshal_with(user_fields)  # user转成一个序列化对象,
    def get(self, id):
        user = User.query.get(id)
        return user  # 不是str,list,int,。。。

    def put(self, id):
        print('endpoint的使用:', url_for('all_user'))
        return {'msg': 'ok'}

    def delete(self, id):
        pass

api.add_resource(UserResource, '/user',endpoint='all_user')
api.add_resource(UserSimpleResource, '/user/', endpoint='single_user')

蓝图中所以代码

我们添加一个展示字段的字典,前面的是展示所有用户的,但是不展示用户所有字段,指包含用户名和每个用户详情访问地址,点击用户访问地址就能看到该用户的详情,所有字段;后面的给前端展示单个用户的,也就是用户详情,

13-flask博客项目之restful api详解2-使用插图123

 

 查看所有用户和单个用户的视图类的get,要和对应字段响应格式绑定上。

13-flask博客项目之restful api详解2-使用插图124

 

 用户详情的要传用户id。传参字段名要和模型中用户id字段名保持一致,之前使用uid但是报错了,名称对不上和模型中,这个id也可能是响应字段字典中对应的字段名,也就是给前端看的那个字段名,后面测试。传参进去,才能查出并返回单个用户对象

13-flask博客项目之restful api详解2-使用插图125

 

 Url输出字段,第一个参数填字符串,是放用户详情的endpoint。这里使用absolute 

13-flask博客项目之restful api详解2-使用插图126

 

 网页测试:

访问所有用户,响应所有用户的,生成单个用户的访问地址。这个地址如何生成的呢?根据反向解析找的路径,然后根据传参id,在这里的id还是在模型里的id添加上的数字

13-flask博客项目之restful api详解2-使用插图127

 

 点击id是1的地址打开一个postman请求标签页,就是用户1的访问地址,点击发送

13-flask博客项目之restful api详解2-使用插图128

 

 成为走单个用户,走响应详情的字段格式。这样的话,像首页博客列表,每个博客详情页需要地址 ;商品列表,每个商品详情页需要地址;产品列表,每个产品详情页需要地址等等类似的,都可以这样来做

13-flask博客项目之restful api详解2-使用插图129

 当我们将字段改为uid时

13-flask博客项目之restful api详解2-使用插图130

 

 fields.Url根据endpoint反向解析路径,会找不到uid,因此路由添加的用户id传参和视图名称要和model里面用户id名称要一致才行。model里面是id,这里传参也是用id才能根据名字相同,在fields.Url反向解析时,将用户id拼接到url访问地址上,成为用户详情访问地址

13-flask博客项目之restful api详解2-使用插图131

 

13-flask博客项目之restful api详解2-使用插图132

复杂结构以及第一第二张表是同一张表的表结构设计

你可以有一个扁平的结构,marshal_with 将会把它转变为一个嵌套结构

>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String}
>>> resource_fields['address'] = {}
>>> resource_fields['address']['line 1'] = fields.String(attribute='addr1')
>>> resource_fields['address']['line 2'] = fields.String(attribute='addr2')
>>> resource_fields['address']['city'] = fields.String
>>> resource_fields['address']['state'] = fields.String
>>> resource_fields['address']['zip'] = fields.String
>>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'

注意:address 字段并不真正地存在于数据对象中,但是任何一个子字段(sub-fields)可以直接地访问对象的属性,就像没有嵌套一样。

我们根据用户表构建一个朋友表,用户表是第一张表,第二张表还是用户表,这个朋友表相当于第三张表。 因为朋友也是用户,朋友的信息也是在用户表里面,只是朋友表里面存放的使用某个用户和其它用户的关系映射,是同一张表不同行之间的关系映射。这个应该是可以做成一对一和一对多 多对多的关系的,只要设定好下面那张表的外键uid和Fid是否是唯一键就可以实现。

13-flask博客项目之restful api详解2-使用插图133

 

 在原有用户表上,添加朋友表和关系映射对象,迁移数据

13-flask博客项目之restful api详解2-使用插图134

 

 我们给朋友表添加数据

13-flask博客项目之restful api详解2-使用插图135

 

 我们添加用户朋友表的路由和视图类,这里只是查询,就只写get就好了

13-flask博客项目之restful api详解2-使用插图136

 

 我们再看下用户朋友视图类,我们要查用户朋友,需要知道是查哪个用户的朋友,所以路由上需要添加上用户的id。视图类方法中需要接收这个用户id参数,因为用户的朋友还是用户,所以我们根据用户id从用户表中查到该用户的朋友列表。然后构造数据结构响应给请求客户端。需要响应的数据结构如下:要显示是哪个用户,有多少个朋友,然后是朋友列表里面每个朋友的信息,每个朋友就是一个用户详情的信息 。然后将这个数据结构返回给客户端。那么restful api中想要响应这种复杂的数据结构,是需要如何实现呢

13-flask博客项目之restful api详解2-使用插图137

 

 那么朋友列表需要怎么做呢?friends是我们根据传进来的用户id在朋友表中查询来的该用户对应的所有朋友的id。但是我们需要给客户端传朋友的详情而不是朋友的id,所以需要根据所有朋友的id在用户表中找的所有朋友的用户对象。这里是遍历朋友id列表,然后在用户表中查出每个朋友对象并追加到朋友列表中。这样我们就有了朋友对象列表了,而返回客户端详情信息,我们一般都是通过传返回对象,然后根据输出字段格式将对象的所有字段显示给客户端的

 13-flask博客项目之restful api详解2-使用插图138

 

 这时我们请求一下试试,我们可以看到报错外键错误

13-flask博客项目之restful api详解2-使用插图139

 

 在后端报错 foreign_keys,需要个外键参数

  File "D:softwareinstallpython3libsite-packagessqlalchemyutilcompat.py", line 208, in raise_
    raise exception
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.friends - there are multiple foreign key paths linking the tables.  Specify th
e 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.

我们添加到这里试试

13-flask博客项目之restful api详解2-使用插图140

 

 还是会报错

13-flask博客项目之restful api详解2-使用插图141

 

 我们把朋友列表先注释试试

13-flask博客项目之restful api详解2-使用插图142

 

 还是会报错

13-flask博客项目之restful api详解2-使用插图143

 

 将关系映射字段先注释掉

13-flask博客项目之restful api详解2-使用插图144

 

 get这里返回的是个字典,没有model对象。没有model对象的时候没有使用装饰器也是可以正常请求到的。如果有model对象返回,是需要添加装饰器然后绑定输出字段格式,那么这里需要怎么做呢,这里其它的已经正常输出给客户端了,我们需要将朋友对象列表也返回给客户端

13-flask博客项目之restful api详解2-使用插图145

 

 

如果我们直接返回朋友对象列表,是没有序列化的。

13-flask博客项目之restful api详解2-使用插图146

 

 

 我们可以使用marshal加工一下。将model对象列表放到marshl中,然后指定列表中每个对象需要按照哪个输出字段格式usre_fields展示给客户端。这样就会将包含model对象的数据按指定格式去展示给客户端了。如果这里有多个model对象展示,那么我们可以构造含有多个model对象的数据结构,将每个model对象或对象列表都用一下marshal,并且定义好对象的返回格式。这里不能用marshal_with这个装饰器,因为marshal_with虽然能序列化model对象或者对象列表,但是不能格式化其它数据,这里的数据还包含了username,nums等等。要使用装饰器的方式需要另外做处理,后面讲

from flask_restful import  marshal

'friends': marshal(friend_list,user_fields)

13-flask博客项目之restful api详解2-使用插图147

 

 我们用页面访问一下。正是我们需要的样式

13-flask博客项目之restful api详解2-使用插图148

 

 

列表字段

你也可以把字段解组(unmarshal)成列表

>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)}
>>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']}
>>> json.dumps(marshal(data, resource_fields))
>>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'

高级:嵌套字段

尽管使用字典套入字段能够使得一个扁平的数据对象变成一个嵌套的响应,你可以使用 Nested 解组(unmarshal)嵌套数据结构并且合适地呈现它们。

>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> address_fields = {}
>>> address_fields['line 1'] = fields.String(attribute='addr1')
>>> address_fields['line 2'] = fields.String(attribute='addr2')
>>> address_fields['city'] = fields.String(attribute='city')
>>> address_fields['state'] = fields.String(attribute='state')
>>> address_fields['zip'] = fields.String(attribute='zip')
>>>
>>> resource_fields = {}
>>> resource_fields['name'] = fields.String
>>> resource_fields['billing_address'] = fields.Nested(address_fields)
>>> resource_fields['shipping_address'] = fields.Nested(address_fields)
>>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2}
>>>
>>> json.dumps(marshal_with(data, resource_fields))
'{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}'

此示例使用两个嵌套字段。Nested 构造函数把字段的字典作为子字段(sub-fields)来呈现。使用 Nested 和之前例子中的嵌套字典之间的重要区别就是属性的上下文。在本例中 “billing_address” 是一个具有自己字段的复杂的对象,传递给嵌套字段的上下文是子对象(sub-object),而不是原来的“数据”对象。换句话说,data.billing_address.addr1 是在这里的范围(译者:这里是直译),然而在之前例子中的 data.addr1 是位置属性。记住:嵌套和列表对象创建一个新属性的范围。

如下,我们这里使用嵌套字段实现上面那个案例

user_fields = {
    'id': fields.Integer(attribute='id'),
    'private_name': fields.String(attribute='username',default='匿名'),
    'password': fields.String,
    'isDelete': fields.Boolean(attribute='isdelete'),
    'isDelete1': IsDelete(attribute='isdelete'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

user_friend_fields = {
    'username': fields.String,
    'nums': fields.Integer,
    'friends': fields.List(fields.Nested(user_fields))
}

class UserFriendResource(Resource):
    @marshal_with(user_friend_fields)
    def get(self, id):
        friends = Friend.query.filter(Friend.uid == id).all()
        user = User.query.get(id)

        friend_list = []
        for friend in friends:
            u = User.query.get(friend.fid)
            friend_list.append(u)

        data = {
            'username': user.username,
            'nums': len(friends),
            'friends': friend_list  # [user,user,user]
        }
        return data

如下,我们不用marshal了,用marshal_with。我们的输出数据还是data,data里面有friend_list这个model对象列表。把data返回去,那么需要将它交给marsh_with这个装饰器处理,这个装饰器里面需要指定user_friend_fields这个我们定义的输出字段格式,这个输出字段格式和我们在get方法里面定义的数据结构是一样的,但是有model对象的地方它是做了处理的。处理的方式就是使用fields.List(),将朋友对象列表,解组成朋友信息列表,然后指定user_fields这个输出字段格式来输出给客户端。而user_fields是之前定义的用户详情输出字段格式。

如果返回的数据中有多个model对象或者对象列表,那么应该也是可以这样做的,然后在外面定义一个同样数据结构的字段格式。将这个字段格式有model对象或对象列表的地方再使用下面的方式来嵌套其它输出字段格式。这就完成了复杂的数据结构输出。如果friends_list里面还要更复杂的嵌套那就一层层往里面嵌套,有时间去试试

13-flask博客项目之restful api详解2-使用插图149

 

 13-flask博客项目之restful api详解2-使用插图150

 

13-flask博客项目之restful api详解2-使用插图151

 

 这一章总结

知识点

什么是RESTful架构:

(1)每一个URI代表一种资源;

(2)客户端和服务器之间,传递这种资源的某种表现层;

(3)客户端通过四个HTTP动词(GET,POST,PUT,DELETE,[PATCH]),对服务器端资源进行操作,实现"表现层状态转化"。

Postman

前后端分离:
前端: app,小程序,pc页面

后端: 没有页面,mtv: 模型模板视图  去掉了t模板。
      mv:模型 视图
      模型的使用:跟原来的用法相同
      视图: api构建视图
  步骤:
     1. pip3 install flask-restful

     2.创建api对象
      api = Api(app=app)
      api = Api(app=蓝图对象)
     3.
      定义类视图:
      from flask_restful import Resource
      class xxxApi(Resource):
        def get(self):
            pass

        def post(self):
            pass

        def put(self):
            pass

        def delete(self):
            pass
      4. 绑定

      api.add_resource(xxxApi,'/user')

参照:http://www.pythondoc.com/Flask-RESTful/quickstart.html
https://flask-restful.readthedocs.io/en/latest/


路由:
@app.route('/user')
def user():              -------》视图函数
    .....
    return response对象

增加  修改   删除   查询  按钮动作

http://127.0.0.1:5000/user?id=1
http://127.0.0.1:5000/user/1


restful: ---->api ----> 接口 ---->资源 ----> url

class xxx(Resource):  -------> 类视图
    def get(self):
        pass
    ....

http://127.0.0.1:5000/user
get
post
put
delete
增加  修改   删除   查询  是通过请求方式完成的

路径产生:
api.add_resource(Resource的子类,'/user')
api.add_resource(Resource的子类,'/goods')
api.add_resource(Resource的子类,'/order')

endpoint:
http://127.0.0.1:5000/user/1

http://127.0.0.1:5000/goods?type=xxx&page=1&sorted=price   ----》get

----------------进:请求参数传入-------------------

步骤:
1。创建RequestParser对象:
# 参数解析
parser = reqparse.RequestParser(bundle_errors=True)  # 解析对象
2。给解析器添加参数:
    通过parser.add_argument('名字',type=类型,required=是否必须填写,help=错误的提示信息,location=表明获取的位置form就是post表单提交)
    注意在type的位置可以添加一些正则的验证等。
    例如:
    parser.add_argument('username', type=str, required=True, help='必须输入用户名', location=['form'])
    parser.add_argument('password', type=inputs.regex(r'^d{6,12}$'), required=True, help='必须输入6~12位数字密码',
                        location=['form'])
    parser.add_argument('phone', type=inputs.regex(r'^1[356789]d{9}$'), location=['form'], help='手机号码格式错误')
    parser.add_argument('hobby', action='append')  # ['篮球', '游戏', '旅游']
    parser.add_argument('icon', type=FileStorage, location=['files'])
只要添加上面的内容,就可以控制客户端的提交,以及提交的格式。
3。在请求的函数中获取数据:
  可以在get,post,put等中获取数据,通过parser对象.parse_args()
  # 获取数据
  args = parser.parse_args()
  args是一个字典底层的结构中,因此我们获取具体的数据时可以通过get
  username = args.get('username')
  password = args.get('password')

------------输出-----------------

1.需要定义字典,字典的格式就是给客户端看的格式
user_fields = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'pwd': fields.String(attribute='password'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

客户端能看到的是: id,username,pwd,udatetime这四个key
默认key的名字是跟model中的模型属性名一致,如果不想让前端看到命名,则可以修改
但是必须结合attribute='模型的字段名'

自定义fields
1。必须继承Raw
2。重写方法:
   def format(self):
        return 结果

class IsDelete(fields.Raw):
    def format(self, value):
        print('------------------>', value)
        return '删除' if value else '未删除'
user_fields = {
    。。。
    'isDelete1': IsDelete(attribute='isdelete'),
    。。。
}

URI:

xxxlist ----->点击具体的一个获取详情 ------> 详情

定义两个user_fields,
1.用于获取用户的列表信息结构的fields:
user_fields_1 = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'uri': fields.Url('single_user', absolute=True)  ----》参数使用的就是endpoint的值
}

2。具体用户信息展示的fields
user_fields = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'pwd': fields.String(attribute='password'),
    'isDelete': fields.Boolean(attribute='isdelete'),
    'isDelete1': IsDelete(attribute='isdelete'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

涉及endpoint的定义:

api.add_resource(UserSimpleResource, '/user/', endpoint='single_user')

出:
return data
注意:data必须是符合json格式
{
  'aa':10,
  'bb':[
     {
       'id':1,
       'xxxs':[
                {},{}
              ]
     },
     {

     }
  ]
}
如果直接返回不能有自定义的对象User,Friend,。。。。

如果有这种对象,需要:marchal(),marchal_with()帮助进行转换。
1。marchal(对象,对象的fields格式)  # 对象的fields格式是指字典的输出格式
   marchal([对象,对象],对象的fields格式)

2。marchal_with() 作为装饰器修饰请求方法

    @marshal_with(user_friend_fields)
    def get(self, id):
        。。。。
        return data

 函数需要参数,参数就是最终数据输出的格式

 参数: user_friend_fields,类型是:dict类型
 例如:
 user_friend_fields = {
    'username': fields.String,
    'nums': fields.Integer,
    'friends': fields.List(fields.Nested(user_fields))
}

fields.Nested(fields.String)  ----> ['aaa','bbb','bbbc']
fields.Nested(user_fields)  -----> user_fields是一个字典结构,将里面的每一个对象转成user_fields
-----》[user,user,user]

程序

13-flask博客项目之restful api详解2-使用插图2713-flask博客项目之restful api详解2-使用插图28

import os

from flask import Blueprint, url_for
from flask_restful import Resource, marshal_with, fields, reqparse, inputs, marshal
from werkzeug.datastructures import FileStorage

from apps.user.model import User, Friend
from exts import api, db
from settings import Config

user_bp = Blueprint('user', __name__, url_prefix='/api')

class IsDelete(fields.Raw):
    def format(self, value):
        # print('------------------>', value)
        return '删除' if value else '未删除'

user_fields_1 = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'uri': fields.Url('single_user', absolute=True)
}

user_fields = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'pwd': fields.String(attribute='password'),
    'isDelete': fields.Boolean(attribute='isdelete'),
    'isDelete1': IsDelete(attribute='isdelete'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

# 参数解析
parser = reqparse.RequestParser(bundle_errors=True)  # 解析对象
# a783789893hf     request.form.get()  | request.args.get() | request.cookies.get() | request.headers.get()
parser.add_argument('username', type=str, required=True, help='必须输入用户名', location=['form'])
parser.add_argument('password', type=inputs.regex(r'^d{6,12}$'), required=True, help='必须输入6~12位数字密码',
                    location=['form'])
parser.add_argument('phone', type=inputs.regex(r'^1[356789]d{9}$'), location=['form'], help='手机号码格式错误')
parser.add_argument('hobby', action='append')  # ['篮球', '游戏', '旅游']
parser.add_argument('icon', type=FileStorage, location=['files'])

# 定义类视图
class UserResource(Resource):
    # get 请求的处理
    @marshal_with(user_fields_1)
    def get(self):
        users = User.query.all()
        # userList = []
        # for user in users:
        #     userList.append(user.__dict__)
        return users

    # post
    @marshal_with(user_fields)
    def post(self):
        # 获取数据
        args = parser.parse_args()

        username = args.get('username')
        password = args.get('password')
        phone = args.get('phone')
        bobby = args.get('hobby')
        print(bobby)
        icon = args.get('icon')
        print(icon)
        # 创建user对象
        user = User()
        user.username = username
        user.password = password
        if icon:
            upload_path = os.path.join(Config.UPLOAD_ICON_DIR, icon.filename)
            icon.save(upload_path)
            # 保存路径个
            user.icon = os.path.join('upload/icon', icon.filename)
        if phone:
            user.phone = phone
        db.session.add(user)
        db.session.commit()

        return user

    # put
    def put(self):
        return {'msg': '------>put'}

    # delete
    def delete(self):
        return {'msg': '------>delete'}

class UserSimpleResource(Resource):
    @marshal_with(user_fields)  # user转成一个序列化对象,
    def get(self, id):
        user = User.query.get(id)
        return user  # 不是str,list,int,。。。

    def put(self, id):
        print('endpoint的使用:', url_for('all_user'))
        return {'msg': 'ok'}

    def delete(self, id):
        pass

user_friend_fields = {
    'username': fields.String,
    'nums': fields.Integer,
    'friends': fields.List(fields.Nested(user_fields))
}

class UserFriendResource(Resource):

    @marshal_with(user_friend_fields)
    def get(self, id):
        friends = Friend.query.filter(Friend.uid == id).all()
        user = User.query.get(id)

        friend_list = []
        for friend in friends:
            u = User.query.get(friend.fid)
            friend_list.append(u)

        data = {
            'username': user.username,
            'nums': len(friends),
            'friends': friend_list  # [user,user,user]
        }
        return data

api.add_resource(UserResource, '/user', endpoint='all_user')
api.add_resource(UserSimpleResource, '/user/', endpoint='single_user')
api.add_resource(UserFriendResource, '/friend/', endpoint='user_friend')

视图函数

13-flask博客项目之restful api详解2-使用插图2713-flask博客项目之restful api详解2-使用插图28

import datetime

from exts import db

class Friend(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    uid = db.Column(db.Integer, db.ForeignKey('user.id'))
    fid = db.Column(db.Integer, db.ForeignKey('user.id'))

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(15), nullable=False)
    password = db.Column(db.String(12), nullable=False)
    phone = db.Column(db.String(11))
    icon = db.Column(db.String(150))
    isdelete = db.Column(db.Boolean())
    email = db.Column(db.String(100))
    udatetime = db.Column(db.DateTime, default=datetime.datetime.now)

    friends = db.relationship('Friend', backref='user', foreign_keys=Friend.uid)

    def __str__(self):
        return self.username

model

 蓝图中写restful api  待添加

 

文章来源于互联网:13-flask博客项目之restful api详解2-使用

THE END
分享
二维码