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?

Unfortunately that's not specific enough. You're going to have to put them through a binary diff. – Ignacio Vazquez-Abrams Mar 16, 2012 at 3:38

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.