Search code examples
djangofile-uploadfetch-api

Send File via Fetch to Django REST Endpoint?


I'm trying to upload a file via Fetch, to a Django REST endpoint.

Component with Fetch Code:

function myComponent(props) {
  const classes = useStyles();
  const [{token}] = useContext();

  function handleImageUpload(files) {
    let formData = new FormData()
    formData.append('file', files[0])

    fetch(receiveSpreadsheetEndpoint, {
      method: 'POST',
      body: formData,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`,
      },
    })
      .then(response => response.json())
      .then(data => {
        console.log(data)
      })
      .catch(error => {
        console.error(error)
      })
  }

  function Dropzone(props) {
    const [files, setFiles] = useState([]);

    function handleChange(event) {
      setFiles(event[0]);
    }

    return (
      <>
        <DropzoneArea
          onChange={event => handleChange(event)}
        />

        <Button
          variant="contained"
          color="primary"
          onClick={event => handleImageUpload([files])}
        >
          Primary
        </Button>
      </>
    )
  }

  return (
    <Container className={classes.root} height="100%">
      <Dropzone
        acceptedFiles={['.csv', 'text/*', 'text/csv']}
        showPreviews={true}
        showFileNamesInPreview={true}
      />

    </Container>
  );
}

Django REST endpoint:

class ReceiveFileData(APIView):
    permission_classes = (IsAuthenticated,)
    parser_classes = (JSONParser, FormParser, MultiPartParser)

    def post(self, request):
        my_file = request.stream.read 

        data = {} <== a breakpoint is set here
        return Response(data, status=status.HTTP_200_OK)

After my_file = request.stream.read, my_file contains b''.

What am I leaving out?

UPDATE

Here's the request object when it arrives at the REST endpoint on the server:

enter image description here


Solution

  • This is now working. Here is working code in case it may be helpful to others.

    REACT

    import React, {useEffect, useRef, useState, Component} from 'react';
    import 'typeface-roboto';
    import {makeStyles} from '@material-ui/styles';
    import {useContext} from "../../context";
    import Container from "@material-ui/core/Container";
    import {DropzoneArea} from 'material-ui-dropzone'
    import Button from "@material-ui/core/Button";
    
    //ENDPOINTS
    const receiveSpreadsheetEndpoint = '/spreadsheet_generator/api/receive-spreadsheet/';
    
    function UploadSpreadsheet(props) {
      const classes = useStyles();
      const [{token}] = useContext();
    
      function handleImageUpload(event, theFile) {
        event.stopPropagation();
        event.preventDefault();
    
        let formData = new FormData()
        formData.append('file', theFile)
        fetch(receiveSpreadsheetEndpoint, {
          method: 'POST',
          body: formData,
          headers: {
            Authorization: `Bearer ${token}`,
          },
        })
          .then(response => response.json())
          .then(data => {
            console.log(data)
          })
          .catch(error => {
            console.error(error)
          })
      }
    
      function Dropzone(props) {
        const [file, setFile] = useState(null);
    
        function handleChange(event) {
          setFile(event[0]);
        }
    
        return (
          <>
            <DropzoneArea
              onChange={event => handleChange(event)}
              filesLimit={1}
            />
    
            <Button
              variant="contained"
              color="primary"
              onClick={event => handleImageUpload(event, file)}
            >
              Primary
            </Button>
    
          </>
        )
      }
    
      return (
        <Container className={classes.root} height="100%">
          <Dropzone
            acceptedFiles={['.csv', 'text/*', 'text/csv']}
            showPreviews={true}
            showFileNamesInPreview={true}
          />
        </Container>
      );
    }
    
    //https://material-ui.com/components/buttons/
    const useStyles = makeStyles(theme => ({
        root: {
          '& > *': {
            margin: theme.spacing(1),
          },
        },
      }))
    ;
    
    export default UploadSpreadsheet;
    

    DJANGO REST ENDPOINT

    from rest_framework import status
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    from rest_framework.views import APIView
    from rest_framework.parsers import FormParser, MultiPartParser, JSONParser, FileUploadParser
    import logging
    
    from server_modules.spreadsheet.models import SourceSpreadsheet
    
    logger = logging.getLogger(__name__)
    
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(name)s %(levelname)s %(message)s',
    )
    
    def create_spreadsheet_record(file):
        # insert record to SourceSpreadsheet
        SourceSpreadsheet.objects.create(file=file)
    
    class ReceiveData(APIView):
        permission_classes = (IsAuthenticated,)
        parser_classes = (FileUploadParser,)
    
        def post(self, request):
            success = True
            message = 'OK'
            file = request.stream.FILES['file']
    
            # save to db
            try:
                create__spreadsheet_record(file)
            except Exception as e:
                print("Error calling ReceiveData: " + str(e))
                message = str(e)
                success = False
    
            data = {'message': message }
            if (success):
                return Response(data, status=status.HTTP_200_OK)
            else:
                return Response(data, status=status.HTTP_400_BAD_REQUEST)
    

    DJANGO MODEL

    class SourceSpreadsheet(models.Model):
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
        file = models.FileField(upload_to='source_spreadsheets/%Y/%m')
    

    BASE.PY Add:

    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')