Десериализовать список идентификаторов для операции PUT «многие ко многим» - PullRequest
0 голосов
/ 06 февраля 2020

У меня есть модель пользователя и роли с отношением «многие ко многим»

class User(BaseModel, TimestampableMixin):

    username = Column(String(MEDIUM_STRING_LENGTH), nullable=False, unique=True)

    roles = relationship('Role', secondary='user_roles', back_populates='users')

class Role(BaseModel, TimestampableMixin):

    label = Column(String(MEDIUM_STRING_LENGTH), nullable=False, unique=True)

    users = relationship('User', secondary='user_roles', back_populates='roles')

class UserRole(BaseModel, TimestampableMixin):

    user_id = Column(ForeignKey('users.id', ondelete=CASCADE, onupdate=CASCADE), nullable=False, index=True)
    role_id = Column(ForeignKey('roles.id', onupdate=CASCADE), nullable=False, index=True)

Затем я определил схемы для пользователя для вложения ролей.

class RoleSchema(BaseSchema):
    class Meta:
        model = models.Role

class UserSchema(BaseSchema):
    class Meta:
        model = models.User

    roles = fields.Nested('RoleSchema', many=True, exclude=['users'])

Для сериализации это отлично работает, когда список объектов роли включен в пользовательский запрос GET. Также работает POSTing пользователя с новыми объектами роли, встроенными в запрос. То, что я не смог выяснить, это как POST / PUT список существующих ролей ids , а не создавать новые объекты. Например, этот запрос работает:

{
    "username": "testuser12",
    "roles": [
        {
            "label": "newrole"
        }
    ]
}

Ответ:

{
  "createdTime": "2020-02-06T19:13:29Z",
  "id": 4,
  "modifiedTime": "2020-02-06T19:13:29Z",
  "roles": [
    {
      "createdTime": "2020-02-06T19:13:29Z",
      "id": 2,
      "label": "newrole",
      "modifiedTime": "2020-02-06T19:13:29Z"
    }
  ],
  "username": "testuser12"
}

Но ни один из этих запросов не работает:

{
    "username": "testuser13",
    "roles": [
        1
    ]
}

{
    "username": "testuser13",
    "roles": [
        {
            "id": 1
        }
    ]
}

Я получаю этот ответ:

{
  "errors": {
    "error": [
      "Unprocessable Entity"
    ],
    "message": [
      "The request was well-formed but was unable to be followed due to semantic errors."
    ]
  }
}

Я могу сказать, что в схеме что-то отсутствует, чтобы можно было получать идентификаторы, а не объекты, и я подозреваю, что мне нужно использовать dump_only / load_only и, возможно, отдельную схему для PUT. Но я нигде не смог найти в сети пример для этого варианта использования.

Также может быть полезно упомянуть, что я использую flask-smorest для проверки запроса и приема аргументов схемы.

@B_API.route('/user/<user_id>')
class UserByIdResource(MethodView):

    @B_API.response(schemas.UserSchema)
    def get(self, user_id):
        """
        Get a single user by id
        """
        return models.User.query.get(user_id)

    @B_API.arguments(schemas.UserSchema)
    @B_API.response(schemas.UserSchema)
    def put(self, updated_user, user_id):
        """
        Update fields of an existing user
        """
        models.User.query.get_or_404(user_id, description=f'User with id {user_id} not found')
        user = updated_user.update_with_db(user_id)
        return user

update_with_db выглядит так:

    def update_with_db(self, id: int):
        self.id = id
        DB.session.merge(self)
        DB.session.commit()
        return self.query.get(id)

Спасибо за любую помощь.

1 Ответ

0 голосов
/ 20 февраля 2020

В итоге я решил эту проблему, создав отдельную схему PUT и используя Pluck для идентификаторов ролей.

class UserSchema(BaseModelSchema):
    class Meta:
        model = models.User

    roles = fields.Nested('RoleSchema', many=True, exclude=['users'])


class UserPutSchema(BaseModelSchema):
    class Meta:
        model = models.User

    roles = fields.Pluck('RoleSchema', 'id', many=True)

Эти схемы реализованы с помощью следующего ресурса, принимая схему PUT для проверки входных данных и возвращая стандартная пользовательская схема для соответствия ответам GET и POST. Кажется, это работает хорошо, с единственным недостатком, состоящим в том, чтобы определить отдельную схему.

    @B_API.arguments(schemas.UserPutSchema(partial=True))
    @B_API.response(schemas.UserSchema)
    def put(self, updated_user, user_id):
        """
        Update fields of an existing user
        """
        get_by_id(models.User, user_id)
        user = updated_user.update_with_db(user_id)
        return user

Теперь я могу сделать запрос PUT типа

PUT /user/1
{
    "roles": [
        1,
        2
    ]
}

и получить ответ типа

{
  "createdTime": "2020-02-20T01:33:42Z",
  "historys": [],
  "id": 1,
  "modifiedTime": "2020-02-20T01:33:42Z",
  "roles": [
    {
      "createdTime": "2020-02-19T23:43:06Z",
      "description": null,
      "historys": [],
      "id": 1,
      "key": "TEST_ROLE_1",
      "label": "role 1",
      "modifiedTime": "2020-02-19T23:43:06Z"
    },
    {
      "createdTime": "2020-02-19T23:43:06Z",
      "description": null,
      "historys": [],
      "id": 2,
      "key": "TEST_ROLE_2",
      "label": "role 2",
      "modifiedTime": "2020-02-19T23:43:06Z"
    }
  ],
  "username": "testuser1"
}

к чему я стремился.

...