From 9ff6df83e5ab1ed28072db454171fc2735761f6f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 14 Sep 2017 21:57:37 -0500 Subject: [PATCH] Add generic support for downloading list results as CSV --- tailbone/templates/master/index.mako | 3 ++ tailbone/views/master.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako index f46f2cee..bc219b00 100644 --- a/tailbone/templates/master/index.mako +++ b/tailbone/templates/master/index.mako @@ -69,6 +69,9 @@ <%def name="context_menu_items()"> + % 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.creatable and request.has_perm('{}.create'.format(permission_prefix)):
  • ${h.link_to("Create a new {}".format(model_title), url('{}.create'.format(route_prefix)))}
  • % endif diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 3f817d4e..b22f9ee4 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -40,6 +40,7 @@ from rattail.db.continuum import model_transaction_query from rattail.util import prettify from rattail.time import localtime #, make_utc from rattail.threads import Thread +from rattail.csvutil import UnicodeDictWriter import formalchemy as fa from pyramid import httpexceptions @@ -64,6 +65,7 @@ class MasterView(View): checkboxes = False listable = True + results_downloadable_csv = False creatable = True viewable = True editable = True @@ -1384,6 +1386,46 @@ class MasterView(View): """ return False + def results_csv(self): + """ + Download current list results as CSV + """ + results = self.get_effective_data() + fields = self.get_csv_fields() + data = six.StringIO() + writer = UnicodeDictWriter(data, fields) + writer.writeheader() + for obj in results: + writer.writerow(self.get_csv_row(obj, fields)) + response = self.request.response + response.text = data.getvalue().decode('utf_8') + data.close() + response.content_length = len(response.text) + response.content_type = b'text/csv' + response.content_disposition = b'attachment; filename={}.csv'.format(self.get_grid_key()) + return response + + def get_csv_fields(self): + """ + Return the list of fields to be written to CSV 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_csv_row(self, obj, fields): + """ + Return a dict for use when writing the row's data to CSV download. + """ + csvrow = {} + for field in fields: + value = getattr(obj, field) + csvrow[field] = '' if value is None else six.text_type(value) + return csvrow + ############################## # CRUD Stuff ############################## @@ -1799,6 +1841,14 @@ class MasterView(View): config.add_view(cls, attr='mobile_index', route_name='mobile.{}'.format(route_prefix), permission='{}.list'.format(permission_prefix)) + if cls.results_downloadable_csv: + config.add_tailbone_permission(permission_prefix, '{}.results_csv'.format(permission_prefix), + "Download {} as CSV".format(model_title_plural)) + config.add_route('{}.results_csv'.format(route_prefix), '{}/csv'.format(url_prefix)) + config.add_view(cls, attr='results_csv', route_name='{}.results_csv'.format(route_prefix), + permission='{}.results_csv'.format(permission_prefix)) + + # create if cls.creatable or cls.mobile_creatable: config.add_tailbone_permission(permission_prefix, '{}.create'.format(permission_prefix),