# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import division from csv import DictWriter # noqa from csv import writer # noqa from StringIO import StringIO from django.http import HttpResponse # noqa from django import template as django_template from django import VERSION # noqa class CsvDataMixin(object): """CSV data Mixin - provides handling for CSV data. .. attribute:: columns A list of CSV column definitions. If omitted - no column titles will be shown in the result file. Optional. """ def __init__(self): self.out = StringIO() super(CsvDataMixin, self).__init__() if hasattr(self, "columns"): self.writer = DictWriter(self.out, map(self.encode, self.columns)) self.is_dict = True else: self.writer = writer(self.out) self.is_dict = False def write_csv_header(self): if self.is_dict: try: self.writer.writeheader() except AttributeError: # For Python<2.7 self.writer.writerow(dict(zip( self.writer.fieldnames, self.writer.fieldnames))) def write_csv_row(self, args): if self.is_dict: self.writer.writerow(dict(zip( self.writer.fieldnames, map(self.encode, args)))) else: self.writer.writerow(map(self.encode, args)) def encode(self, value): # csv and StringIO cannot work with mixed encodings, # so encode all with utf-8 return unicode(value).encode('utf-8') class BaseCsvResponse(CsvDataMixin, HttpResponse): """Base CSV response class. Provides handling of CSV data.""" def __init__(self, request, template, context, content_type, **kwargs): super(BaseCsvResponse, self).__init__() self['Content-Disposition'] = 'attachment; filename="%s"' % ( kwargs.get("filename", "export.csv"),) self['Content-Type'] = content_type self.context = context self.header = None if template: # Display some header info if provided as a template header_template = django_template.loader.get_template(template) context = django_template.RequestContext(request, self.context) self.header = header_template.render(context) if self.header: self.out.write(self.encode(self.header)) self.write_csv_header() for row in self.get_row_data(): self.write_csv_row(row) self.out.flush() self.content = self.out.getvalue() self.out.close() def get_row_data(self): raise NotImplementedError("You must define a get_row_data method on %s" % self.__class__.__name__) if VERSION >= (1, 5, 0): from django.http import StreamingHttpResponse # noqa class BaseCsvStreamingResponse(CsvDataMixin, StreamingHttpResponse): """Base CSV Streaming class. Provides streaming response for CSV data. """ def __init__(self, request, template, context, content_type, **kwargs): super(BaseCsvStreamingResponse, self).__init__() self['Content-Disposition'] = 'attachment; filename="%s"' % ( kwargs.get("filename", "export.csv"),) self['Content-Type'] = content_type self.context = context self.header = None if template: # Display some header info if provided as a template header_template = django_template.loader.get_template(template) context = django_template.RequestContext(request, self.context) self.header = header_template.render(context) self._closable_objects.append(self.out) self.streaming_content = self.get_content() def buffer(self): buf = self.out.getvalue() self.out.truncate(0) return buf def get_content(self): if self.header: self.out.write(self.encode(self.header)) self.write_csv_header() yield self.buffer() for row in self.get_row_data(): self.write_csv_row(row) yield self.buffer() def get_row_data(self): raise NotImplementedError("You must define a get_row_data method " "on %s" % self.__class__.__name__)