never_cache decorator breaks HttpResponse with iterator content
|Reported by:||Ben Davis||Owned by:||nobody|
|Component:||Core (Cache system)||Version:||master|
|Cc:||Forest Bond, net147||Triage Stage:||Accepted|
|Has patch:||no||Needs documentation:||no|
|Needs tests:||no||Patch needs improvement:||no|
This generally isn't a problem, as normal views don't typically use never_cache. However, admin views use never_cache by default, and if you're streaming content from the result of an admin action (for example, a very large csv file), you'll get an empty response.
def export_assembly_list(self, request, batches): import csv from StringIO import StringIO #define output columns cols = get_csv_cols() stream = StringIO() writer = csv.writer(stream) def content(): writer.writerow([k for k,v in cols]) yield stream.getvalue() stream.truncate(0) for batch in batches: for invitation in batch.invitations.values(*[v for k,v in cols]): writer.writerow([invitation[v] for k,v in cols]) yield stream.getvalue() stream.truncate(0) return response = HttpResponse(content(), mimetype='text/csv') response['Content-Disposition'] = 'attachment; filename=batch_assembly_list.csv' return response
What happens is this: never_cache() calls django.utils.cache.add_never_cache_headers(), which calls patch_response_headers(). This function adds the ETag header to the response, and to get the ETag, it does this:
if not response.has_header('ETag'): response['ETag'] = '"%s"' % md5_constructor(response.content).hexdigest()
response.content, in turn, does
"".join(self._container), which causes the iterator to "complete". The problem with this is that the next time the iterator is called, the cursor is already at the end of the iterator, thus the empty response.
The workaround is easy but not at all obvious: just define the Etag on your response.
The overall fix should be just as easy: In the patch_response_headers() function, just detect if the response content is an iterator, and if so use a different method for generating the ETag. Although, I have no idea what that should be.