Getting non_field_errors using JSONField ()

I am trying to make a PATCH request using the Django Rest Framework, but getting the following error:

{"image_data": [{"non_field_errors": ["Invalid data"]}] 

I understand that JSONField () can give some problems, so I took care of this by adding to_native and from_native . But I am still facing this problem. I donโ€™t think the JSONField() problem is here at all, but still worth mentioning.

I believe that I am doing something fundamentally wrong in the way I try to update a related area.

Code below ...

Model:

 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() 

serializers:

 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 ) 

I also tried:

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

But again with the same error:

[{"non_field_errors": ["Invalid data"]}]

+5
source share
1 answer

The initial idea for serializing Django Rest Framework relationships is to not change the values โ€‹โ€‹of the related fields. This means that your payload should contain the pk object of the PhotoData object, not the dataset for it. As with models, you cannot assign a dict to a foreign key field.

Good (only works with serializers.PrimaryKeyRelatedField, which contains the problems themselves):

  payload = {"image_data": 2} 

Bad (does not work in DRF by default):

  payload = {"image_data": {'thisdata': '{}'}} 

In fact, the data model you provided does not need PhotoData at PhotoData (you can move the thisdata field to Photo ), but suppose you have a special case, even when Zen of Python says Special cases aren't special enough to break the rules. .

So, here are a few possible ways:

Using field serializers (your original way)

What you want to do now is a possible but very ugly decision. You can create a PhotoDataField (works for me, but not ready to use code, just for demonstration)

 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 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 (eg via web GUI) return super(PhotoDataField, self).field_from_native(data, files, field_name, into) 

and use it in PhotoSerializer

 class PhotoSerializer(serializers.ModelSerializer): image_data = PhotoDataField(read_only=False, source='image_data') class Meta: model = Photo fields = ("id", "user", "image_data") 

so the request will work well

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

and a "good" request also

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

and as a result you will see in the GET request "image_data": <photodata thisdata value>,

But even if you fix the validation problems with this approach, it will still be a huge pain in the ass, as you can see from my code (this is just what DRF can offer you when you want to โ€œdisrupt the normal workflowโ€, Tastypie offers more )

Normalize code and use @action (recommended)

 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") 

and now define a specific method in your api view that you can use to set the data for any photo

 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) 

Now you can make almost the same request as now, but you have much more extensibility and separation of duties in the code. See URL , it is added when you have a method processed by @action.

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

Hope this helps.

+5
source

Source: https://habr.com/ru/post/1200843/


All Articles