From 6cfc72c8758be5ff78c82c1e9488af2cadea7941 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 5 Feb 2019 18:18:02 -0600 Subject: [PATCH] Add support for downloading batch rows as XLSX file --- tailbone/templates/master/view.mako | 3 ++ tailbone/views/batch/core.py | 4 ++ tailbone/views/master.py | 68 +++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/tailbone/templates/master/view.mako b/tailbone/templates/master/view.mako index 77e11c58..5e7e8c07 100644 --- a/tailbone/templates/master/view.mako +++ b/tailbone/templates/master/view.mako @@ -67,6 +67,9 @@ % if master.has_rows and master.rows_downloadable_csv and request.has_perm('{}.row_results_csv'.format(permission_prefix)):
  • ${h.link_to("Download row results as CSV", url('{}.row_results_csv'.format(route_prefix), uuid=instance.uuid))}
  • % endif + % if master.has_rows and master.rows_downloadable_xlsx and request.has_perm('{}.row_results_xlsx'.format(permission_prefix)): +
  • ${h.link_to("Download row results as XLSX", url('{}.row_results_xlsx'.format(route_prefix), uuid=instance.uuid))}
  • + % endif <%def name="object_helpers()"> diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py index b1b20f36..5c2df5f7 100644 --- a/tailbone/views/batch/core.py +++ b/tailbone/views/batch/core.py @@ -74,6 +74,7 @@ class BatchMasterView(MasterView): has_rows = True rows_deletable = True rows_downloadable_csv = True + rows_downloadable_xlsx = True refreshable = True refresh_after_create = False cloneable = False @@ -1333,6 +1334,9 @@ class BatchMasterView(MasterView): def get_row_results_csv_filename(self, batch): return '{}.{}.csv'.format(self.get_route_prefix(), batch.id_str) + def get_row_results_xlsx_filename(self, batch): + return '{}.{}.xlsx'.format(self.get_route_prefix(), batch.id_str) + def clone(self): """ Clone current batch as new batch diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 5eca8b63..c524da7a 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -45,6 +45,7 @@ from rattail.threads import Thread from rattail.csvutil import UnicodeDictWriter from rattail.files import temp_path from rattail.excel import ExcelWriter +from rattail.gpc import GPC import colander import deform @@ -141,6 +142,7 @@ class MasterView(View): rows_bulk_deletable = False rows_default_pagesize = 20 rows_downloadable_csv = False + rows_downloadable_xlsx = False mobile_rows_creatable = False mobile_rows_creatable_via_browse = False @@ -2354,6 +2356,64 @@ class MasterView(View): row[field] = getattr(obj, field, None) return row + def row_results_xlsx(self): + """ + Download current *row* results as XLSX. + """ + obj = self.get_instance() + results = self.get_effective_row_data(sort=True) + fields = self.get_row_xlsx_fields() + path = temp_path(suffix='.xlsx') + writer = ExcelWriter(path, fields, sheet_title=self.get_row_model_title_plural()) + writer.write_header() + + rows = [] + for row_obj in results: + data = self.get_row_xlsx_row(row_obj, fields) + row = [data[field] for field in fields] + rows.append(row) + + writer.write_rows(rows) + writer.auto_freeze() + writer.auto_filter() + writer.auto_resize() + writer.save() + + response = self.request.response + with open(path, 'rb') as f: + response.body = f.read() + os.remove(path) + + response.content_length = len(response.body) + response.content_type = str('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + filename = self.get_row_results_xlsx_filename(obj) + response.content_disposition = str('attachment; filename={}'.format(filename)) + return response + + def get_row_xlsx_fields(self): + """ + Return the list of row fields to be written to XLSX download. + """ + # TODO: should this be shared at all? in a better way? + return self.get_row_csv_fields() + + def get_row_xlsx_row(self, row, fields): + """ + Return a dict for use when writing the row's data to XLSX download. + """ + xlrow = {} + for field in fields: + value = getattr(row, field, None) + + if isinstance(value, GPC): + value = six.text_type(value) + + xlrow[field] = value + return xlrow + + def get_row_results_xlsx_filename(self, obj): + return '{}.xlsx'.format(self.get_row_grid_key()) + def row_results_csv(self): """ Download current row results data for an object, as CSV @@ -3265,6 +3325,14 @@ class MasterView(View): config.add_view(cls, attr='row_results_csv', route_name='{}.row_results_csv'.format(route_prefix), permission='{}.row_results_csv'.format(permission_prefix)) + # download row results as Excel + if cls.has_rows and cls.rows_downloadable_xlsx: + config.add_tailbone_permission(permission_prefix, '{}.row_results_xlsx'.format(permission_prefix), + "Download {} results as XLSX".format(row_model_title)) + config.add_route('{}.row_results_xlsx'.format(route_prefix), '{}/{{uuid}}/rows-xlsx'.format(url_prefix)) + config.add_view(cls, attr='row_results_xlsx', route_name='{}.row_results_xlsx'.format(route_prefix), + permission='{}.row_results_xlsx'.format(permission_prefix)) + # create row if cls.has_rows: if cls.rows_creatable or cls.mobile_rows_creatable: