使用 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 请求的主要内容,如果未能解决你的问题,请参考以下文章