Flask-Restplus 如何将来自 2 table 的数据组合成单个数据响应?
Flask-Restplus how to combine data from 2 table into single data response?
我正在使用 Flask-Restplus 和 SQLAlchemy 来开发我的 API。我想要 return 一个响应,其中包含来自两个 SQAlchemy 对象(一个用户和一个设备)的信息,它们之间具有 1:1 关系。
我有一个如下所示的查询:
details = db.session.query(User, Device).filter(User.id == Device.id) \
.filter(User.email== data['email'])\
.all()
目前,上面查询的结果可以在控制台打印如下:
[(<User 'None'>, <Device 1>)]
我希望我的 API 端点到 return 以下 JSON:
{
"data": [
[
{
"id": 20,
"name": null,
"token": "Some String here"
}
]
]
}
这是我的 DTO:
class UserDto:
# this is for input
user = api.model('user', {
'email': fields.String(required=False, description='phone number'),
'name': fields.String(required=False, description='username'),
'device_id': fields.String(required=False,description='user_device_id'),
})
# this is for output
details = api.model('details', {
'id': fields.Integer(required=False, description='the id'),
'name': fields.String(required=False, description='name'),
'token': fields.String(required=False, description='token')
})
User
和 Device
的模型:
class User(db.Model):
__tablename__ = "users_info"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Integer, unique=True, nullable=True)
email = db.Column(db.String)
device = db.relationship('Device', backref='user')
# .. more fields ..
class Device(db.Model):
__tablename__ = "user_device"
user_device_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
id = db.Column(db.Integer, db.ForeignKey(User.id))
token = db.Column(db.String, nullable=True)
# .. more fields ..
我为了实现上面的 JSON 结果,我想要 id
和 name
来自 User
对象,而 token
来自Device
对象。
控制器:
api = UserDto.api
_user = UserDto.user
_details = UserDto.details
@api.route('/')
class User(Resource):
@api.response(201, 'successful')
@api.expect(_user, validate=True)
@api.marshal_list_with(_details, envelope='data')
def post(self):
data = request.json
return user(data=data)
实际响应:
{
"data": [
[
{
"id": 20,
"name": null,
"token": null
},
{
"id": 20,
"name": null,
"token": "some string here"
}
]
]
}
正如您在此处看到的,同一条记录出现了 2 次(一次 token
为 null
,一次 token
为我想要的字符串)。
怎样才能达到我想要的上面的响应?
您得到两个输出条目,因为您的查询中有两个对象(行)。您不需要将 Device
对象添加到您的查询中,因为它已经作为 user.device
可用。调整您的 Restplus 模型以使用 user.device.token
属性。
因此您需要更改查询以仅加载用户:
return User.query.filter_by(email == data['email']).all()
如果 User.email
字段应该是唯一的(电子邮件地址实际上应该只引用数据库中的一个用户),请考虑使用 .first()
而不是 .all()
,然后换行列表中 .first()
的 return 值,或更合乎逻辑,将 return 值更改为单个 JSON 对象,而无需列表。还可以考虑使用 first_or_404()
method 自动生成 404 Not Found
响应,以防没有用户使用此电子邮件。
然后您需要配置您的 details
API 型号:
details = api.model('details', {
'id': fields.Integer(required=False, description='the id'),
'name': fields.String(required=False, description='name'),
'token': fields.String(
attribute='device.token',
required=False,
description='token'
)
})
注意token
的attribute
字段,我们在这里告诉Flask-Restplus取每个对象的device
属性的token
属性。
这确实需要您更改 SQLAlchemy 模型,因为您现在没有定义一对一关系。您与 device
的关系是 one to many, so user.device
is actually a list. Add uselist=False
:
class User(db.Model):
__tablename__ = "users_info"
# ...
device = db.relationship('Device', backref='user', uselist=False)
这将您限制为 标量 值,因此每个用户一个 device
对象。
或者,如果您的意思是它是一对多关系,那么您必须将 token
字段替换为 tokens
列表:
details = api.model('details', {
'id': fields.Integer(required=False, description='the id'),
'name': fields.String(required=False, description='name'),
'token': fields.List(
fields.String(attribute='token', required=False),
description='array of tokens for all user devices',
attribute='device'
)
})
在这种情况下,您可能希望将 device
重命名为 devices
,以更好地反映您有多个。
请注意,在所有情况下,您都需要考虑 loading strategies。如果您总是要访问与用户关联的设备,将 lazy="joined"
添加到 device
关系将有助于提高性能。或者,在 .first()
或 .all()
调用之前将 .options(db.joinedload('device'))
添加到您的 User
查询:
return User.query.filter_by(email == data['email']).options(db.joinedload('device')).all()
我正在使用 Flask-Restplus 和 SQLAlchemy 来开发我的 API。我想要 return 一个响应,其中包含来自两个 SQAlchemy 对象(一个用户和一个设备)的信息,它们之间具有 1:1 关系。
我有一个如下所示的查询:
details = db.session.query(User, Device).filter(User.id == Device.id) \
.filter(User.email== data['email'])\
.all()
目前,上面查询的结果可以在控制台打印如下:
[(<User 'None'>, <Device 1>)]
我希望我的 API 端点到 return 以下 JSON:
{
"data": [
[
{
"id": 20,
"name": null,
"token": "Some String here"
}
]
]
}
这是我的 DTO:
class UserDto:
# this is for input
user = api.model('user', {
'email': fields.String(required=False, description='phone number'),
'name': fields.String(required=False, description='username'),
'device_id': fields.String(required=False,description='user_device_id'),
})
# this is for output
details = api.model('details', {
'id': fields.Integer(required=False, description='the id'),
'name': fields.String(required=False, description='name'),
'token': fields.String(required=False, description='token')
})
User
和 Device
的模型:
class User(db.Model):
__tablename__ = "users_info"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Integer, unique=True, nullable=True)
email = db.Column(db.String)
device = db.relationship('Device', backref='user')
# .. more fields ..
class Device(db.Model):
__tablename__ = "user_device"
user_device_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
id = db.Column(db.Integer, db.ForeignKey(User.id))
token = db.Column(db.String, nullable=True)
# .. more fields ..
我为了实现上面的 JSON 结果,我想要 id
和 name
来自 User
对象,而 token
来自Device
对象。
控制器:
api = UserDto.api
_user = UserDto.user
_details = UserDto.details
@api.route('/')
class User(Resource):
@api.response(201, 'successful')
@api.expect(_user, validate=True)
@api.marshal_list_with(_details, envelope='data')
def post(self):
data = request.json
return user(data=data)
实际响应:
{
"data": [
[
{
"id": 20,
"name": null,
"token": null
},
{
"id": 20,
"name": null,
"token": "some string here"
}
]
]
}
正如您在此处看到的,同一条记录出现了 2 次(一次 token
为 null
,一次 token
为我想要的字符串)。
怎样才能达到我想要的上面的响应?
您得到两个输出条目,因为您的查询中有两个对象(行)。您不需要将 Device
对象添加到您的查询中,因为它已经作为 user.device
可用。调整您的 Restplus 模型以使用 user.device.token
属性。
因此您需要更改查询以仅加载用户:
return User.query.filter_by(email == data['email']).all()
如果 User.email
字段应该是唯一的(电子邮件地址实际上应该只引用数据库中的一个用户),请考虑使用 .first()
而不是 .all()
,然后换行列表中 .first()
的 return 值,或更合乎逻辑,将 return 值更改为单个 JSON 对象,而无需列表。还可以考虑使用 first_or_404()
method 自动生成 404 Not Found
响应,以防没有用户使用此电子邮件。
然后您需要配置您的 details
API 型号:
details = api.model('details', {
'id': fields.Integer(required=False, description='the id'),
'name': fields.String(required=False, description='name'),
'token': fields.String(
attribute='device.token',
required=False,
description='token'
)
})
注意token
的attribute
字段,我们在这里告诉Flask-Restplus取每个对象的device
属性的token
属性。
这确实需要您更改 SQLAlchemy 模型,因为您现在没有定义一对一关系。您与 device
的关系是 one to many, so user.device
is actually a list. Add uselist=False
:
class User(db.Model):
__tablename__ = "users_info"
# ...
device = db.relationship('Device', backref='user', uselist=False)
这将您限制为 标量 值,因此每个用户一个 device
对象。
或者,如果您的意思是它是一对多关系,那么您必须将 token
字段替换为 tokens
列表:
details = api.model('details', {
'id': fields.Integer(required=False, description='the id'),
'name': fields.String(required=False, description='name'),
'token': fields.List(
fields.String(attribute='token', required=False),
description='array of tokens for all user devices',
attribute='device'
)
})
在这种情况下,您可能希望将 device
重命名为 devices
,以更好地反映您有多个。
请注意,在所有情况下,您都需要考虑 loading strategies。如果您总是要访问与用户关联的设备,将 lazy="joined"
添加到 device
关系将有助于提高性能。或者,在 .first()
或 .all()
调用之前将 .options(db.joinedload('device'))
添加到您的 User
查询:
return User.query.filter_by(email == data['email']).options(db.joinedload('device')).all()