使用 DRF 中的序列化器字段子集反序列化 POST 请求

Posted

技术标签:

【中文标题】使用 DRF 中的序列化器字段子集反序列化 POST 请求【英文标题】:Deserialize POST request with subset of serializer fields in DRF 【发布时间】:2019-03-16 10:24:20 【问题描述】:

我遇到了一个相当简单的问题,但找到了一些解决方案,我不禁想知道预期的 DRF 方法是什么。

我有一个(简化的)模型和序列化器,如下所示:

class CartProduct(models.Model):
    cart = models.ForeignKey('Cart', on_delete=models.CASCADE)
    product = models.ForeignKey('Product', on_delete=models.CASCADE)

class CartProductSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.ReadOnlyField()
    product = ProductSerializer()

    class Meta:
        model = CartProduct
        fields = ('id', 'url', 'product')

产生这样的 GET 响应:

"url": "http://localhost:8000/appUsers/1/cart/products/16/",
"id": 16,
"product": 
    "url": "http://localhost:8000/products/1/",
    "id": 1,
    "name": "Tomatoes",
,
"cart": "http://localhost:8000/carts/1/"

但是,当现在创建一个新的 CartProduct 时,在这种默认情况下,我需要像上面那样传递一个嵌套的产品字典来从 POST 请求创建/反序列化一个新的 CartProduct。

我想要的是发送一个带有正文的 POST 请求,仅使用主键或 url 来创建新的购物车产品,例如像这样:

"product": 1,
"cart": 1

"product": "http://localhost:8000/products/1/"
"cart": "http://localhost:8000/carts/1/"

所以现在我想知道实现这一目标的最佳方法是什么?我想到了:

编写两个单独的序列化程序(但我不喜欢为几乎每个模型都使用两个序列化程序的想法) 向每个序列化程序添加附加字段,确保嵌套/相关模型始终由 url 和/或 id 表示,并且只需要这些 ID 字段 重写验证/创建函数以使所需的输入成为有效格式 覆盖 ModelViewSet 的创建函数并在那里处理问题

处理这种情况最合适的地方是什么?

【问题讨论】:

【参考方案1】:

我更喜欢使用以下方法,其中我有 2 个序列化器字段用于 1 个模型字段(一个只读字段用于详细信息,一个 id/url 字段用于创建和更新):

class CartProductSerializer(serializers.HyperlinkedModelSerializer):
    product_detail = ProductSerializer(source='product', read_only=True) 

    class Meta:
        model = CartProduct
        fields = ('url', 'cart', 'product', 'product_detail')

请注意,这假定 ProductSerializer 已在别处定义。我省略了 id 因为我们并不真正需要它,但您仍然可以根据需要添加它。

这样有以下优点:

您可以对所有 CRUD 操作使用相同的序列化程序。 您可以在 GET 上获取嵌套字段的详细信息,但可以只在 POST/PUT 上为这些嵌套字段提供 ID。 您不必在视图中编写任何自定义逻辑来解析等 - 您可以坚持使用开箱即用的默认通用视图功能

因此,在您的具体情况下,您将通过 GET 返回的 JSON 将是:


  "url": "http://localhost:8000/appUsers/1/cart/products/16/",
  "product": "http://localhost:8000/products/1/"
  "product_detail": 
    "url": "http://localhost:8000/products/1/",
    "name": "Tomatoes",
  ,
  "cart": "http://localhost:8000/carts/1/"

对于 POST,您只需发送:


  "product": "http://localhost:8000/products/2/"
  "cart": "http://localhost:8000/carts/1/"

对于 PUT,在上述 JSON 中包含 CartProduct 对象自己的 url

【讨论】:

这很好,但如果输入不是 ID 而是相关实体的其他值怎么办。在这个例子中,说它的产品名称?所以 POST 正文看起来像: "product": "Tomatoes", "cart": 1 【参考方案2】:

所以您希望反序列化的CartProductSerializer 包含Product 的嵌套表示,而另一方面,在序列化时,您希望只提供现有Product 的ID?你说得对,创建一个额外的字段是一种解决方案,我最喜欢它。

    product 设置为只读,因为您实际上不接受序列化程序中的嵌套product 字典(you can, though)。 创建一个新字段product_id = ModelField(model_field=Product()._meta.get_field('id'))。这将允许您在序列化时传递product_id。如果想在反序列化的时候排除这个,可以设置为只写。见this answer。

【讨论】:

我喜欢,不知道 read-only=True 解决了这个问题。为什么要为第 2 步选择 ModelField 而不是 PrimaryKeyRelatedField? 这可能行得通。如果你使用 PrimaryKeyRelatedField 会发生什么?【参考方案3】:

您可以通过覆盖 to_representation 方法来更改序列化程序的行为

class CartProduct(models.Model):
    cart = models.ForeignKey('Cart', on_delete=models.CASCADE)
    product = models.ForeignKey('Product', on_delete=models.CASCADE)


class CartProductSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.ReadOnlyField()

    class Meta:
        model = CartProduct
        fields = ('id', 'url', 'product')

    def to_representation(self, instance):
        self.fields['product'] = ProductSerializer(read_only=True)
        return super().to_representation(instance)

这样,您的序列化程序将默认使用PrimaryKeyRelatedField,并且在显示表示时它将使用嵌套的ProductSerializer

注意:如果它是默认的AutoField,你也不需要显式提供id字段,只需将它添加到fields元选项中就足够了

【讨论】:

以上是关于使用 DRF 中的序列化器字段子集反序列化 POST 请求的主要内容,如果未能解决你的问题,请参考以下文章

drf序列化组件

drf序列化组件

drf的序列化和反序列化

drf中的Serialiazer序列化器

DRF ---- ModelSerializer

drf序列化与反序列化