Flask-Restplus如何将2表中的数据组合成单个数据响应?

时间:2019-01-30 13:03:58

标签: python sqlalchemy flask-restplus

我正在使用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')
    })

UserDevice的模型:

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结果,我希望idname来自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次(一次,tokennull,一次是token,并带有我想要的字符串)。

如何获得上面想要的响应?

1 个答案:

答案 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()