Django dynamic file output on form submit doesn't work on mobile

Viewed 443

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.

2 Answers

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

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

Related