2011-02-28 51 views
17

Estoy intentando transmitir un archivo csv como una descarga de archivo adjunto. Los archivos CSV tienen un tamaño de 4 MB o más, y necesito una forma de que el usuario descargue activamente los archivos sin esperar a que se creen todos los datos y se los confíe primero a la memoria.Transmitir un archivo CSV en Django

Primero utilicé mi propio contenedor de archivos según la clase FileWrapper de Django. Eso falló Entonces vi un método aquí por el uso de un generador para transmitir la respuesta: How to stream an HttpResponse with Django

Cuando planteo un error dentro del generador, puedo ver que estoy creando los datos adecuados con la función get_row_data(), pero cuando intento devuelve la respuesta que vuelve vacía. También he desactivado el Django GZipMiddleware. ¿Alguien sabe lo que estoy haciendo mal?

Editar: El problema que estaba teniendo fue con el ConditionalGetMiddleware. Tuve que reemplazarlo, el código está en una respuesta a continuación.

Aquí es la vista:

from django.views.decorators.http import condition 

@condition(etag_func=None) 
def csv_view(request, app_label, model_name): 
    """ Based on the filters in the query, return a csv file for the given model """ 

    #Get the model 
    model = models.get_model(app_label, model_name) 

    #if there are filters in the query 
    if request.method == 'GET': 
     #if the query is not empty 
     if request.META['QUERY_STRING'] != None: 
      keyword_arg_dict = {} 
      for key, value in request.GET.items(): 
       #get the query filters 
       keyword_arg_dict[str(key)] = str(value) 
      #generate a list of row objects, based on the filters 
      objects_list = model.objects.filter(**keyword_arg_dict) 
     else: 
      #get all the model's objects 
      objects_list = model.objects.all() 
    else: 
     #get all the model's objects 
     objects_list = model.objects.all() 
    #create the reponse object with a csv mimetype 
    response = HttpResponse(
     stream_response_generator(model, objects_list), 
     mimetype='text/plain', 
     ) 
    response['Content-Disposition'] = "attachment; filename=foo.csv" 
    return response 

Aquí es el generador que utilizo para transmitir la respuesta:

def stream_response_generator(model, objects_list): 
    """Streaming function to return data iteratively """ 
    for row_item in objects_list: 
     yield get_row_data(model, row_item) 
     time.sleep(1) 

Y aquí es cómo crear los datos de fila csv:

def get_row_data(model, row): 
    """Get a row of csv data from an object""" 
    #Create a temporary csv handle 
    csv_handle = cStringIO.StringIO() 
    #create the csv output object 
    csv_output = csv.writer(csv_handle) 
    value_list = [] 
    for field in model._meta.fields: 
     #if the field is a related field (ForeignKey, ManyToMany, OneToOne) 
     if isinstance(field, RelatedField): 
      #get the related model from the field object 
      related_model = field.rel.to 
      for key in row.__dict__.keys(): 
       #find the field in the row that matches the related field 
       if key.startswith(field.name): 
        #Get the unicode version of the row in the related model, based on the id 
        try: 
         entry = related_model.objects.get(
          id__exact=int(row.__dict__[key]), 
          ) 
        except: 
         pass 
        else: 
         value = entry.__unicode__().encode("utf-8") 
         break 
     #if it isn't a related field 
     else: 
      #get the value of the field 
      if isinstance(row.__dict__[field.name], basestring): 
       value = row.__dict__[field.name].encode("utf-8") 
      else: 
       value = row.__dict__[field.name] 
     value_list.append(value) 
    #add the row of csv values to the csv file 
    csv_output.writerow(value_list) 
    #Return the string value of the csv output 
    return csv_handle.getvalue() 

Respuesta

30

Aquí hay un código simple que transmitirá un CSV; es probable que pueda pasar de esto a lo que hay que hacer:

import cStringIO as StringIO 
import csv 

def csv(request): 
    def data(): 
     for i in xrange(10): 
      csvfile = StringIO.StringIO() 
      csvwriter = csv.writer(csvfile) 
      csvwriter.writerow([i,"a","b","c"]) 
      yield csvfile.getvalue() 

    response = HttpResponse(data(), mimetype="text/csv") 
    response["Content-Disposition"] = "attachment; filename=test.csv" 
    return response 

Esto simplemente escribe cada fila a un archivo en memoria, se lee en la fila y lo cede.

Esta versión es más eficiente para la generación de datos en bloque, pero asegúrese de entender lo anterior antes de usarlo:

import cStringIO as StringIO 
import csv 

def csv(request): 
    csvfile = StringIO.StringIO() 
    csvwriter = csv.writer(csvfile) 

    def read_and_flush(): 
     csvfile.seek(0) 
     data = csvfile.read() 
     csvfile.seek(0) 
     csvfile.truncate() 
     return data 

    def data(): 
     for i in xrange(10): 
      csvwriter.writerow([i,"a","b","c"]) 
     data = read_and_flush() 
     yield data 

    response = HttpResponse(data(), mimetype="text/csv") 
    response["Content-Disposition"] = "attachment; filename=test.csv" 
    return response 
+0

No he tenido la necesidad de transmitir datos todavía, pero es bueno saber lo rápido que es obtener algo que sea simple y elegante. –

+0

Aunque realmente me gusta esta respuesta, resulta que este no es mi problema. Literalmente usé este código exacto que escribiste, solo para ver si generaría una respuesta, pero la respuesta vuelve como 0 bytes. Así que todavía estoy atrapado con el mismo resultado. – bfrederix

+0

Este código funciona bien, por lo que hay algo mal con su entorno que tendrá que solucionar. –

2

El problema que tenía era con la ConditionalGetMiddleware. Vi django-pistón llegar a un middleware reemplazo para el ConditionalGetMiddleware que permite la transmisión:

from django.middleware.http import ConditionalGetMiddleware 

def compat_middleware_factory(klass): 
    """ 
    Class wrapper that only executes `process_response` 
    if `streaming` is not set on the `HttpResponse` object. 
    Django has a bad habbit of looking at the content, 
    which will prematurely exhaust the data source if we're 
    using generators or buffers. 
    """ 
    class compatwrapper(klass): 
     def process_response(self, req, resp): 
      if not hasattr(resp, 'streaming'): 
       return klass.process_response(self, req, resp) 
      return resp 
    return compatwrapper 

ConditionalMiddlewareCompatProxy = compat_middleware_factory(ConditionalGetMiddleware) 

Así entonces reemplazará ConditionalGetMiddleware con su middleware ConditionalMiddlewareCompatProxy, y en su opinión (código tomado de una respuesta inteligente a esta pregunta):

def csv_view(request): 
    def data(): 
     for i in xrange(10): 
      csvfile = StringIO.StringIO() 
      csvwriter = csv.writer(csvfile) 
      csvwriter.writerow([i,"a","b","c"]) 
      yield csvfile.getvalue() 

    #create the reponse object with a csv mimetype 
    response = HttpResponse(
     data(), 
     mimetype='text/csv', 
     ) 
    #Set the response as an attachment with a filename 
    response['Content-Disposition'] = "attachment; filename=test.csv" 
    response.streaming = True 
    return response 
11

La cuestión middleware ha sido resuelto como de Django 1.5 y una StreamingHttpResponse se ha introducido. Lo siguiente debe hacer:

import cStringIO as StringIO 
import csv 

def csv_view(request): 
    ... 
    # Assume `rows` is an iterator or lists 
    def stream(): 
     buffer_ = StringIO.StringIO() 
     writer = csv.writer(buffer_) 
     for row in rows: 
      writer.writerow(row) 
      buffer_.seek(0) 
      data = buffer_.read() 
      buffer_.seek(0) 
      buffer_.truncate() 
      yield data 
    response = StreamingHttpResponse(
     stream(), content_type='text/csv' 
    ) 
    disposition = "attachment; filename=file.csv" 
    response['Content-Disposition'] = disposition 
    return response 

Hay algo de documentación sobre how to output csv from Django pero no toma ventaja de la StreamingHttpResponse así que siguió adelante y opened a ticket in order to track it.