2008-09-15 24 views
45

¿Cómo se sirve a los usuarios un archivo ZIP generado dinámicamente en Django?Sirviendo archivos ZIP generados dinámicamente en Django

Estoy creando un sitio, donde los usuarios pueden elegir cualquier combinación de libros disponibles y descargarlos como archivo ZIP. Me preocupa que la generación de dichos archivos para cada solicitud reduzca la velocidad de mi servidor. También he escuchado que Django actualmente no tiene una buena solución para servir archivos generados dinámicamente.

Respuesta

38

La solución es la siguiente.

Utilice el módulo Python zipfile para crear un archivo comprimido, pero como el archivo especifique el objeto StringIO (el constructor ZipFile requiere un objeto similar a un archivo). Agregue los archivos que desea comprimir. Luego, en su aplicación Django, devuelva el contenido del objeto StringIO en HttpResponse con mimetype establecido en application/x-zip-compressed (o al menos application/octet-stream). Si lo desea, puede configurar el encabezado content-disposition, pero esto no debería ser realmente necesario.

Pero tenga cuidado, crear archivos comprimidos en cada solicitud es una mala idea y esto puede matar su servidor (sin contar los tiempos de espera si los archivos son grandes). El enfoque basado en el rendimiento consiste en almacenar en caché la salida generada en algún lugar del sistema de archivos y regenerarla solo si los archivos fuente han cambiado. Una idea aún mejor es preparar los archivos por adelantado (por ejemplo, mediante un trabajo cron) y hacer que su servidor web los sirva como estáticos habituales.

+0

StringIO desaparecerá en Python 3.0, por lo que es posible que desee colocar el código de forma acorde. –

+11

No se ha ido, solo se movió al módulo io. http://docs.python.org/3.0/library/io.html#io.StringIO –

+1

Solo como una idea, ya que estás creando manualmente una HttpResponse, ¿no podrías usar eso como el buffer? Con eso me refiero a pasar la respuesta a 'zipfile' y dejar que escriba directamente sobre eso. Lo he hecho con otras cosas. Si se trata de flujos pesados, podría ser más rápido y más eficiente en la memoria. – Oli

0

¿No puedes simplemente escribir un enlace a un "servidor zip" o algo así? ¿Por qué el archivo comprimido en sí necesita ser servido desde Django? Una secuencia de comandos CGI de la era de los 90 para generar un zip y escupirlo a stdout es realmente todo lo que se necesita aquí, al menos hasta donde puedo ver.

6

Django no maneja directamente la generación de contenido dinámico (específicamente archivos Zip). Ese trabajo lo haría la biblioteca estándar de Python. Puede ver cómo crear dinámicamente un archivo Zip en Python here.

Si le preocupa la ralentización de su servidor, puede almacenar en caché las solicitudes si espera tener muchas de las mismas solicitudes. Puede usar Django's cache framework para ayudarlo con eso.

En general, los archivos de compresión pueden ser intensivos en la CPU, pero Django no debe ser más lento que otro framework web de Python.

1

Sugiero usar un modelo separado para almacenar esos archivos zip temporales. Puede crear zip on-fly, guardar en el modelo con el campo de archivos y finalmente enviar url al usuario.

Ventajas:

  • servicio de archivos zip estáticas con mecanismo de medios Django (como las subidas habituales).
  • Capacidad para limpiar archivos zip obsoletos mediante la ejecución regular de scripts cron (que puede usar el campo de fecha desde el modelo de archivo zip).
37

Aquí está una vista de Django para hacer esto:

import os 
import zipfile 
import StringIO 

from django.http import HttpResponse 


def getfiles(request): 
    # Files (local path) to put in the .zip 
    # FIXME: Change this (get paths from DB etc) 
    filenames = ["/tmp/file1.txt", "/tmp/file2.txt"] 

    # Folder name in ZIP archive which contains the above files 
    # E.g [thearchive.zip]/somefiles/file2.txt 
    # FIXME: Set this to something better 
    zip_subdir = "somefiles" 
    zip_filename = "%s.zip" % zip_subdir 

    # Open StringIO to grab in-memory ZIP contents 
    s = StringIO.StringIO() 

    # The zip compressor 
    zf = zipfile.ZipFile(s, "w") 

    for fpath in filenames: 
     # Calculate path for file in zip 
     fdir, fname = os.path.split(fpath) 
     zip_path = os.path.join(zip_subdir, fname) 

     # Add file, at correct path 
     zf.write(fpath, zip_path) 

    # Must close zip for all contents to be written 
    zf.close() 

    # Grab ZIP file from in-memory, make response with correct MIME-type 
    resp = HttpResponse(s.getvalue(), mimetype = "application/x-zip-compressed") 
    # ..and correct content-disposition 
    resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename 

    return resp 
+2

No es necesario en este ejemplo, pero en general, asegúrese de que el nombre del archivo en el encabezado de disposición de contenido esté entre comillas y se escape como necesario. Por ejemplo, si hay un espacio en el nombre del archivo, la mayoría de los navegadores solo usarán la parte hasta el espacio para el nombre del archivo (por ejemplo, 'attachment; filename = Test File.zip' se guardará como' Test'.) –

+0

@MikeDeSimone Good point . ¿Hay un buen método para escapar del nombre de archivo para ese contexto? – dbr

+0

http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http –

5

Conector desvergonzado: puede usar django-zipview para el mismo propósito.

Después de un pip install django-zipview:

from zipview.views import BaseZipView 

from reviews import Review 


class CommentsArchiveView(BaseZipView): 
    """Download at once all comments for a review.""" 

    def get_files(self): 
     document_key = self.kwargs.get('document_key') 
     reviews = Review.objects \ 
      .filter(document__document_key=document_key) \ 
      .exclude(comments__isnull=True) 

     return [review.comments.file for review in reviews if review.comments.name] 
3

Para python3 utilizo el io.ByteIO desde StringIO está en desuso para lograr esto. Espero eso ayude.

import io 

def my_downloadable_zip(request): 
    zip_io = io.BytesIO() 
    with zipfile.ZipFile(zip_io, mode='w', compression=zipfile.ZIP_DEFLATED) as backup_zip: 
     backup_zip.write('file_name_loc_to_zip') # u can also make use of list of filename location 
               # and do some iteration over it 
    response = HttpResponse(zip_io.getvalue(), content_type='application/x-zip-compressed') 
    response['Content-Disposition'] = 'attachment; filename=%s' % 'your_zipfilename' + ".zip" 
    response['Content-Length'] = zip_io.tell() 
    return response 
+0

Al usar un código como este, no puedo obtener el nombre correcto del archivo. Por el momento, es solo una cadena aleatoria que se parece a un UUID. – freethebees

Cuestiones relacionadas