pythondjangopostrequestapiclient

Django multipart/form-data pass Dict & File


I want to create a post requests that sends a file along with information in the form of a dictionary. I have the following implementation:

# conftest.py
import pytest


@pytest.fixture
def api_client():
    from rest_framework.test import APIClient

    return APIClient()

Testing with pytest:

# test_dataset.py
@pytest.mark.django_db()
class TestDatasetEndpoint:
    
    def test_dataset_create(self, api_client):

        data_raw = baker.make(Dataset)
        serialized_dataset = DatasetSerializer(data_raw).data
        print(serialized_dataset)
        file_path = "./Top 250s in IMDB.csv"

        with open(file_path, "rb") as fp:

            encoded_data = encode_multipart(
                BOUNDARY, {"data": serialized_dataset, "file": fp}
            )

            response_post = api_client.post(
                reverse("datasets-list"),
                encoded_data,
                content_type="multipart/form-data; boundary=BOUNDARY",
            )

        assert response_post.status_code == 201

Server side:

# views.py
class DatasetViewSet(viewsets.ModelViewSet):
    queryset = Dataset.objects.all()
    serializer_class = DatasetSerializer

    def create(self, request, *args, **kwargs) -> Response:

        data = request.data

        return Response(request.data["data"], status=status.HTTP_201_CREATED)

Lastly, the Dataset model is:

class Dataset(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=100)
    path = models.CharField(max_length=100, editable=False, null=True, blank=True)
    description = models.CharField(max_length=500, null=True, blank=True)
    useCase = models.CharField(max_length=100, editable=False, null=True, blank=True)
    useCaseSpecificVariable = models.CharField(
        max_length=50, editable=False, null=True, blank=True
    )
    origin = models.CharField(max_length=50, editable=False, default="")
    creationDate = models.DateTimeField(editable=False, null=True, blank=True)
    workflowsUsedOn = models.JSONField(blank=True, null=True)
    owners = models.JSONField(blank=True, null=True)
    sampleSize = models.IntegerField(editable=False, default=0)
    featureSize = models.IntegerField(editable=False, default=0)
    metadataPath = models.CharField(
        max_length=100, editable=False, null=True, blank=True
    )
    timeFrom = models.DateTimeField(editable=False, null=True, blank=True)
    timeUntil = models.DateTimeField(editable=False, null=True, blank=True)

The response i am getting is a combination of the dict and the file data. How can i handle the data and the file in the server side (views.py)?


Solution

  • Django can handle such requests via form. So the pattern is the following:

    forms.py

    from django import forms
    
    class DatasetForm(forms.Form):
        class Meta:
            model = Dataset
    

    views.py

    class DatasetViewSet(viewsets.ModelViewSet):
        queryset = Dataset.objects.all()
        serializer_class = DatasetSerializer
    
        def create(self, request, *args, **kwargs) -> Response:
    
            dataset_form = DatasetForm(request.POST, request.FILES)
            if dataset_form.is_valid():
                dataset = dataset_form.save()
    
                return Response(self.serializer_class(dataset).data, status=status.HTTP_201_CREATED)
            else:
                return Response(dataset_form.error_messages, status=status.HTTP_400_BAD_REQUEST)
    
        
    

    Note that you will need to have a field that can store file on your model. E.g: file = models.FileField(upload_to='attachments', blank=False)

    Then content of fields in dictionary (and content of your file) will be automatically mapped via form to the relevant fields.

    See additional info: https://docs.djangoproject.com/en/4.0/topics/forms/