djangodjango-rest-frameworkmultipartform-datadjango-serializerdjango-validation

Django run_validation removing MultiValueDict from QueryDict


I have a nested serializer field on a serializer which accepts Multipart/form-data (json + images). The run_validation happens to remove the nested serializer field data from the QueryDict. Here is the code:

class CreateSerializer(ModelSerializer[Dress]):
    dress_sizes = SizeSerializer(many=True, required=False)

    def run_validation(self, data: Any = ...) -> Any | None:
        return super(CreateSerializer, self).run_validation(data)

    def validate(self, data):
        return super().validate(data)

The view uses a custom parser:

class MultipartJsonParser(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 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)
        qdict.update(data)
        return DataAndFiles(qdict, result.files)

Here is the data that I get in the run_validation method:

< QueryDict: {
  'file_name': ['swimsuit.svg'],
  'dress_sizes': [
    [{
      'size': 'xs'
    }, {
      'size': 's'
    }, {
      'size': 'm'
    }, {
      'size': 'l'
    }, {
      'size': 'xl'
    }]
  ],
  'image': [ < TemporaryUploadedFile: swim.svg(image / svg + xml) > ]
} >

After run_validation the validate gets following data:

OrderedDict([('file_name', 'swimsuit.svg'), ('image', <
  TemporaryUploadedFile: swim.svg(image / svg + xml) > )])

Here the whole dress_sizes has disappeared. Which is needed to create the list of dress_sizes objects. I have reduced the code to make it readable. Maybe there are some mismatches in the spellings which is not the issue. Question is how to make the run_validation not remove the list of dress_sizes?

Finally in the create method there is no data for the dress_sizes = []. Here is create:

   def create(self, validated_data: Dict[str, Any]) -> Dress:
        dress_sizes = validated_data.pop("dress_sizes", [])

Solution

  • The reason why the run_validation method is removing the nested serializer field data from the QueryDict is that the run_validation method of the ModelSerializer class is designed to validate the fields defined on the serializer class, but not the nested serializers.

    One way to solve this issue is to override the to_internal_value method of the serializer to manually instantiate and validate the nested serializer fields. Here is an example:

    class CreateSerializer(serializers.ModelSerializer):
    dress_sizes = SizeSerializer(many=True, required=False)
    
    def to_internal_value(self, data):
        dress_sizes_data = data.pop('dress_sizes', [])
        validated_data = super().to_internal_value(data)
        
        # Manually validate and save the nested serializer fields
        dress_sizes = []
        for size_data in dress_sizes_data:
            size_serializer = SizeSerializer(data=size_data)
            size_serializer.is_valid(raise_exception=True)
            dress_sizes.append(size_serializer.save())
        validated_data['dress_sizes'] = dress_sizes
        
        return validated_data
    
    class Meta:
        model = Dress
        fields = '__all__'
    

    With this implementation, the to_internal_value method is called after the request data is parsed and before validation is performed. It manually extracts the data for the nested serializer fields, validates them, and saves them to the validated_data dictionary. Then it returns the validated data with the nested serializer fields properly validated and saved.

    By doing this, the run_validation method of the ModelSerializer class will no longer remove the dress_sizes data from the validated_data dictionary.