Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
I have a django view that I want to return an Excel file. The code is below:
def get_template(request, spec_pk):
spec = get_object_or_404(Spec, pk=spec_pk)
response = HttpResponse(spec.get_template(), mimetype='application/ms-excel')
response['Content-Disposition'] = 'attachment; filename=%s_template.xls' % spec.name
return response
In that example, the type of spec.get_template()
is <type 'bytearray'>
which contains the binary data of an Excel spreadsheet.
The problem is, when I try to download that view, and open it with Excel, it comes in as garbled binary data. I know that the bytearray
is correct though, because if I do the following:
f = open('temp.xls', 'wb')
f.write(spec.get_template())
I can open temp.xls
in the Excel perfectly.
I've even gone so far as to modify my view to:
def get_template(request, spec_pk):
spec = get_object_or_404(Spec, pk=spec_pk)
f = open('/home/user/temp.xls', 'wb')
f.write(spec.get_template())
f.close()
f = open('/home/user/temp.xls', 'rb')
response = HttpResponse(f.read(), mimetype='application/ms-excel')
response['Content-Disposition'] = 'attachment; filename=%s_template.xls' % spec.name
return response
And it works perfectly- I can open the xls file from the browser into Excel and everything is alright.
So my question is- what do I need to do that bytearray
before I pass it to the HttpResponse
. Why does saving it as binary, then re-opening it work perfectly, but passing the bytearray
itself results in garbled data?
–
Okay, through completely random (and very persistent) trial and error, I found a solution using the python binascii
module.
This works:
response = HttpResponse(binascii.a2b_qp(spec.get_template()), mimetype='application/ms-excel')
According to the python docs for binascii.a2b_qp
:
Convert a block of quoted-printable data back to binary and return the binary data. More than one line may be passed at a time. If the optional argument header is present and true, underscores will be decoded as spaces.
Would love for someone to tell me why saving it as binary, then reopening it worked though.
TLDR: Cast the bytearray
to bytes
The problem is that Django's HttpResponse
doesn't treat bytearray
objects the same as bytes
objects. HttpResponse
has a special case for bytes
which sends them to the client as-is, but it doesn't have a similar case for bytearray
objects. They get handled by a catchall case which treats them as an iterable of int
.
If you open the corrupted Excel file in a text editor, you'll probably see a bunch of ascii numbers, which are the numeric values of the bytes you were trying to return from the bytearray
Mr. Digital Flapjack gives a very complete explanation here: https://www.digitalflapjack.com/blog/2021/4/27/bytes-not-bytearrays-with-django-please
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.