磨方APP项目-04-用户模块API接口、Marshmallow,基本构造器(Schema),Schema数据序列化、Schema数据反序列化、反序列化对数据验证、模型构造器(ModelSchema)
用户模块
当前开发的项目属于社交类型项目,所以关于用户的信息和功能直接贯穿了整个项目。所以此处实现用户模块功能,我们先把用户基本信息构建起来,并通过基本信息实现用户注册登录相关功能,后面遇到业务再继续扩展。
服务端实现API接口
创建并注册用户蓝图以及路由信息。
cd application/apps
python ../../manage.py blue -n users
application.settings.dev
,代码:
# 注册蓝图
INSTALLED_APPS = [
"application.apps.home",
"application.apps.users",
]
application.urls
,代码:
from application.utils import include
urlpatterns = [
include("","home.urls"),
include("/users","users.urls"),
]
用户相关模型
application.utils.models
,代码:
from application import db
from datetime import datetime
class BaseModel(db.Model):
"""公共模型"""
__abstract__ = True # 抽象模型
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(255), default="", comment="名称/标题")
is_deleted = db.Column(db.Boolean, default=False, comment="逻辑删除")
orders = db.Column(db.Integer, default=0, comment="排序")
status = db.Column(db.Boolean, default=True, comment="状态(是否显示,是否激活)")
created_time = db.Column(db.DateTime, default=datetime.now, comment="创建时间")
updated_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.name)
application.apps.user.models
,代码:
from application.utils.models import BaseModel,db
from werkzeug.security import generate_password_hash, check_password_hash
class User(BaseModel):
"""用户基本信息表"""
__tablename__ = "mf_user"
name = db.Column(db.String(255), index=True, comment="用户账户")
nickname = db.Column(db.String(255), comment="用户昵称")
_password = db.Column(db.String(255), comment="登录密码")
age = db.Column(db.SmallInteger, comment="年龄")
money = db.Column(db.Numeric(7,2), comment="账户余额")
ip_address = db.Column(db.String(255), default="", index=True, comment="登录IP")
intro = db.Column(db.String(500), default="", comment="个性签名")
avatar = db.Column(db.String(255), default="", comment="头像url地址")
sex = db.Column(db.SmallInteger, default=0, comment="性别" ) # 0表示未设置,保密, 1表示男,2表示女
email = db.Column(db.String(32), index=True, default="", nullable=False, comment="邮箱地址")
mobile = db.Column(db.String(32), index=True, nullable=False, comment="手机号码")
unique_id = db.Column(db.String(255), index=True, default="", comment="客户端唯一标记符")
province = db.Column(db.String(255), default="", comment="省份")
city = db.Column(db.String(255), default="", comment="城市")
area = db.Column(db.String(255), default="", comment="地区")
info = db.relationship('UserProfile', backref='user', uselist=False)
@property
def password(self):
return self._password
@password.setter
def password(self, rawpwd):
"""密码加密"""
self._password = generate_password_hash(rawpwd)
def check_password(self, rawpwd):
"""验证密码"""
return check_password_hash(self.password, rawpwd)
class UserProfile(BaseModel):
"""用户详情信息表"""
__tablename__ = "mf_user_profile"
user_id = db.Column(db.Integer,db.ForeignKey('mf_user.id'), comment="用户ID")
education = db.Column(db.Integer, comment="学历教育")
middle_school = db.Column(db.String(255), default="", comment="初中/中专")
high_school = db.Column(db.String(255), default="", comment="高中/高职")
college_school = db.Column(db.String(255), default="", comment="大学/大专")
profession_cate = db.Column(db.String(255), default="", comment="职业类型")
profession_info = db.Column(db.String(255), default="", comment="职业名称")
position = db.Column(db.SmallInteger, default=0, comment="职位/职称")
emotion_status = db.Column(db.SmallInteger, default=0, comment="情感状态")
birthday =db.Column(db.DateTime, default="", comment="生日")
hometown_province = db.Column(db.String(255), default="", comment="家乡省份")
hometown_city = db.Column(db.String(255), default="", comment="家乡城市")
hometown_area = db.Column(db.String(255), default="", comment="家乡地区")
hometown_address = db.Column(db.String(255), default="", comment="家乡地址")
living_province = db.Column(db.String(255), default="", comment="现居住省份")
living_city = db.Column(db.String(255), default="", comment="现居住城市")
living_area = db.Column(db.String(255), default="", comment="现居住地区")
living_address = db.Column(db.String(255), default="", comment="现居住地址")
数据迁移,命令:
cd ../.. # 切换工作目录会到项目根目录,manage.py所在目录下
python manage.py db init
python manage.py db migrate -m "users table"
python manage.py db upgrade
Marshmallow
官方文档:https://marshmallow.readthedocs.io/en/latest/
Marshmallow,中文译作:棉花糖。是一个轻量级的数据格式转换的模块,也叫序列化和反序列化模块,常用于将复杂的orm模型对象与python原生数据类型之间相互转换。marshmallow提供了丰富的api功能。如下:
Serializing
序列化[可以把数据对象转化为可存储或可传输的数据类型,例如:objects/object->list/dict,dict/list->string]
Deserializing
反序列化器[把可存储或可传输的数据类型转换成数据对象,例如:list/dict->objects/object,string->dict/list]
Validation
数据校验,可以在反序列化阶段,针对要转换数据的内容进行类型验证或自定义验证。
Marshmallow本身是一个单独的库,基于我们当前项目使用框架是flask并且数据库ORM框架使用SQLAlchemy,所以我们可以通过安装flask-sqlalchemy和marshmallow-sqlalchemy集成到项目就可以了。
一、基本安装和配置
模块安装:
pip install -U marshmallow-sqlalchemy
pip install -U flask-sqlalchemy
pip install -U flask-marshmallow
模块初始化:
import os
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session
from flask_migrate import Migrate,MigrateCommand
from flask_jsonrpc import JSONRPC
from flask_marshmallow import Marshmallow
from application.utils import init_blueprint
from application.utils.config import load_config
from application.utils.session import init_session
from application.utils.logger import Log
from application.utils.commands import load_command
# 创建终端脚本管理对象
manager = Manager()
# 创建数据库链接对象
db = SQLAlchemy()
# redis链接对象
redis = FlaskRedis()
# Session存储对象
session_store = Session()
# 数据迁移实例对象
migrate = Migrate()
# 日志对象
log = Log()
# jsonrpc模块实例对象
jsonrpc = JSONRPC()
# 数据转换器的对象创建
ma = Marshmallow()
def init_app(config_path):
"""全局初始化"""
# 创建app应用对象
app = Flask(__name__)
# 项目根目录
app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 加载配置
Config = load_config(config_path)
app.config.from_object(Config)
# 数据库初始化
db.init_app(app)
redis.init_app(app)
# 数据转换器的初始化
ma.init_app(app)
# session存储初始化
init_session(app)
session_store.init_app(app)
# 数据迁移初始化
migrate.init_app(app,db)
# 添加数据迁移的命令到终端脚本工具中
manager.add_command('db', MigrateCommand)
# 日志初始化
app.log = log.init_app(app)
# 蓝图注册
init_blueprint(app)
# jsonrpc初始化
jsonrpc.service_url = "/api" # api接口的url地址前缀
jsonrpc.init_app(app)
# 初始化终端脚本工具
manager.app = app
# 注册自定义命令
load_command(manager)
return manager
marshmallow初始化必须在数据库初始化后面
为了方便学习和使用Marshllow, 我们单独创建一个蓝图来验证这个模块的基本使用.
cd application/apps
python ../../manage.py blue -n marsh
注册marsh模块,application.settings.dev
代码:
INSTALLED_APPS = [
"application.apps.home",
"application.apps.users",
"application.apps.marsh",
]
总路由,application.urls
代码:
from application.utils import include
urlpatterns = [
include("","home.urls"),
include("/users","users.urls"),
include("/marsh","marsh.urls"),
]
模型application.apps.marsh.models
代码:
from . import views
from application.utils import path
urlpatterns = [
path('', views.index)
]
视图application.apps.marsh.views
代码:
from marshmallow import Schema,fields
from application.apps.users.models import User,UserProfile
class UserSchema(Schema):
name = fields.String()
age = fields.Integer()
email = fields.Email()
money = fields.Number()
class Meta:
fields = ["name","age","money","email","info"]
ordered = True # 转换成有序字典
def index():
"""序列化"""
return "ok"
子路由,application.apps.marsh.urls
,代码:
from . import views
from application.utils import path
urlpatterns = [
path("", views.index),
]
二、基本构造器(Schema)
marshmallow转换数据格式主要通过构造器类来完成,而Schema类提供了数据转换的基本功能:序列化,验证和反序列化。所以在使用marshmallow的过程中所有的构造器类必须直接或间接继承于Schema基类
1.基于Schema完成数据序列化转换
application.apps.marsh.views
代码:
from marshmallow import Schema,fields
from application.apps.users.models import User,UserProfile
class UserSchema(Schema):
name = fields.String()
age = fields.Integer()
email = fields.Email()
money = fields.Number()
class Meta:
fields = ["name","age","money","email"]
ordered = True # 转换成有序字典
def index():
"""序列化"""
"""单个模型数据的序列化处理"""
user1 = User( name="xiaoming", password="123456", age=16, email="333@qq.com", money=31.50 )
# print(user1)
# 把模型对象转换成字典格式
data1 = UserSchema().dump(user1)
print(type(data1), data1) # <class 'collections.OrderedDict'> OrderedDict([('name', 'xiaoming'), ('age', 16), ('money', 31.5), ('email', '333@qq.com')])
# 把模型对象转换成json字符串格式
data2 = UserSchema().dumps(user1)
print(type(data2), data2) # <class 'str'> {"name": "xiaoming", "age": 16, "money": 31.5, "email": "333@qq.com"}
return "ok"
schema常用属性数据类型
类型 | 描述 |
---|---|
fields.Dict (keys, type]] = None, values, …) |
字典类型,常用于接收json类型数据 |
fields.List (cls_or_instance, type], **kwargs) |
列表类型,常用于接收数组数据 |
fields.Tuple (tuple_fields, *args, **kwargs) |
元组类型 |
fields.String (*, default, missing, data_key, …) |
字符串类型 |
fields.UUID (*, default, missing, data_key, …) |
UUID格式类型的字符串 |
fields.Number (*, as_string, **kwargs) |
数值基本类型 |
fields.Integer (*, strict, **kwargs) |
整型 |
fields.Decimal (places, rounding, *, allow_nan, …) |
数值型 |
fields.Boolean (*, truthy, falsy, **kwargs) |
布尔型 |
fields.Float (*, allow_nan, as_string, **kwargs) |
浮点数类型 |
fields.DateTime (format, **kwargs) |
日期时间类型 |
fields.Time (format, **kwargs) |
时间类型 |
fields.Date (format, **kwargs) |
日期类型 |
fields.Url (*, relative, schemes, Set[str]]] = None, …) |
url网址字符串类型 |
fields.Email (*args, **kwargs) |
邮箱字符串类型 |
fields.IP (*args[, exploded]) |
IP地址字符串类型 |
fields.IPv4 (*args[, exploded]) |
IPv4地址字符串类型 |
fields.IPv6 (*args[, exploded]) |
IPv6地址字符串类型 |
fields.Method (serialize, deserialize, **kwargs) |
基于Schema类方法返回值的字段 |
fields.Function (serialize, Any], Callable[[Any, …) |
基于函数返回值得字段 |
fields.Nested (nested, type, str, Callable[[], …) |
外键类型 |
Schema数据类型的常用通用属性
属性名 | 描述 |
---|---|
default | 序列化阶段中设置字段的默认值 |
missing | 反序列化阶段中设置字段的默认值 |
validate | 反序列化阶段调用的内置数据验证器或者内置验证集合 |
required | 设置当前字段的必填字段 |
allow_none | 是否允许为空 |
load_only | 是否在反序列化阶段才使用到当前字段 |
dump_omly | 是否在序列化阶段才使用到当前字段 |
error_messages | 字典类型,可以用来替代默认的字段异常提示语,格式: error_messages={“required”: “用户名为必填项。”} |
在前面进行的序列化操作属于序列化单个数据对象, MarshMallow中也可以进行多个数据对象的序列化.
from marshmallow import Schema,fields
from application.apps.users.models import User,UserProfile
class UserSchema(Schema):
name = fields.String()
age = fields.Integer()
email = fields.Email()
money = fields.Number()
# info = fields.Nested(UserProfileSchema,only=["middle_school"])
class Meta:
fields = ["name","age","money","email"]
ordered = True # 转换成有序字典
def index():
"""序列化"""
"""多个模型数据的序列化"""
user1 = User(name="xiaoming", password="123456", age=15, email="333@qq.com", money=31.50)
user2 = User(name="xiaohong", password="123456", age=16, email="333@qq.com", money=31.50)
user3 = User(name="xiaopang", password="123456", age=17, email="333@qq.com", money=31.50)
data_list = [user1,user2,user3]
data1 = UserSchema(many=True).dumps(data_list)
print(type(data1),data1) # <class 'str'> [{"name": "xiaoming", "age": 15, "money": 31.5, "email": "333@qq.com"}, {"name": "xiaohong", "age": 16, "money": 31.5, "email": "333@qq.com"}, {"name": "xiaopang", "age": 17, "money": 31.5, "email": "333@qq.com"}]
return "ok"
构造器嵌套使用
application.apps.marsh.views
,代码:
from marshmallow import Schema,fields
from application.apps.users.models import User,UserProfile
class UserProfileSchema(Schema):
education = fields.Integer()
middle_school = fields.String()
class UserSchema(Schema):
name = fields.String()
age = fields.Integer()
email = fields.Email()
money = fields.Number()
info = fields.Nested(UserProfileSchema,only=["middle_school"])
class Meta:
fields = ["name","age","money","email","info"]
ordered = True # 转换成有序字典
def index():
"""序列化"""
"""序列化嵌套使用"""
user1 = User(name="xiaoming", password="123456", age=15, email="333@qq.com", money=31.50)
user1.info = UserProfile(
education=3,
middle_school="北京师范学院附属中学白沙路分校"
)
data = UserSchema().dump(user1) # OrderedDict([('name', 'xiaoming'), ('age', 15), ('money', 31.5), ('email', '333@qq.com'), ('info', {'middle_school': '北京师范学院附属中学白沙路分校'})])
data = UserSchema().dumps(user1) # {"name": "xiaoming", "age": 15, "money": 31.5, "email": "333@qq.com", "info": {"middle_school": "\u5317\u4eac\u5e08\u8303\u5b66\u9662\u9644\u5c5e\u4e2d\u5b66\u767d\u6c99\u8def\u5206\u6821"}}
print(data)
return "ok"
2.基于Schema完成数据反序列化转换
代码:
from marshmallow import Schema, fields, validate, ValidationError,post_load
class UserSchema2(Schema):
name = fields.String()
sex = fields.String()
age = fields.Integer(missing=18)
email = fields.Email()
mobile = fields.String()
@post_load
def post_load(self, data, **kwargs):
return User(**data)
def index():
user_data = {"mobile":"1331345635","name": "xiaoming", "email": "xiaoming@qq.com","sex":"abc"}
us2 = UserSchema2()
result = us2.load(user_data)
print(result) # ==> <User xiaoming>
return "ok"
①反序列化时转换/忽略部分数据
from marshmallow import Schema, fields, validate, ValidationError,post_load
class UserSchema2(Schema):
name = fields.String()
sex = fields.String()
age = fields.Integer(missing=18)
email = fields.Email()
mobile = fields.String(required=True)
@post_load
def post_load(self, data, **kwargs):
return User(**data)
def index():
user_data = {"name": "xiaoming","sex":"abc"}
us2 = UserSchema2()
result = us2.load(user_data,partial=True)
print(result) # ==> <User xiaoming>
return "ok"
②设置字段只在序列化或反序列化阶段才启用
from marshmallow import Schema, fields, validate, ValidationError,post_load
class UserSchema2(Schema):
name = fields.String()
sex = fields.Integer(validate=validate.OneOf([0,1,2]))
age = fields.Integer(missing=18)
email = fields.Email()
mobile = fields.String()
password = fields.String(load_only=True) # 设置当前字段为只写字段,只会在反序列化阶段启用
created_time = fields.DateTime(dump_only=True) # 相当于只读字段 "read_only"
@post_load
def post_load(self, data, **kwargs):
return User(**data)
def index():
user_data = {"name": "xiaoming","password":"123456","sex":1}
us2 = UserSchema2()
# 反序列化
result = us2.load(user_data)
print(result) # ==> <User xiaoming>
# 序列化
us3 = UserSchema2(only=["sex","name","age"]) # 限制处理的字段
result2 = us3.dump(result)
print(result2) # {'name': 'xiaoming', 'sex': 1, 'age': 18}
return "ok"
③反序列化阶段的钩子方法
post_dump
([fn,pass_many,pass_original]) 注册要在序列化对象后调用的方法,它会在对象序列化后被调用。post_load
([fn,pass_many,pass_original]) 注册反序列化对象后要调用的方法,它会在验证数据之后被调用。pre_dump
([fn,pass_many]) 注册要在序列化对象之前调用的方法,它会在序列化对象之前被调用。pre_load
([fn,pass_many]) 在反序列化对象之前,注册要调用的方法,它会在验证数据之前调用。
from marshmallow import Schema, fields, validate, ValidationError,post_load,post_dump
class UserSchema2(Schema):
name = fields.String()
sex = fields.Integer(validate=validate.OneOf([0,1,2]))
age = fields.Integer(missing=18)
email = fields.Email()
mobile = fields.String()
password = fields.String(load_only=True) # 设置当前字段为只写字段,只会在反序列化阶段启用
@post_load
def post_load(self, data, **kwargs):
return User(**data)
@post_dump
def post_dump(self,data, **kwargs):
data["mobile"] = data["mobile"][:3] +"*****"+ data["mobile"][-3:]
return data
def index():
user_data = {"name": "xiaoming","password":"123456","sex":1,"mobile":"13512456230"}
us2 = UserSchema2()
# 反序列化
result = us2.load(user_data)
print(result) # ==> <User xiaoming>
# 序列化
us3 = UserSchema2(only=["sex","name","age","mobile"]) # 限制处理的字段
result2 = us3.dump(result)
print(result2) # {'mobile': '135*****230', 'sex': 1, 'name': 'xiaoming', 'age': 18}
return "ok"
3.反序列化阶段对数据进行验证
①基于内置验证器进行数据验证
内置验证器 | 描述 |
---|---|
validate.Email (*, error) |
邮箱验证 |
validate.Equal (comparable, *, error) |
判断值是否相等 |
validate.Length (min, max, *, equal, error) |
值长度/大小验证 |
validate.OneOf (choices, labels, *, error) |
选项验证 |
validate.Range ([min, max]) |
范围验证 |
validate.Regexp (regex, bytes, Pattern][, flags]) |
正则验证 |
validate.URL (*, relative, schemes, Set[str]]] = None, …) |
验证是否为URL |
代码:
from marshmallow import Schema, fields, validate, ValidationError,post_load
class UserSchema3(Schema):
name = fields.String(required=True)
sex = fields.String(required=True,error_messages={"required":"对不起,permission必须填写"})
age = fields.Integer(missing=18,validate=validate.Range(min=18,max=40,error="年龄必须在18-40之间!")) # 限制数值范围
email = fields.Email(error_messages={"invalid":"对不起,必须填写邮箱格式!"})
mobile = fields.String(required=True, validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号码格式不正确"),error_messages={"Regexp":"手机格式不正确"})
@post_load
def make_user_obj(self, data, **kwargs):
return User(**data)
def index3():
user_data = {"mobile":"1331345635","name": "xiaoming","age":40, "email": "xiaoming@qq.com","sex":"abc"}
us2 = UserSchema3()
result = us2.load(user_data)
result2 = us2.dumps(result)
print(result)
print(result2)
return "ok"
②自定义验证方法
from marshmallow import Schema, fields, validate,validates, ValidationError,post_load,validates_schema
class UserSchema4(Schema):
name = fields.String(required=True)
sex = fields.String(required=True,error_messages={"required":"对不起,permission必须填写"})
age = fields.Integer(missing=18,validate=validate.Range(min=18,max=40,error="年龄必须在18-40之间!")) # 限制数值范围
email = fields.Email(error_messages={"invalid":"对不起,必须填写邮箱格式!"})
mobile = fields.String(required=True, validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号码格式不正确"),error_messages={"Regexp":"手机格式不正确"})
password = fields.String(required=True, load_only=True)
password2 = fields.String(required=True, allow_none=True)
@post_load
def make_user_obj(self, data, **kwargs):
return User(**data)
@validates("name")
def validate_name(self,data,**kwargs):
print("name=%s" % data)
if data == "root":
raise ValidationError("对不起,root用户是超级用户!您没有权限注册!")
# 必须有返回值
return data
@validates_schema
def validate(self,data,**kwargs):
print(data)
if data["password"] != data["password2"]:
raise ValidationError("密码和确认密码必须一样!")
data.pop("password2")
return data
def index():
user_data = {"password":"12345","password2":"123456","mobile":"13313345635","name": "root1","age":40, "email": "xiaoming@qq.com","sex":"abc"}
us2 = UserSchema4()
result = us2.load(user_data)
print(result)
return "ok"
二、模型构造器(ModelSchema)
官方文档:https://github.com/marshmallow-code/marshmallow-sqlalchemy
https://marshmallow-sqlalchemy.readthedocs.io/en/latest/
注意:flask_marshmallow在0.12.0版本以后已经移除了ModelSchema和TableSchema这两个模型构造器类,官方转而推荐了使用SQLAlchemyAutoSchema和SQLAlchemySchema这2个类,前后两者用法类似。
创建
class UserSchema(SQLAlchemyAutoSchema):
class Meta:
model = 模型类名 # table = models.Album.__table__
include_relationships = True # 输出模型对象时同时对外键,是否也一并进行处理
include_fk = True # 序序列阶段是否也一并返回主键
load_instance = True # 反序列化阶段时,直接返回模型对象
sql_session = db.session # 数据库连接会话对象
# fields= ["id","name"] # 启动的字段列表
exclude = ["id","name"] # 排除字段列表
application.apps.marsh.views
,代码:
"""模型构造器"""
from marshmallow import fields
from application.apps.users.models import User
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field
class UserSchema5(SQLAlchemySchema):
# auto_field的作用,设置当前数据字段的类型和选项声明自动从模型中对应的字段中提取
# 此处,数据库中根本没有username,需要在第一个参数位置,声明当前数据字典的类型和选项声明从模型的哪个字段提取的
username = auto_field('name', dump_only=True)
# 可以在原字段基础上面,增加或者覆盖模型中原来的声明
created_time = auto_field(format='%Y-%m-%d')
# 甚至可以声明一些不是模型的字段
token = fields.String()
class Meta:
model = User
fields = ['username', 'created_time', 'token']
def index():
"""单个模型数据的序列化处理"""
from datetime import datetime
user1 = User(
name='xiaoming',
password='123456',
age=16,
email='333@qq.com',
money=31.50,
created_time=datetime.now(),
)
user1.token = 'abc'
# 把模型对象转换成字典格式
data1 = UserSchema5().dump(user1)
print(type(data1), data1) # <class 'dict'> {'username': 'xiaoming', 'token': 'abc', 'created_time': '2020-12-02'}
return 'ok'
"""SQLAlchemySchema使用起来,虽然比上面的Schema简单许多,但是还是需要给小转换的字段全部统一写上才转换这些字段,如果不想编写字段信息,直接从模型中复制,也可以使用SQLAlchemyAutoSchema。"""
class UserSchema6(SQLAlchemySchema):
token = fields.String()
class Meta:
model = User
include_fk = False # 启用外键关系
include_relationships = False # 模型关系外部属性
fields = ['name', 'created_time', 'info', 'token'] # 如果要全换全部字段,就不要声明fields或exclude字段即可
sql_session = db.session
def index():
"""单个模型数据的序列化处理"""
from datetime import datetime
user1 = User(
name='xiaoming',
password='123456',
age=16,
email='333@qq.com',
money=31.50,
created_time=datetime.now(),
info=UserProfile(position='助教')
)
# 把模型对象转换成字典格式
user1.token = 'abccc'
data1 = UserSchema6().dump(user1)
print(type(data1), data1) # <class 'dict'> {'created_time': '2020-12-02T17:45:40.443225', 'token': 'abccc', 'info': <UserProfile: None>, 'name': 'xiaoming'}
return 'ok'
本文地址:https://blog.csdn.net/qq_45957580/article/details/110433469