From 9ad412827b0e9c1eeeab602e24181e20c61816a3 Mon Sep 17 00:00:00 2001 From: Rob Raymond Date: Fri, 14 Feb 2014 15:36:14 -0700 Subject: [PATCH] Refactor CSV generation code to utils Moving general CSV generation code from usage into utils so that it can be used by other dashboards. The file is named csvbase to avoid name collisions with csv module. Change-Id: I3f4a573b53cb2d85fa36e84320707d74917498ad Closes-bug: #1280475 --- horizon/utils/csvbase.py | 144 ++++++++++++++++++ .../dashboards/admin/overview/views.py | 4 +- .../dashboards/project/overview/views.py | 5 +- openstack_dashboard/usage/base.py | 129 ---------------- 4 files changed, 149 insertions(+), 133 deletions(-) create mode 100644 horizon/utils/csvbase.py diff --git a/horizon/utils/csvbase.py b/horizon/utils/csvbase.py new file mode 100644 index 0000000000..d6caa3f6d4 --- /dev/null +++ b/horizon/utils/csvbase.py @@ -0,0 +1,144 @@ +# 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__) diff --git a/openstack_dashboard/dashboards/admin/overview/views.py b/openstack_dashboard/dashboards/admin/overview/views.py index ddf972e059..087c8f2174 100644 --- a/openstack_dashboard/dashboards/admin/overview/views.py +++ b/openstack_dashboard/dashboards/admin/overview/views.py @@ -24,13 +24,13 @@ from django.utils import translation from django.utils.translation import ugettext_lazy as _ from horizon import exceptions +from horizon.utils import csvbase from openstack_dashboard import api from openstack_dashboard import usage -from openstack_dashboard.usage import base -class GlobalUsageCsvRenderer(base.BaseCsvResponse): +class GlobalUsageCsvRenderer(csvbase.BaseCsvResponse): columns = [_("Project Name"), _("VCPUs"), _("Ram (MB)"), _("Disk (GB)"), _("Usage (Hours)")] diff --git a/openstack_dashboard/dashboards/project/overview/views.py b/openstack_dashboard/dashboards/project/overview/views.py index fc58eb110d..a2cf699c86 100644 --- a/openstack_dashboard/dashboards/project/overview/views.py +++ b/openstack_dashboard/dashboards/project/overview/views.py @@ -24,11 +24,12 @@ from django.template.defaultfilters import floatformat # noqa from django.utils.translation import ugettext_lazy as _ from django.views.generic import TemplateView # noqa +from horizon.utils import csvbase + from openstack_dashboard import usage -from openstack_dashboard.usage import base -class ProjectUsageCsvRenderer(base.BaseCsvResponse): +class ProjectUsageCsvRenderer(csvbase.BaseCsvResponse): columns = [_("Instance Name"), _("VCPUs"), _("Ram (MB)"), _("Disk (GB)"), _("Usage (Hours)"), diff --git a/openstack_dashboard/usage/base.py b/openstack_dashboard/usage/base.py index f3a36204ba..7d7604d14c 100644 --- a/openstack_dashboard/usage/base.py +++ b/openstack_dashboard/usage/base.py @@ -12,17 +12,10 @@ from __future__ import division -from csv import DictWriter # noqa -from csv import writer # noqa - import datetime -from django.http import HttpResponse # noqa -from django import template as django_template from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django import VERSION # noqa -import six from horizon import exceptions from horizon import forms @@ -282,125 +275,3 @@ class ProjectUsage(BaseUsage): instances.append(server_usage) usage.server_usages = instances return (usage,) - - -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 = six.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__)