How can I deserialize multiple files and form data in a single request using Django Rest Framework?

Viewed 392

I would like to desearialize a form with some basic form data + one or more uploaded files.

If I send the following request (generated as Python Requests code using Postman)

import requests

url = "http://127.0.0.1:8000/forms/test/"

payload = {
'customer_id': '123456789',
'customer_name': 'Billy Bob'}
files = [
  ('files', open('/C:/tmp/aaaaa.csv','rb')),
  ('files', open('/C:/tmp/bbbbb.csv','rb'))
]
headers= {}

response = requests.request("POST", url, headers=headers, data = payload, files = files)

print(response.text.encode('utf8'))

The code returns the following response (I hoped it would save the file instead).

{
    "files": [
        "This field is required."
    ]
}

Models

from datetime import datetime
from django.db import models

class Mtfar(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    customer_id = models.IntegerField()
    customer_name = models.CharField(max_length=254)

class MtfarFile(models.Model):
    file = models.FileField(blank=False, null=False)
    mtfar = models.ForeignKey(Mtfar, related_name='files', on_delete=models.CASCADE)

Serializers

from rest_framework import serializers
from .models import Mtfar, MtfarFile

class MtfarFileSerializer(serializers.ModelSerializer):
    class Meta:
        model = MtfarFile
        fields = ['file']

class MtfarSerializer(serializers.ModelSerializer):

    files = MtfarFileSerializer(many=True)

    class Meta:
        model = Mtfar
        fields = ['customer_ptid', 'customer_name', 'files']

View

from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.decorators import api_view, parser_classes
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from .serializers import MtfarSerializer, MtfarFileSerializer

@api_view(['POST'])
@parser_classes([MultiPartParser])
def post_generic_form(request):

    data = request.data
    
    filelist = []
    for f in request.FILES.getlist('files'):
        filelist.append({
            'file': f
        })
    data.setlist('files', filelist)

    serializer = MtfarSerializer(data=data)

    if serializer.is_valid():
        serializer.save()
        return Response(status=status.HTTP_201_CREATED)
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
1 Answers

You need to convert the QueryDict object into a dict to support nested serialization, because DRF parsing the QueryDict in a different way. (Ref: get_value(self, dictionary) method -- (DRF source code))

@api_view(['POST'])
@parser_classes([MultiPartParser])
def post_generic_form(request):
    dict_data = {
        **request.data.dict(),
        **{"files": [{"file": file} for file in request.FILES.getlist('files')]}
    }
    serializer = MtfarSerializer(data=dict_data)

    if serializer.is_valid():
        serializer.save()
        return Response(status=status.HTTP_201_CREATED)
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Related