Flask-Restplus, как объединить данные из 2 таблиц в один ответ данных? - PullRequest
0 голосов
/ 30 января 2019

Я использую 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 снужная строка).

Как мне получить ответ, который я хочу выше?

1 Ответ

0 голосов
/ 04 февраля 2019

Вы получаете две выходные записи, потому что у вас есть два объекта (строки) в вашем запросе.Вам не нужно добавлять объект Device к вашему запросу, потому что он уже доступен как user.device.Настройте модель Restplus для использования атрибута user.device.token.

Поэтому вам нужно изменить запрос, чтобы загрузить только пользователя:

return User.query.filter_by(email == data['email']).all()

Если поле User.email должно быть уникальным(адрес электронной почты должен действительно ссылаться только на одного пользователя в вашей базе данных), рассмотрите возможность использования .first() вместо .all(), затем добавьте возвращаемое значение .first() в список или, что более логично, измените возвращаемое значение в одинJSON-объект без списка.Также рассмотрите возможность использования first_or_404() метода для автоматического получения ответа 404 Not Found в случае, если нет пользователя с этим электронным письмом.

Затем вам необходимо настроить модель API details:

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 для token, мы сообщаем Flask-Restplus здесь взять атрибут token атрибута device каждого объекта.

Это требует от вас изменения вашей модели SQLAlchemy, потому что у вас нет определенных отношений один на один, определенных прямо сейчас.У вас есть один ко многим , поэтому user.device на самом деле список.Добавьте uselist=False к вашему device отношению:

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, чтобы лучше отразить, что у вас есть кратное число.

Обратите внимание, что во всех случаях вам нужно подуматьо стратегии загрузки .Если вы всегда собираетесь получить доступ к устройству, связанному с пользователем, добавление lazy="joined" к отношению device будет способствовать повышению производительности.В качестве альтернативы, добавьте .options(db.joinedload('device')) к вашему запросу User перед вызовом .first() или .all():

return User.query.filter_by(email == data['email']).options(db.joinedload('device')).all()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...