5

I am trying to serve a .json file through this function. The problem is that every time I make the request the browser displays the content instead of downloading the file.

I think it could be due to the fact that I am using .read() as a parameter for the HttpResponse object constructor. However, if I use only the file object, I get the following exception:

TypeError: cannot serialize '_io.BufferedRandom' object

Code

try:
    invoices = models.Invoice.objects.filter(pk__in=document_ids).order_by(*ordering)
    pcustomers = models.CustomerProxy.objects.all()
    mixed_query = list(invoices) + list(pcustomers)

    file = tempfile.NamedTemporaryFile(suffix='.json')
    file.write(serializers.serialize('json', mixed_query).encode())
    file.seek(0)

    response = HttpResponse(file.read(), content_type='application/json')
    response['Content-Disposition'] = 'attachment; filename=%s' % file.name
    response['Content-Length'] = os.path.getsize(file.name)

except Exception:
    raise

return response
Ev.
  • 1,537
  • 1
  • 24
  • 49

3 Answers3

10

You don't need to go through the whole file generation process to create a downloadable file, you just need to add the Content-Disposition header normally. Does the code below work?

...
mixed_query = list(invoices) + list(pcustomers)
json_str = serializers.serialize('json', mixed_query))
response = HttpResponse(json_str, content_type='application/json')
response['Content-Disposition'] = 'attachment; filename=export.json'
raphv
  • 1,153
  • 7
  • 10
  • 1
    The file is generated and I can see it under `/tmp`. I just can't make it be served as a downloadable file instead of a content. – Ev. May 18 '16 at 12:25
  • You don't need to go through the whole file generation process to create a downloadable file, you just need to add the Content-Disposition header. How does it work if you simplify the code, remove the whole file creation part as I'm doing in the code above? – raphv May 18 '16 at 12:28
  • 1
    If I do that I have the same result as before, which is the page displayed as json. – Ev. May 18 '16 at 12:32
  • I have tried similar code at home and do get an attachment. I suspect that your browser may be associated with the JSON file type. Can you try it (a) with another browser (b) with another content_type (e.g. text/plain or application/x-invoices) – raphv May 18 '16 at 12:59
  • I had a function encapsulating the request and replacing the header. You were right. After a lot of hours trying to see what was wrong I missed this small detail. Thanks. – Ev. May 18 '16 at 13:02
2

Based on the code that you show, you do not need to write to a temporary file. Why don't you just pass the result of serialize() into HttpResponse()?

response = HttpResponse(serializers.serialize('json', mixed_query), content_type='application/json')

You can set the attachment name to whatever you like, something descriptive would seem to be better than the random alphanumeric string generated by tempfile.NamedTemporaryFile().

response['Content-Disposition'] = 'attachment; filename="invoices_and_customers.json"'

If you really want to specify the length:

response['Content-Length'] = len(response.content)

or you could add the ConditionalGetMiddleware middleware to your settings and have Django add the Content-Length for you.

mhawke
  • 84,695
  • 9
  • 117
  • 138
  • Exactly. We are using a function to encapsulate the request and in the end it was replacing the header. I will give the right answer to him since he might be eager to get more points once he's a beginner :p. Thanks! – Ev. May 18 '16 at 13:04
  • 1
    Fair enough. Also @raphv answered first. – mhawke May 18 '16 at 13:07
1

add this to your Http response

HttpResponse(mimetype='application/force-download')
Dave Plug
  • 1,068
  • 1
  • 11
  • 22
  • It doesn't work. I am using Python 3.5 and it says there is no mimetype attribute to that constructor. Maybe if you meant `content_type="application/force-download"`, I tried that and it did either. – Ev. May 18 '16 at 12:05