1818"""
1919Common library to export either CSV or XLS.
2020"""
21- import gc
2221import re
2322import logging
2423import numbers
2726
2827import six
2928import tablib
30- import openpyxl
29+ import xlsxwriter
3130from django .http import HttpResponse , StreamingHttpResponse
3231from django .utils .encoding import smart_str
3332
3635LOG = logging .getLogger ()
3736
3837DOWNLOAD_CHUNK_SIZE = 1 * 1024 * 1024 # 1MB
39- ILLEGAL_CHARS = r'[\000-\010]|[\013-\014]|[\016-\037]'
38+
39+ ILLEGAL_CHARS_RE = re .compile (r'[\000-\010]|[\013-\014]|[\016-\037]' )
40+ HYPERLINK_RE = re .compile (r'^(https?://.+)' , re .IGNORECASE )
41+
4042FORMAT_TO_CONTENT_TYPE = {
4143 'csv' : 'application/csv' ,
4244 'xls' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ,
4345 'json' : 'application/json'
4446}
4547
46-
4748def nullify (cell ):
48- return cell if cell is not None else "NULL"
49-
49+ return cell if cell is not None else "NULL"
5050
5151def file_reader (fh ):
52- """Generator that reads a file, chunk-by-chunk."""
53- while True :
54- chunk = fh .read (DOWNLOAD_CHUNK_SIZE )
55- if not chunk :
56- fh .close ()
57- break
58- yield chunk
52+ """Generator that reads a file, chunk-by-chunk."""
53+ while True :
54+ chunk = fh .read (DOWNLOAD_CHUNK_SIZE )
55+ if not chunk :
56+ fh .close ()
57+ break
58+ yield chunk
5959
6060
6161def encode_row (row , encoding = None , make_excel_links = False ):
62- encoded_row = []
63- encoding = encoding or i18n .get_site_encoding ()
62+ encoded_row = []
63+ encoding = encoding or i18n .get_site_encoding ()
6464
65- for cell in row :
66- if isinstance (cell , six .string_types ):
67- cell = re .sub (ILLEGAL_CHARS , '?' , cell )
68- if make_excel_links :
69- cell = re . compile ( '^(https?://.+)' , re . IGNORECASE ) .sub (r'=HYPERLINK("\1")' , cell )
70- cell = nullify (cell )
71- if not isinstance (cell , numbers .Number ):
72- cell = smart_str (cell , encoding , strings_only = True , errors = 'replace' )
73- encoded_row .append (cell )
74- return encoded_row
65+ for cell in row :
66+ if isinstance (cell , six .string_types ):
67+ cell = ILLEGAL_CHARS_RE .sub ('?' , cell )
68+ if make_excel_links :
69+ cell = HYPERLINK_RE .sub (r'=HYPERLINK("\1")' , cell )
70+ cell = nullify (cell )
71+ if not isinstance (cell , numbers .Number ):
72+ cell = smart_str (cell , encoding , strings_only = True , errors = 'replace' )
73+ encoded_row .append (cell )
74+ return encoded_row
7575
7676
7777def dataset (headers , data , encoding = None ):
@@ -90,43 +90,32 @@ def dataset(headers, data, encoding=None):
9090
9191 return dataset
9292
93-
94- class XlsWrapper (object ):
95- def __init__ (self , xls ):
96- self .xls = xls
97-
98-
99- def xls_dataset (workbook ):
100- output = string_io ()
101- workbook .save (output )
102- output .seek (0 )
103- return XlsWrapper (output .read ())
104-
105-
10693def create_generator (content_generator , format , encoding = None ):
10794 if format == 'csv' :
10895 show_headers = True
10996 for headers , data in content_generator :
11097 yield dataset (show_headers and headers or None , data , encoding ).csv
11198 show_headers = False
11299 elif format == 'xls' :
113- workbook = openpyxl .Workbook (write_only = True )
114- worksheet = workbook .create_sheet ()
100+ output = string_io ()
101+ workbook = xlsxwriter .Workbook (output )
102+ worksheet = workbook .add_worksheet ()
115103 row_ctr = 0
116104
117105 for _headers , _data in content_generator :
118106 # Write headers to workbook once
119107 if _headers and row_ctr == 0 :
120- worksheet .append ( encode_row (_headers , encoding ))
108+ worksheet .write_row ( row_ctr , 0 , encode_row (_headers , encoding , make_excel_links = False ))
121109 row_ctr += 1
122110
123111 # Write row data to workbook
124112 for row in _data :
125- worksheet .append ( encode_row (row , encoding , make_excel_links = True ))
113+ worksheet .write_row ( row_ctr , 0 , encode_row (row , encoding , make_excel_links = False ))
126114 row_ctr += 1
127115
128- yield xls_dataset (workbook ).xls
129- gc .collect ()
116+ workbook .close ()
117+ output .seek (0 )
118+ yield output .getvalue ()
130119 else :
131120 raise Exception ("Unknown format: %s" % format )
132121
@@ -147,7 +136,7 @@ def make_response(generator, format, name, encoding=None, user_agent=None): # T
147136 pass
148137 elif format == 'xls' :
149138 format = 'xlsx'
150- resp = HttpResponse ( next ( generator ) , content_type = content_type )
139+ resp = StreamingHttpResponse ( generator , content_type = content_type )
151140 elif format == 'json' or format == 'txt' :
152141 resp = HttpResponse (generator , content_type = content_type )
153142 else :
0 commit comments