我想根据嵌套 json 数组中收到的数据创建多个对象。为此,我创建了一个
serializers.Serializer
并重写 to_representation
方法以使用 ListSerializer
来序列化新创建的对象。
简化的代码如下所示(为了便于通过安装 DRF 运行 python 来轻松重现)
import json
from rest_framework import serializers
class Link:
def __init__(self, title: str, description: str, url: str):
self.title = title
self.description = description
self.url = url
class LinkResponseSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
description = serializers.CharField(required=False)
url = serializers.CharField()
class URLSerializer(serializers.Serializer):
url = serializers.CharField()
class LinkSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
description = serializers.CharField()
urls = URLSerializer(many=True)
def create(self, validated_data):
links = []
for obj in validated_data.pop("urls", []):
links.append(Link(url=obj["url"], **validated_data))
return links
def to_representation(self, instance):
return LinkResponseSerializer(instance, many=True).data
data = {
"title": "Example",
"description": "Exampe Description",
"urls": [
{"url": "https://google.com"},
{"url": "https://yahoo.com"},
]
}
serializer = LinkSerializer(data=data)
serializer.is_valid(raise_exception=True)
serializer.save()
print(json.dumps(serializer.data, indent=4))
运行此代码将导致错误:
Traceback (most recent call last):
File "/tmp/.../test.py", line 54, in <module>
print(json.dumps(serializer.data, indent=4))
^^^^^^^^^^^^^^^
File "/tmp/.../venv/lib64/python3.12/site-packages/rest_framework/serializers.py", line 556, in data
return ReturnDict(ret, serializer=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/.../venv/lib64/python3.12/site-packages/rest_framework/utils/serializer_helpers.py", line 19, in __init__
super().__init__(*args, **kwargs)
ValueError: too many values to unpack (expected 2)
我知道这个问题来自于
.data
的 serializers.Serializer
属性,它尝试使用 ReturnDict
作为返回值,并使用列表作为参数进行初始化。
我可以通过覆盖
.data
属性来解决这个问题,如下所示:
from rest_framework.utils.serializer_helpers import ReturnList
class LinkSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
description = serializers.CharField()
urls = URLSerializer(many=True)
def create(self, validated_data):
links = []
for obj in validated_data.pop("urls", []):
links.append(Link(url=obj["url"], **validated_data))
return links
def to_representation(self, instance):
return LinkResponseSerializer(instance, many=True).data
@property
def data(self):
self._data = self.to_representation(self.instance)
return ReturnList(self._data, serializer=self)
这将产生所需的输出:
[
{
"title": "Example",
"description": "Exampe Description",
"url": "https://google.com"
},
{
"title": "Example",
"description": "Exampe Description",
"url": "https://yahoo.com"
}
]
但是如果我们使用通用视图,DRF Browserable API 将不再起作用,因为它期望 ReturnDict 作为
.data
的 serializers.Serializer
属性的返回类型!
处理问题的正确方法是什么?用例对我来说并不奇怪,感觉必须有一种更简单的方法。
覆盖数据属性不是一个好习惯。
LinkSerializer 的
to_representation
应返回链接数据的字典,而不是代码中的列表,这就是可浏览 API 不起作用的原因。留下
如果要创建自定义列表响应,则需要修改
LinkResponseSerializer
to_representation 处理列表`
喜欢
class LinkResponseSerializer(serializers.ListSerializer):
title = serializers.CharField(max_length=100)
description = serializers.CharField(required=False)
url = serializers.CharField()
def to_representation(self, instance):
links = instance.all() # This should be a list and you can return it.
return links
class LinkSerializer(serializers.ListSerializer):
title = serializers.CharField(max_length=100)
description = serializers.CharField()
urls = URLSerializer(many=True)
def create(self, validated_data):
links = []
for obj in validated_data.pop("urls", []):
links.append(Link(url=obj["url"], **validated_data))
return links
def to_representation(self, instance):
return LinkResponseSerializer(instance, many=True)
class Meta:
list_serializer_class = LinkResponseSerializer
这未经测试,因此您应该预料到代码中会出现一些错误。
有关此内容的更多详细信息可以在这里找到 -> https://www.django-rest-framework.org/api-guide/serializers/#customizing-listserializer-behavior