2

I need to send arbitrary (e.g. xls) files from React-based frontend to Django REST Framework backend.

Googled and tried many code variants for couple of hours, none of them worked completely.

Here are essential parts of code:

  1. React

1.1 Form input field

<input
    type="file"
    multiple={true}
    accept=".xls,.xlsx,.csv,.txt"
    onChange={this.handleFilesChosen}
/>

1.2 handleFilesChosen

    handleFilesChosen = event => {
        this.setState({
            files: event.target.files
        });
    }

1.3 Upload click handler (authHeader is function substituting Authorization Bearer token)

    handleUploadClick = event => {
        let formData = new FormData();
        for (let file of this.state.files) {
            formData.append('files', file);
        }
        const csrf = this.getCookie('csrftoken');
        fetch(`${API_BASE_PATH}/load-input-data/`, {
            method: 'POST',
            headers: authHeader({contentType: 'multipart/form-data', csrf: csrf}),
            body: formData,
        })
        .then(result => result.json())
        .catch(error => error);
    }
  1. DRF View
class LoadInputDataView(APIView):
    parser_class = (MultiPartParser,)

    @method_decorator(login_required)
    def post(self, request, format=None):
        print(request.data)
        return Response(status=status.HTTP_201_CREATED)

I selected simple txt file (to make debugging easy, binary will go later) with hello world content, uploaded it and get <QueryDict: {}> in Django runserver console.

If I look at Chrome network tab, I see following empty request payload instead of real file content:

------WebKitFormBoundaryYw6ABRFkvxatzHqi
Content-Disposition: form-data; name="files"; filename="foo.txt"
Content-Type: text/plain


------WebKitFormBoundaryYw6ABRFkvxatzHqi--

Tried to remove contentType header - got 400 error with message JSON parse error (browser substitutes JSON contentType header automatically).

I'm stuck. Could anybody guide me?

6
  • Look at axios package. This may be also helpful youtube.com/watch?v=Uyei2iDA4Hs Commented Jul 19, 2019 at 22:28
  • 1
    did you try print(request.body) ? Commented Jul 20, 2019 at 0:02
  • @BobWhite, looks better, now I got b'------WebKitFormBoundary9XymQXg2cF659SKh\r\nContent-Disposition: form-data; name="files"; filename="foo.txt"\r\nContent-Type: text/plain\r\n\r\nhello world\n\r\n------WebKitFormBoundary9XymQXg2cF659SKh--\r\n'. How to correctly strip boundaries and extract content now? I understand that I could use regexps, but maybe there are some built in method? Commented Jul 20, 2019 at 1:47
  • i got it. your are using formdata you should get it by using request.POST.get('files') also try request.FILES Commented Jul 20, 2019 at 3:58
  • request.POST.get('files') - None, request.FILES - <MultiValueDict: {}> Commented Jul 20, 2019 at 4:00

2 Answers 2

1

Found solution. I should not set Content-Type header manually, it is set automatically with boundary option. Now Django's request.FILES work too and I could work with uploaded files from backend using code like:

class ParseInputDataView(APIView):
    parser_class = (MultiPartParser,)
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, controller_id, format=None):
        for file_entry in request.FILES.getlist('files'):
            uploaded_file_name = file_entry.name
            uploaded_file_content = file_entry.read()
            ...
Sign up to request clarification or add additional context in comments.

Comments

0

I decided to maintain uniformity in the API and send the image within JSON.

In React:

const [image, setImage] = useState(null);

  const handleImageChange = (e) => {
    e.preventDefault();
    const reader = new FileReader();
    reader.onload = () => {
      var blocks = reader.result.split(";");
      const realData = blocks[1].split(",")[1];
      setImage(realData);
    };
    reader.onerror = (error) => console.error(error);
    reader.readAsDataURL(e.target.files[0]);
  };

 const onSaveHandler = () => {
    fetch(`/url`, {
        method: "post",
        credentials: "include", // send cookie with auth
        headers: {
            "Content-Type": "application/json",
            "X-CSRFToken": document.getElementById("csrf-token").value,
        }
        body: JSON.stringify({imageData: image}),
     });
 }

return(
 <div>
      <input
        onChange={handleImageChange}
        id="logo"
        type="file"
        multiple="false"
        accept="image/*"
      />
      <button onClick={onSaveHandler}>
        SAVE
      </button>
 </div>);

In Django (DRF):

class CustomerViewSet(viewsets.ModelViewSet):
      # override create method
      def create(self, request, *args, **kwargs):
        image_path = "whatever.jpg"
        print('save image on disk: ' + image_path)
        with open(image_path, "wb") as fh:
            fh.write(base64.b64decode(request.data.get("imageData")))
        return super().create(request)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.