2011-02-28 17 views
17

Próbuję przesyłać strumieniowo plik CSV jako załącznik do pobrania. Pliki CSV mają rozmiar 4 MB lub więcej i potrzebuję sposobu, aby użytkownik aktywnie pobierał pliki, nie czekając, aż wszystkie dane zostaną najpierw utworzone i zatwierdzone w pamięci.Przesyłanie strumieniowe pliku CSV w Django

Najpierw użyłem mojego własnego pliku na podstawie klasy Django FileWrapper. To się nie udało. Potem zobaczyłem metodę tutaj używając generatora strumienia odpowiedzi: How to stream an HttpResponse with Django

Kiedy podnieść błąd w generatorze, widzę, że tworzę odpowiednie dane z funkcji get_row_data(), ale gdy próbuję zwraca odpowiedź, która wraca pusta. Wyłączyłem także Django GZipMiddleware. Czy ktoś wie, co robię źle?

Edytuj: Problem, który miałem, dotyczył ConditionalGetMiddleware. Musiałem go zastąpić, kod znajduje się w odpowiedzi poniżej.

Oto widok:

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 

Oto generator używam do strumienia odpowiedzi:

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) 

A oto jak tworzę danych CSV rzędu:

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() 

Odpowiedz

30

Oto prosty kod, który przesyła strumieniowo plik CSV; prawdopodobnie można iść z tym do tego, co trzeba zrobić:

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 

To po prostu zapisuje każdy wiersz do pliku w pamięci, czyta wiersz i daje ją.

Ta wersja jest bardziej wydajny do generowania danych zbiorczych, ale należy zrozumieć powyższe przed użyciem go:

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

Nie potrzebowałem jeszcze przesyłać strumieniowo danych, ale miło jest wiedzieć, jak szybko można uzyskać coś prostego i eleganckiego. –

+0

Chociaż bardzo podoba mi się ta odpowiedź, okazuje się, że to nie mój problem. Dosłownie użyłem tego kodu, który napisałeś, aby sprawdzić, czy wygeneruje on odpowiedź, ale odpowiedź wraca jako 0 bajtów. Więc wciąż utknąłem z tym samym wynikiem. – bfrederix

+0

Ten kod działa poprawnie, więc w twoim środowisku jest coś nie tak, co trzeba będzie rozwiązać. –

2

Problem miałem był z ConditionalGetMiddleware. Widziałem django-tłokowe wymyślić middleware zastępczej dla ConditionalGetMiddleware który umożliwia strumieniowe:

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) 

Więc wtedy zastąpić ConditionalGetMiddleware z ConditionalMiddlewareCompatProxy middleware, aw widoku (kod pożyczoną od sprytnego odpowiedź na to pytanie)

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

pośredniej problem został rozwiązany w Django 1,5 i StreamingHttpResponse został wprowadzony. Poniższa należy zrobić:

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 

Jest jakaś dokumentacja how to output csv from Django ale nie skorzystać z StreamingHttpResponse więc poszedł do przodu i opened a ticket in order to track it.

Powiązane problemy