使用JSONField获取non_field_errors()

Pro*_*eus 5 python django django-rest-framework

我正在尝试使用Django Rest Framework 进行PATCH请求,但是收到以下错误:

{"image_data": [{"non_field_errors": ["Invalid data"]}]
Run Code Online (Sandbox Code Playgroud)

我知道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()
Run Code Online (Sandbox Code Playgroud)

串行器:

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")
Run Code Online (Sandbox Code Playgroud)

补丁:

> payload = {"image_data": {"thisdata": "{}"}}
> requests.patch("/photo/123/",payload )
Run Code Online (Sandbox Code Playgroud)

我也尝试过:

> payload = {"image_data": [{"thisdata": "{}"}]}
> requests.patch("/photo/123/",payload )
Run Code Online (Sandbox Code Playgroud)

但再次给出同样的错误:

[{"non_field_errors":["无效数据"]}]

Nik*_*luk 5

Django Rest Framework关系序列化的最初想法是不改变相关领域的价值.这意味着,你的有效载荷应包含pk的PhotoData对象,而不是它的数据集.就像在模型中一样,你不能将dict分配给外键字段.

好(仅适用于自身包含问题的serializers.PrimaryKeyRelatedField):

   payload = {"image_data": 2}
Run Code Online (Sandbox Code Playgroud)

错误(默认情况下不适用于DRF):

   payload = {"image_data": {'thisdata': '{}'}}
Run Code Online (Sandbox Code Playgroud)

实际上你提供的数据模型根本不需要PhotoData(你可以将thisdata字段移动到Photo),但是假设你有一个特殊情况,即使是Python的Zen说Special cases aren't special enough to break the rules..

所以,这里有一些可能的方法:

使用字段序列化程序(您的原始方式)

你现在想做什么是可能的,但是非常难看的解决方案.您可以创建一个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)
Run Code Online (Sandbox Code Playgroud)

并使用它 PhotoSerializer

class PhotoSerializer(serializers.ModelSerializer):

    image_data = PhotoDataField(read_only=False, source='image_data')

    class Meta:
        model = Photo
        fields = ("id", "user", "image_data")
Run Code Online (Sandbox Code Playgroud)

所以请求会很好

payload = {"image_data": '{}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
Run Code Online (Sandbox Code Playgroud)

和"好"的要求也

photodata = PhotoData.objects.get(pk=1)
payload = {"image_data": photodata.pk}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
Run Code Online (Sandbox Code Playgroud)

结果你会在GET请求中看到"image_data": <photodata's thisdata value>,.

但是,即使您将通过这种方法修复验证问题,从我的代码中可以看出它仍然是一个巨大的痛苦(当您想要"打破正常的工作流程"时,这只是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")
Run Code Online (Sandbox Code Playgroud)

现在在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)
Run Code Online (Sandbox Code Playgroud)

现在,您可以执行与现在几乎相同的请求,但您在代码中具有更多可扩展性和职责划分. 查看URL,当你有@ action的包装方法时,它会被附加.

payload = {"thisdata": '{"test": "ok"}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/set_photodata/"), payload)
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 谢谢!这是一个很好的答案.我确实简化了将此数据作为OP的相关模型的原因.自从您发布此内容并了解更多您的推理后,我做了一些研究.这是一个巨大的帮助.再次感谢 (2认同)