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()
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. –
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
Este código funciona bien, por lo que hay algo mal con su entorno que tendrá que solucionar. –