Как говорит Мандрейк, конструктор HttpResponse принимает итерируемые объекты.
К счастью, формат ZIP таков, что архив может быть создан за один проход, запись центрального каталога находится в самом конце файла:
(Изображение из Википедия )
И, к счастью, zipfile
действительно ничего не ищет, пока вы только добавляете файлы.
Вот код, который я придумал. Некоторые заметки:
- Я использую этот код для архивирования нескольких изображений JPEG. Нет смысла сжимать их, я использую ZIP только как контейнер.
- Использование памяти равно O (size_of_largest_file), а не O (size_of_archive). И это достаточно хорошо для меня: множество сравнительно небольших файлов, которые складываются в потенциально огромный архив
- Этот код не устанавливает заголовок Content-Length, поэтому пользователь не может получить хороший индикатор прогресса. должно быть возможно , чтобы рассчитать это заранее, если известны размеры всех файлов.
- Предоставление почтового индекса пользователю таким образом означает, что возобновление загрузки не будет работать.
Итак, вот так:
import zipfile
class ZipBuffer(object):
""" A file-like object for zipfile.ZipFile to write into. """
def __init__(self):
self.data = []
self.pos = 0
def write(self, data):
self.data.append(data)
self.pos += len(data)
def tell(self):
# zipfile calls this so we need it
return self.pos
def flush(self):
# zipfile calls this so we need it
pass
def get_and_clear(self):
result = self.data
self.data = []
return result
def generate_zipped_stream():
sink = ZipBuffer()
archive = zipfile.ZipFile(sink, "w")
for filename in ["file1.txt", "file2.txt"]:
archive.writestr(filename, "contents of file here")
for chunk in sink.get_and_clear():
yield chunk
archive.close()
# close() generates some more data, so we yield that too
for chunk in sink.get_and_clear():
yield chunk
def my_django_view(request):
response = HttpResponse(generate_zipped_stream(), mimetype="application/zip")
response['Content-Disposition'] = 'attachment; filename=archive.zip'
return response