Marshmallow 反序列化多对多PUT操作的ID列表

Marshmallow 反序列化多对多PUT操作的ID列表,marshmallow,flask-marshmallow,marshmallow-sqlalchemy,Marshmallow,Flask Marshmallow,Marshmallow Sqlalchemy,我有一个具有多对多关系的用户和角色模型 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,

我有一个具有多对多关系的用户和角色模型

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请求中包含角色对象列表时,这非常有效。同样有效的方法是向用户发布嵌入在请求中的新角色对象。我没有弄清楚的是如何发布/放置现有角色ID的列表,而不是创建新对象。 例如,此请求适用于:

{
    "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."
    ]
  }
}
我可以看出模式中缺少了一些东西,无法接收ID而不是对象,我怀疑我需要使用dump_only/load_only,并可能为PUT使用一个单独的模式。但是,我还没有在网上找到这个用例的例子

提到我正在使用
rest
进行请求验证和模式参数摄取也可能会有所帮助

@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

感谢您的帮助。

我最终解决了这个问题,创建了一个单独的PUT模式,并对角色ID使用了Pull

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"
}

这正是我的目标。

既然您已将角色声明为
字段。嵌套('RoleSchema',many=True,exclude=['users'])
Marshmallow
希望在您的POST请求中使用该结构。您必须分离逻辑。首先-获取用户,第二-创建新角色,第三-向用户添加角色。我可以建议这样的更改:GET-
/users/
,POST-
/roles
,POST-(向用户添加角色)
/users//roles/
。有了这样的逻辑,您就不用混合任何内容,而不用有效负载作为
id
容器,只需在url中将其指定为资源标识符即可。谢谢您的建议。这与我的想法类似,如果现有库不支持我正在尝试做的事情,那么我需要一个解决方案。我可以从/user//roles获取,修改数组,然后将其放回仅使用角色ID的端点。是的,您可以这样做,但我建议使用“无负载”解决方案:
/users//roles/
。另外,请注意,使用
/roles
您可以创建角色,而无需将其分配给用户,因此您可以将您的职责分配给各个控制器。另一方面,在
/users//roles
上发布将创建带有分配的新角色。
{
  "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"
}