简体   繁体   中英

Django rest framework valid related field not found by serializer but present in request

I have 2 related models and I am trying to perform an ajax 'multipart/form' post request. But it seems like data regarding the related model is not identified by the serializer for some reason. I have tried editing the 'create' method of the viewset, to understand why the data is not passed, but to no avail. I think the issue is related to json serialization, but i do not know how to fix it

Here are some things i tried:

models.py

class Dream(models.Model):
    # Some fields
    ...

class Milestone(models.Model):
    title = models.CharField(max_length=100)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)
    description = models.TextField(max_length=500)
    dream = models.ForeignKey(
        Dream, on_delete=models.CASCADE, related_name='milestones')

serializers.py

class DreamSerializer(TaggitSerializer, serializers.ModelSerializer):
    milestones = MilestoneSerializer(many=True, read_only=False)
    class Meta:
        model = Dream
        fields = (..., 'milestones')


class MilestoneSerializer(serializers.ModelSerializer):
    class Meta:
        model = Milestone
        fields = ('id', 'title', 'user', 'date', 'description', 'dream')
        read_only_fields = ('user', 'dream', 'date')

views.py

class DreamViewSet(viewsets.ModelViewSet):
    queryset = Dream.objects.prefetch_related('user').all()
    serializer_class = DreamSerializer
    permission_classes = [permissions.IsAuthenticated]

    # Tried to manually override the create function to fix the error
    def create(self, request, *args, **kwargs):
        print(request.data) # <QueryDict: {'title': ['test'], 'image':[<InMemoryUploadedFile: somePhoto.jpg (image/jpeg)>], ... , 'milestones': ['[{"title":"test1","description":"test1"}]']}>
        
        # seems like it is evaluating to string literal '[{"title":"test1","description":"test1"}]'
        print(request.data['milestones']) # [{"title":"test1","description":"test1"}]
        print(type(request.data['milestones'])) # <class 'str'>
        cleaned_data = request.data.copy()
        milestones = cleaned_data.pop('milestones', [])
        print(milestones) # ['[{"title":"test1","description":"test1"}]'] (type list)
        if len(milestones):
            # tried to manually deserialize data 
            cleaned_data['milestones'] = json.loads(milestones[0])

        milestone_serializer = MilestoneSerializer(
            data=cleaned_data['milestones'], many=True)
        print(cleaned_data) # <QueryDict: {'title': ['test'], 'image': [<InMemoryUploadedFile: somePhoto.jpg (image/jpeg)>], 'milestones': [[{'title': 'test1', 'description': 'test1'}]]}>
        print(milestone_serializer.is_valid()) # True
        print(milestone_serializer.data) # [OrderedDict([('title', 'test1'), ('description', 'test1')])]
        serializer = self.get_serializer(data=cleaned_data)

        if serializer.is_valid(): # False
            # not executedd
            print(serializer.validated_data)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=201, headers=headers)
        else:
            print(serializer.errors) # {'milestones': [ErrorDetail(string='This field is required.', code='required')]}

on the frontend:

async add({title, image, ..., milestones}) {
    const imageFile = new File([image], `somePhoto.jpg`, {
      type: image.type,
    });
    
    const formData = new FormData();
    formData.append("title", title);
    formData.append("image", imageFile);
    ....
    formData.append("milestones", JSON.stringify(milestones));

    await axios.post(endpoint, formData); // status: 400, statusText: 'Bad Request',  milestones: ['This field is required.']
}

It doesn't work because in the DreamSerializer , you set the milestones field as the list of milestone objects. But in frontend you set it as string. You need to change that first. And I think create logic should be written in the create method of the serializer. First you define other field for writing data of the milestones.

import json

class DreamSerializer(TaggitSerializer, serializers.ModelSerializer):
    milestones = MilestoneSerializer(many=True, read_only=True)
    milestone_str = serializers.CharField(write_only = True)

    class Meta:
        model = Dream
        fields = (..., 'milestones', 'milestone_str')

    def create(self, validated_data):
        milestone_str = validated_data.pop('milestone_str')
        milestone_data = json.loads(milestone_str)

        milestone_serializer = MilestoneSerializer(
            data=milestone_data, many=True)

        if milestone_serializer.is_valid():

            # create Dream object first
            dream = Dream.objects.create(**validated_data)

            for milestone_item in milestone_data:
                Milestone.objects.create(dream = dream, **milestone_item)
            return dream
        else:
            raise serializers.ValidationError("invalid milestone data")

Then in views.py, you don't need to define create method.

class DreamViewSet(viewsets.ModelViewSet):
    queryset = Dream.objects.prefetch_related('user').all()
    serializer_class = DreamSerializer
    permission_classes = [permissions.IsAuthenticated]

In frontend,

async add({title, image, ..., milestones}) {
    ...

    formData.append("milestone_str", JSON.stringify(milestones));

    ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM