diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako index 014e1f97..21070505 100644 --- a/tailbone/templates/master/index.mako +++ b/tailbone/templates/master/index.mako @@ -72,6 +72,9 @@ % if master.results_downloadable_csv and request.has_perm('{}.results_csv'.format(permission_prefix)):
  • ${h.link_to("Download results as CSV", url('{}.results_csv'.format(route_prefix)))}
  • % endif + % if master.results_downloadable_xlsx and request.has_perm('{}.results_xlsx'.format(permission_prefix)): +
  • ${h.link_to("Download results as XLSX", url('{}.results_xlsx'.format(route_prefix)))}
  • + % endif % if master.creatable and request.has_perm('{}.create'.format(permission_prefix)): % if master.creates_multiple:
  • ${h.link_to("Create new {}".format(model_title_plural), url('{}.create'.format(route_prefix)))}
  • diff --git a/tailbone/views/master.py b/tailbone/views/master.py index a21188df..bb320612 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2017 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -41,6 +41,8 @@ from rattail.util import prettify from rattail.time import localtime #, make_utc from rattail.threads import Thread from rattail.csvutil import UnicodeDictWriter +from rattail.files import temp_path +from rattail.excel import ExcelWriter import formalchemy as fa from pyramid import httpexceptions @@ -66,6 +68,7 @@ class MasterView(View): listable = True results_downloadable_csv = False + results_downloadable_xlsx = False creatable = True viewable = True editable = True @@ -1472,6 +1475,57 @@ class MasterView(View): response.content_disposition = b'attachment; filename={}.csv'.format(self.get_grid_key()) return response + def results_xlsx(self): + """ + Download current list results as XLSX. + """ + results = self.get_effective_data() + fields = self.get_xlsx_fields() + path = temp_path(suffix='.xlsx') + writer = ExcelWriter(path, fields, sheet_title=self.get_model_title_plural()) + writer.write_header() + + rows = [] + for obj in results: + data = self.get_xlsx_row(obj, fields) + row = [data[field] for field in fields] + rows.append(row) + + writer.write_rows(rows) + writer.auto_freeze() + writer.auto_filter() + 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 = b'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + response.content_disposition = b'attachment; filename={}.xlsx'.format(self.get_grid_key()) + return response + + def get_xlsx_fields(self): + """ + Return the list of fields to be written to XLSX download. + """ + fields = [] + mapper = orm.class_mapper(self.model_class) + for prop in mapper.iterate_properties: + if isinstance(prop, orm.ColumnProperty): + fields.append(prop.key) + return fields + + def get_xlsx_row(self, obj, fields): + """ + Return a dict for use when writing the row's data to CSV download. + """ + row = {} + for field in fields: + row[field] = getattr(obj, field, None) + return row + def row_results_csv(self): """ Download current row results data for an object, as CSV @@ -1965,6 +2019,13 @@ class MasterView(View): config.add_view(cls, attr='results_csv', route_name='{}.results_csv'.format(route_prefix), permission='{}.results_csv'.format(permission_prefix)) + if cls.results_downloadable_xlsx: + config.add_tailbone_permission(permission_prefix, '{}.results_xlsx'.format(permission_prefix), + "Download {} as XLSX".format(model_title_plural)) + config.add_route('{}.results_xlsx'.format(route_prefix), '{}/xlsx'.format(url_prefix)) + config.add_view(cls, attr='results_xlsx', route_name='{}.results_xlsx'.format(route_prefix), + permission='{}.results_xlsx'.format(permission_prefix)) + # create if cls.creatable or cls.mobile_creatable: