Forums

FileResponse generating UnhandledException in Django

I have an application using Django and, when the user clicks in a button in certain page, it calls a view method from the server side, which is responsible for generating a file in memory (.docx) and sending it to the user's browser. This file is not stored anywhere in the server. This works fine at localhost, when I run it in my own machine.

But, when I uploaded the code to PythonAnywhere and I click in the button, I am redirected to the page Something went wrong, presenting an Unhandled Exception.


Looking at the error.log file, I see this:

2020-08-26 00:32:58,175: SystemError: <built-in function uwsgi_sendfile> returned a result with an error set
2020-08-26 00:32:58,175:   File "/home/capitale/.virtualenvs/myenv/lib/python3.8/site-packages/django/core/handlers/wsgi.py", line 148, in __call__
2020-08-26 00:32:58,176:     response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size)

The code resposible for generating the file and returning it to browser is the one below (I've ommited some parts to focus in what I think are the most important ones):

from docxtpl import DocxTemplate
import io

dev generateFile(request):

    document = DocxTemplate(filePath)
    document.render({
        ...
    })

    doc_io = io.BytesIO()
    document.save(doc_io)        
    doc_io.seek(0)

    return FileResponse(doc_io, as_attachment=True, filename='file.docx')

Any idea on what is going on and how do I fix it?

It sounds like you're encountered a similar problem to one that we've seen with Flask. I believe the same fix will work -- add this to the top of your views.py:

from werkzeug import FileWrapper

...and then change your code so that you do this:

doc_io = io.BytesIO()
document.save(doc_io)        
doc_io.seek(0)

w = FileWrapper(doc_io)
return FileResponse(w, as_attachment=True, filename='file.docx')

Hi, giles!

Thanks for the answer. But, I couldn't make this command work:

from werkzeug import FileWrapper

I've already installed werkzeug using pip install werzeug and the installation worked fine. But, when I run the command above, it gives me this result:

> Traceback (most recent call last):   File "<stdin>", line 1, in
> <module> ImportError: cannot import name 'FileWrapper' from 'werkzeug'
> (/home/capitale/.virtualenvs/myenv/lib/python3.8/site-packages/werkzeug/__init__.py)

Any suggestions?

Looking at this other topic, I think the corret import was that:

from werkzeug.wsgi import FileWrapper

I've just made the other adjustments that you suggested, and now it is not resulting in any error. BUT, instead of automatically downloading the .docx file when I click in the button, it shows the content of the file in the browser, as a bunch of characters. I'll just copy the beginning of it:

PK�kQ=�{�[Content_Types].xml���n0��>E%CE��"02��}B��� o��4P`������J��x��d >(kl�����Ry�}M�{OY��kk������n8]; M( =3D���:0SZ9ҭ3��7�{�����`cG:�B�5&o+ZnDRgi

This was tried in my local machine. I was first testing in my development scenario and, if it was successfull I would put it in production in PythonAnywhere.

You may need to change the headers sent in the response. Something like this:

Content-Type: application/octet-stream
Content-Disposition: attachment; filename="myfile.doc"

I am lost in this. I have no experience with Flask... Can you show me exactly the code to make it work?

I tried something like this, but clearly it is not right:

from flask import Response, make_response
from werkzeug.wsgi import FileWrapper

def generateFile(request):
    ...

    doc_io = io.BytesIO()
    document.save(doc_io)        
    doc_io.seek(0)

    w = FileWrapper(doc_io)

    response = make_response(w)
    response.headers.set('Content-Type', 'application/octet-stream')
    response.headers.set(
        'Content-Disposition', 'attachment', filename='file.docx')
    return response

I've got this error, when I tried the code above:

Working outside of application context.

This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context().  See the
documentation for more information.

I've tried a lot of things and nothing works and I've lost hours to find a solution in other topics or StackOverflow, but nothing really clearify a solution.

I already tried the solution in this StackOverflow topic, but I get this error:

'Response' object has no attribute 'get'

I'm glad to hear you worked out the import problem -- the way you import the FileWrapper depends on the version of Werkzeug you have installed, and it looks like your account or virtualenv is using an older one than I thought you were.

You don't need to use Flask for this; Flask is an alternative web framework to Django. Your site is a Django one, so the way you set headers is different. You should remove the Flask code, and then set the headers that Conrad mentioned like this:

doc_io = io.BytesIO()
document.save(doc_io)        
doc_io.seek(0)

w = FileWrapper(doc_io)
return FileResponse(w, as_attachment=True, filename='file.docx', content_type="application/octet-stream")

The as_attachment and the filename arguments set the Content-Disposition: attachment; filename="myfile.doc" header, and the content_type sets the Content-Type: application/octet-stream.

If that still doesn't work, can we take a look at your code? We can see it from our admin interface, but we always ask for permission first. It would also be useful to know the URL that causes the problem.

Thank you, giles!

I just had to change the content_type from application/octet-stream to application/vnd.openxmlformats-officedocument.wordprocessingml.document, as I saw in this post as the correct MIME type for a docx file.

Yes, now it worked! EXCEPT for just one thing: the name of the file downloaded is different from the one I've set in the code. It put the filename as the same name of the view's method that returned it.

If the code is the one below, it works fine at localhost and generates a file called 'file.docx' (but this solution is the first one and it does not work at PythonAnywhere):

dev generateFile(request):
    ...
    doc_io = io.BytesIO()
    document.save(doc_io)        
    doc_io.seek(0)

    return FileResponse(doc_io, as_attachment=True, filename='file.docx')

And this final solution, as suggested, works fine at localhost (and PythonAnywhere) and generates a file. But the name of the file generated is not 'file.docx'. It is 'generateFile.docx', the same name of the method that returned it.

dev generateFile(request):
    ...
    doc_io = io.BytesIO()
    document.save(doc_io)        
    doc_io.seek(0)

    w = FileWrapper(doc_io)
    return FileResponse(w, as_attachment=True,
        filename='file.docx',
        content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document")

You can look at the code if you want. The URL is /contratos/<id>/edicaoProposta. It will not work for now since it has no data stored yet, cause I was trying to solve this thing first.

Hey, I found the solution using StreamingHttpResponse:

from django.http import StreamingHttpResponse
from werkzeug.wsgi import FileWrapper

def generateFile(request):
    ...
    doc_io = io.BytesIO()
    document.save(doc_io)        
    doc_io.seek(0)

    response = StreamingHttpResponse(
        FileWrapper(doc_io),
        content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    )
    response['Content-Disposition'] = "attachment; filename=file.docx"
    return response

Now, it downloaded the file with the right name! Thanks for the help!

Great! Glad you were able to get there in the end :-)