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

Deserialize POST request with subset of serializer fields in DRF

我遇到了一个相当简单的问题,但找到了一些解决方案并且忍不住想知道预期的 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。

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

"product": 1,
"cart": 1

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

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

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

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

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

您可以通过重写 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 ] 元选项应该足够了

我更喜欢使用以下方法,其中我有 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。
  • 您不必在视图中编写任何自定义逻辑来解析等 - 您可以坚持使用开箱即用的默认通用视图功能

因此,在您的特定情况下,JSON 您返回的 GET 将是:

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