使用JSONField()获取非字段错误
我正在尝试使用 Django Rest Framework 发起一个 PATCH 请求,但遇到了以下错误:
{"image_data": [{"non_field_errors": ["Invalid data"]}]
我知道 JSONField() 可能会出现一些问题,所以我通过添加 to_native
和 from_native
来处理这个问题。但是,我仍然遇到这个问题。我觉得 JSONField()
根本不是问题,但提到一下也没坏处。
我认为我在更新相关字段的方式上做错了什么根本性的事情。
下面是代码...
模型:
class Photo(models.Model):
user = models.ForeignKey(AppUser, help_text="Item belongs to.")
image_data = models.ForeignKey("PhotoData", null=True, blank=True)
class PhotoData(models.Model):
thisdata = JSONField()
序列化器:
class ExternalJSONField(serializers.WritableField):
def to_native(self, obj):
return json.dumps(obj)
def from_native(self, value):
try:
val = json.loads(value)
except TypeError:
raise serializers.ValidationError(
"Could not load json <{}>".format(value)
)
return val
class PhotoDataSerializer(serializers.ModelSerializer):
thisdata = ExternalJSONField()
class Meta:
model = PhotoData
fields = ("id", "thisdata")
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataSerializer()
class Meta:
model = Photo
fields = ("id","user", "image_data")
PATCH 请求:
> payload = {"image_data": {"thisdata": "{}"}}
> requests.patch("/photo/123/",payload )
我也尝试过:
> payload = {"image_data": [{"thisdata": "{}"}]}
> requests.patch("/photo/123/",payload )
但还是出现了同样的错误:
[{"non_field_errors": ["无效数据"]}]
1 个回答
Django Rest Framework(DRF)中的关系序列化的最初想法是保持相关字段的值不变。这意味着你的数据包应该包含一个PhotoData
对象的pk
(主键),而不是它的数据集。就像在模型中,你不能把一个字典直接赋值给外键字段。
好的(仅适用于有问题的serializers.PrimaryKeyRelatedField):
payload = {"image_data": 2}
坏的(在DRF中默认不工作):
payload = {"image_data": {'thisdata': '{}'}}
实际上,你提供的数据模型根本不需要PhotoData
(你可以把thisdata
字段移动到Photo
中),但我们假设你有一个特殊情况,即使Python的“禅”也说过特殊情况不足以打破规则。
。
所以,这里有一些可能的方法:
使用字段序列化器(你原来的方法)
你现在想做的事情是可能的,但解决方案非常丑陋。你可以创建一个PhotoDataField
(对我有效,但不是可以直接使用的代码,仅用于演示)
class PhotoDataField(serializers.PrimaryKeyRelatedField):
def field_to_native(self, *args):
"""
Use field_to_native from RelatedField for correct `to_native` result
"""
return super(serializers.RelatedField, self).field_to_native(*args)
# Prepare value to output
def to_native(self, obj):
if isinstance(obj, PhotoData):
return obj.thisdata
return super(PhotoDataField, self).to_native(obj)
# Handle input value
def field_from_native(self, data, files, field_name, into):
try:
int(data['image_data'])
except ValueError:
# Looks like we have a data for `thisdata` field here.
# So let's do write this to PhotoData model right now.
# Why? Because you can't do anything with `image_data` in further.
if not self.root.object.image_data:
# Create a new `PhotoData` instance and use it.
self.root.object.image_data = PhotoData.objects.create()
self.root.object.image_data.thisdata = data['image_data']
self.root.object.image_data.save()
return data['image_data']
except KeyError:
pass
# So native behaviour works (e.g. via web GUI)
return super(PhotoDataField, self).field_from_native(data, files, field_name, into)
并在PhotoSerializer
中使用它
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataField(read_only=False, source='image_data')
class Meta:
model = Photo
fields = ("id", "user", "image_data")
这样请求就能正常工作
payload = {"image_data": '{}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
而且“好的”请求也可以
photodata = PhotoData.objects.get(pk=1)
payload = {"image_data": photodata.pk}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
最终你会在GET请求中看到"image_data": <photodata的thisdata值>,
。
但是,即使你用这种方法解决了验证问题,仍然会非常麻烦,正如我代码中所示(这是DRF在你想“打破正常工作流程”时能提供的唯一解决方案,Tastypie
提供了更多选择)。
规范化你的代码并使用@action(推荐)
class PhotoDataSerializer(serializers.ModelSerializer):
class Meta:
model = PhotoData
fields = ("id", "thisdata")
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataSerializer() # or serializers.RelatedField
class Meta:
model = Photo
fields = ("id", "user", "image_data", "test")
现在在你的API视图中定义一个特定的方法,这样你就可以用它来设置任何照片的数据
from rest_framework import viewsets, routers, generics
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
# ViewSets define the view behavior.
class PhotoViewSet(viewsets.ModelViewSet):
model = Photo
serializer_class = PhotoSerializer
@action(methods=['PATCH'])
def set_photodata(self, request, pk=None):
photo = self.get_object()
serializer = PhotoDataSerializer(data=request.DATA)
if serializer.is_valid():
if not photo.image_data:
photo.image_data = PhotoData.objects.create()
photo.save()
photo.image_data.thisdata = serializer.data
photo.image_data.save()
return Response({'status': 'ok'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
现在你可以做几乎和现在一样的请求,但你在代码中有了更多的扩展性和责任划分。查看URL,当你有@action修饰的方法时,它会被附加上去。
payload = {"thisdata": '{"test": "ok"}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/set_photodata/"), payload)
希望这对你有帮助。