2

I'm trying to output a file by writing to io.BytesIO and send it when form is submitted as file attachment. Everything works fine in Firefox on Linux, but mobile browsers send a GET request when download is accepted in browser and save the HTTP response as the downloaded file.

Here is my view function:

from django.views.decorators.csrf import csrf_exempt
from django.http import FileResponse, HttpResponse
import io


@csrf_exempt
def download_file(request):
    if request.method == 'POST':
        buffer = io.BytesIO()
        buffer.write('Text file content'.encode('UTF-8'))
        buffer.seek(0)
        return FileResponse(buffer, as_attachment=True, filename='file.txt')
    return HttpResponse('<form method="post"><button>Download</button></form>')

This is what the logs look like when form is submitted from Firefox on Linux:

[20/Sep/2020 18:15:31] "POST /test/ HTTP/1.1" 200 17

Downloaded file on Linux:

Text file content

This is what the logs look like when form is submitted from Firefox on Android:

[20/Sep/2020 18:16:47] "POST /test/ HTTP/1.1" 200 17
[20/Sep/2020 18:16:48] "GET /test/ HTTP/1.1" 200 52

Downloaded file on Android:

<form method="post"><button>Download</button></form>

I am using Python 3.8.5 and Django 3.1.1.

mjuk
  • 547
  • 1
  • 5
  • 8
  • 1
    INFO: it seems this is something with the firefox android, In chrome android the download seems fine – JPG Sep 23 '20 at 02:09

2 Answers2

2

Issue

In the Firefox android app, the app is sending an HTTP GET request after confirming the Download action. You can see the server logs of the same (in OP too,)

OP behaviour

What is the solution?

It is not recommended to use an HTTP POST method to download a file, use HTTP GET instead ( Ref: Which HTTP method to use for file downloading? ). So, change your view accordingly to download the file.

def sample_view(request):
    buffer = io.BytesIO()
    buffer.write('Text file content'.encode('UTF-8'))
    buffer.seek(0)
    return FileResponse(buffer, as_attachment=True, filename='file.txt')

HTTP get download

JPG
  • 82,442
  • 19
  • 127
  • 206
  • Thanks, looks like Firefox android app is the problem here. One problem is that the actual view function does some database changes before returning the file, so I thought the POST method was appropriate. I have solved this by first doing database changes and then redirecting to download file view. – mjuk Sep 23 '20 at 09:17
0

Solution

Consider using HttpResponse or StreamingHttpResponse with proper content type and content disposition.

Example

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
import io

@csrf_exempt
def download_file(request):
    if request.method == 'POST':
        buffer = io.BytesIO()
        buffer.write('Text file content'.encode('UTF-8'))
        buffer.seek(0)
        response = HttpResponse(buffer, content_type='text/plain')
        response['Content-Disposition'] = 'attachment; filename=file.txt'

        return response

    return HttpResponse('<form method="post"><button>Download</button></form>')

References

StreamingHttpResponse: https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.StreamingHttpResponse

pygeek
  • 7,356
  • 1
  • 20
  • 41
  • [From the doc](https://docs.djangoproject.com/en/3.1/ref/request-response/#fileresponse-objects), *"FileResponse is a subclass of StreamingHttpResponse optimized for binary files. It uses wsgi.file_wrapper if provided by the wsgi server, otherwise it streams the file out in small chunks."* – JPG Sep 23 '20 at 01:51
  • I didn't try your answer, but, what advantage will someone get if they use your solution over `FileResponse`, which is already done in OP? – JPG Sep 23 '20 at 01:55
  • UPDATE: When I tried with Firefox Android to download the file, the content was not the *"Text file content"* – JPG Sep 23 '20 at 02:17