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

时间:2018-10-11 10:55:58

标签: django django-rest-framework django-views django-serializer

我遇到了一个非常简单的问题,但是找到了一些解决方案,并且不停地想知道预期的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的创建函数并在那里解决问题

处理此类案件最合适的地方是什么?

3 个答案:

答案 0 :(得分:2)

我更喜欢使用以下方法,其中我有 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

答案 1 :(得分:1)

因此,您希望反序列化的CartProductSerializer包括Product的嵌套表示,而另一方面,在序列化时,您只希望提供现有Product的ID?没错,创建附加字段是一种解决方案,我最喜欢它。

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

答案 2 :(得分:0)

您可以通过覆盖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

注意::如果默认值为id,则无需显式提供AutoField字段,只需将其添加到fields元选项中即可足够