我正在使用Flask-Restplus和SQLAlchemy开发我的API。我想返回一个响应,其中包含来自两个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端点返回以下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
,并带有我想要的字符串)。
如何获得上面想要的响应?
答案 0 :(得分:1)
您将获得两个输出条目,因为查询中有两个对象(行)。您不需要将Device
对象添加到查询中,因为它已经可以作为user.device
使用。调整Restplus模型以使用user.device.token
属性。
因此,您需要更改查询以仅加载用户:
return User.query.filter_by(email == data['email']).all()
如果User.email
字段是唯一的(一个电子邮件地址实际上应该只引用您数据库中的一个用户),请考虑使用.first()
而不是.all()
,然后包装返回值列表中.first()
的值,或者更具逻辑性,将您的返回值更改为没有列表的单个JSON对象。如果没有用户使用此电子邮件,还可以考虑使用this forum question自动生成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'
)
})
请注意attribute
的{{1}}字段,我们在这里告诉Flask-Restplus接受每个对象的token
属性的token
属性。
这确实需要您更改SQLAlchemy模型,因为您现在还没有定义一对一的关系。您有一个first_or_404()
method,因此device
实际上是一个列表。将one to many添加到您的user.device
关系中:
device
这将您限制为 scalar 值,因此每个用户只有一个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
,以更好地反映您有多个。
请注意,在所有情况下,您都需要考虑uselist=False
。如果您始终要访问与用户关联的设备,则将devices
添加到lazy="joined"
关系中将有助于提高性能。或者,在device
或.options(db.joinedload('device'))
调用之前,将User
添加到您的.first()
查询中:
.all()