django - Django Rest Framework (DRF) 在 to_internal_value 函数中将 JSON 字段设置为 Empty
问题描述
所以我正在尝试按照这个解决方案上传文件并将 JSON 数据发布到我的 API。我已经创建了解析器,将它放在我的视图集中。我什至在to_internal_value
函数中接收到图像和 JSON 数据,我认为它在解析器解析数据之后运行。
但是,一旦to_internal_value
运行该函数,该location
字段就会设置为空。我重写了该函数以查看发生了什么,并且该field.get_value
函数似乎返回了空值。
这是解析器:
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# for case1 with nested serializers
# parse each field with json
for key, value in result.data.items():
if type(value) != str:
data[key] = value
continue
if '{' in value or "[" in value:
try:
data[key] = json.loads(value)
except ValueError:
data[key] = value
else:
data[key] = value
qdict = QueryDict('', mutable=True)
print(data)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
这是序列化器:
class AssetSerializer(serializers.ModelSerializer):
"""
Serializes data recieved/retrieved to/from endpoints concerning Assets.
"""
asset_location = LocationSerializer(required=False,allow_null=True)
asset_devices = serializers.PrimaryKeyRelatedField(many=True,queryset=Device.objects.all(),required=False)
parent = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(),required=False)
busy_ranges = serializers.CharField(required=False)
time = serializers.ReadOnlyField(
source='datetime')
status = serializers.CharField(source="current_status",required=False)
class Meta:
model = Asset
fields = ['id','asset_devices','name','slug','buying_date','asset_location','weight','icon','enabled','parent','is_parent','time','status','busy_ranges']
read_only_fields = ('id','slug','is_parent','busy_ranges')
extra_kwargs = {
'status': {
'help_text': 'Status of the asset. Available options are "AV" for available, "LE" for Lent, "MT" for In Maintenance'
}
}
#exclude_when_nested = {'device'} # not an official DRF meta attribute ...
def create(self,validated_data):
location_data = validated_data.pop('asset_location')
location = Location.objects.create(**location_data)
validated_data['asset_location']=location
owner = self.context['request'].user
asset = Asset.objects.create(**validated_data,owner=owner)
return asset
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, Mapping):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
#The line below print(data) prints the following
#<QueryDict: {'name': ['test asset'], 'weight': ['20'], 'asset_location': [{'x': '10', 'y': '30'}], 'icon': [<InMemoryUploadedFile: issues_mzali.png (image/png)>]}>
print(data)
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
print(primitive_value) #this line prints 'asset_location' field as <class 'rest_framework.fields.empty'> which gives KeyError in the create method
#All other fields are printed fine.
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
def update(self,instance,validated_data):
try:
location_data = validated_data['asset_location']
if instance.asset_location is None:
location = Location.objects.create(**location_data)
instance.asset_location = location
instance.save()
else:
instance_location = instance.asset_location
for key,value in location_data.items():
setattr(instance_location,key,value)
instance_location.save()
instance.save()
return instance
except KeyError as e:
print(e.message)
return super(AssetSerializer,self).update(instance,validated_data)
请检查to_internal_value
功能中的注释,您将更好地了解问题。
解决方案
因此,在深入研究代码之后,field.get_value()
嵌套序列化程序实际上从 rest_framework.html 调用 html.parse_html_dict(),它只验证带有点符号的字段。
简而言之,我需要以这种方式发送字段:
asset_location.x
asset_location.y
所以我只是发送了 JSON,并更改了我的解析器以将 JSON 字段更改为上述格式,这就是我的解析器现在的样子:
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
#For nested serializers, drf accepts values in dotted notaion. E.g if location is nested serializer.
# It will accept location.x and location.y if data is to be entered in form fields.
# the 2 nested for loops, ensures that the JSON data sent in form field is converted to the above format.
#e.g if the key is asset_location. and it has x and y keys inside. It will be converted to asset_location.x, and asset_location.y
for key, value in result.data.items():
if type(value) != str:
data[key] = value
continue
if '{' in value or "[" in value:
try:
data[key] = json.loads(value)
if type(data[key]) == dict:
for inner_key,inner_value in data[key].items():
data[f'{key}.{inner_key}']=inner_value
except ValueError:
data[key] = value
else:
data[key] = value
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
推荐阅读
- xcode - Xcode 自定义框架以不同方式存储小部件和主应用程序的数据
- python - 在计数函数之前转换数据类型会产生与内联转换不同的结果
- c# - 尝试覆盖 .txt 文件 c# 时出现奇怪的 System.IndexOutOfRangeException
- api - 如何使用 api 提取 Vcenter 整体统计信息
- neo4j - 使用多个 OPTIONAL MATCH 提高 neo4j 查询的性能
- java - Java 中的 getUrlEncoder/getUrlDecoder 问题填充
- wordpress - 联系表格 7 需要 2 个不同字段之一
- javascript - 如何允许用户在 Django 中使用 paypal 相互支付
- r - 数值求根:R 中的二分法
- javascript - javacscipt 如何输入 1 个字母和 7 个数字