Add "download row results as CSV" feature to master view

This commit is contained in:
Lance Edgar 2017-10-14 14:14:24 -07:00
parent f338a03c97
commit c95e2dbb06
5 changed files with 67 additions and 53 deletions

View file

@ -40,9 +40,6 @@
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if master.rows_downloadable and request.has_perm('{}.csv'.format(permission_prefix)):
<li>${h.link_to("Download row data as CSV", url('{}.csv'.format(route_prefix), uuid=batch.uuid))}</li>
% endif
% if master.cloneable and request.has_perm('{}.clone'.format(permission_prefix)):
<li>${h.link_to("Clone as new batch", url('{}.clone'.format(route_prefix), uuid=batch.uuid))}</li>
% endif

View file

@ -61,6 +61,9 @@
% if master.cloneable and request.has_perm('{}.clone'.format(permission_prefix)):
<li>${h.link_to("Clone this as new {}".format(model_title), url('{}.clone'.format(route_prefix), uuid=instance.uuid))}</li>
% endif
% if master.rows_downloadable_csv and request.has_perm('{}.row_results_csv'.format(permission_prefix)):
<li>${h.link_to("Download row results as CSV", url('{}.row_results_csv'.format(route_prefix), uuid=instance.uuid))}</li>
% endif
</%def>
<ul id="context-menu">

View file

@ -37,7 +37,6 @@ from sqlalchemy import orm
from rattail.db import model, Session as RattailSession
from rattail.threads import Thread
from rattail.csvutil import UnicodeDictWriter
from rattail.util import load_object, prettify
import formalchemy as fa
@ -64,7 +63,7 @@ class BatchMasterView(MasterView):
default_handler_spec = None
has_rows = True
rows_deletable = True
rows_downloadable = True
rows_downloadable_csv = True
refreshable = True
refresh_after_create = False
edit_with_rows = False
@ -905,47 +904,14 @@ class BatchMasterView(MasterView):
def get_execute_results_success_url(self, result, **kwargs):
return self.get_index_url()
def csv(self):
"""
Download batch data as CSV.
"""
batch = self.get_instance()
fields = self.get_csv_fields()
data = StringIO()
writer = UnicodeDictWriter(data, fields)
writer.writeheader()
for row in batch.data_rows:
if not row.removed:
writer.writerow(self.get_csv_row(row, 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=batch.csv'
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_row_class)
for prop in mapper.iterate_properties:
if isinstance(prop, orm.ColumnProperty):
if prop.key != 'removed' and not prop.key.endswith('uuid'):
fields.append(prop.key)
def get_row_csv_fields(self):
fields = super(BatchMasterView, self).get_row_csv_fields()
fields = [field for field in fields
if field != 'removed' and not field.endswith('uuid')]
return fields
def get_csv_row(self, row, fields):
"""
Return a dict for use when writing the row's data to CSV download.
"""
csvrow = {}
for field in fields:
value = getattr(row, field)
csvrow[field] = '' if value is None else unicode(value)
return csvrow
def get_row_results_csv_filename(self, batch):
return '{}.{}.csv'.format(self.get_route_prefix(), batch.id_str)
def clone(self):
"""
@ -1021,14 +987,6 @@ class BatchMasterView(MasterView):
config.add_tailbone_permission(permission_prefix, '{}.execute_multiple'.format(permission_prefix),
"Execute multiple {}".format(model_title_plural))
# download rows as CSV
if cls.rows_downloadable:
config.add_route('{}.csv'.format(route_prefix), '{}/{{uuid}}/csv'.format(url_prefix))
config.add_view(cls, attr='csv', route_name='{}.csv'.format(route_prefix),
permission='{}.csv'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{}.csv'.format(permission_prefix),
"Download {} rows as CSV".format(model_title))
class FileBatchMasterView(BatchMasterView):
"""

View file

@ -112,6 +112,7 @@ class MasterView(View):
rows_deletable_speedbump = False
rows_bulk_deletable = False
rows_default_pagesize = 20
rows_downloadable_csv = False
mobile_rows_creatable = False
mobile_rows_filterable = False
@ -1319,6 +1320,9 @@ class MasterView(View):
# default previously came from cls.get_normalized_model_name() but this is hopefully better
return cls.get_route_prefix()
def get_row_grid_key(self):
return '{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
def get_grid_actions(self):
main, more = self.get_main_actions(), self.get_more_actions()
if len(more) == 1:
@ -1472,6 +1476,29 @@ class MasterView(View):
response.content_disposition = b'attachment; filename={}.csv'.format(self.get_grid_key())
return response
def row_results_csv(self):
"""
Download current row results data for an object, as CSV
"""
obj = self.get_instance()
fields = self.get_row_csv_fields()
data = six.StringIO()
writer = UnicodeDictWriter(data, fields)
writer.writeheader()
for row in self.get_effective_row_data(sort=True):
writer.writerow(self.get_row_csv_row(row, fields))
response = self.request.response
response.body = data.getvalue()
data.close()
response.content_length = len(response.body)
response.content_type = b'text/csv'
filename = self.get_row_results_csv_filename(obj)
response.content_disposition = b'attachment; filename={}'.format(filename)
return response
def get_row_results_csv_filename(self, instance):
return '{}.csv'.format(self.get_row_grid_key())
def get_csv_fields(self):
"""
Return the list of fields to be written to CSV download.
@ -1483,6 +1510,17 @@ class MasterView(View):
fields.append(prop.key)
return fields
def get_row_csv_fields(self):
"""
Return the list of row fields to be written to CSV download.
"""
fields = []
mapper = orm.class_mapper(self.model_row_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.
@ -1493,6 +1531,16 @@ class MasterView(View):
csvrow[field] = '' if value is None else six.text_type(value)
return csvrow
def get_row_csv_row(self, row, fields):
"""
Return a dict for use when writing the row's data to CSV download.
"""
csvrow = {}
for field in fields:
value = getattr(row, field)
csvrow[field] = '' if value is None else six.text_type(value)
return csvrow
##############################
# CRUD Stuff
##############################
@ -2026,6 +2074,14 @@ class MasterView(View):
### sub-rows stuff follows
# download row results as CSV
if cls.has_rows and cls.rows_downloadable_csv:
config.add_tailbone_permission(permission_prefix, '{}.row_results_csv'.format(permission_prefix),
"Download {} results as CSV".format(row_model_title))
config.add_route('{}.row_results_csv'.format(route_prefix), '{}/{{uuid}}/rows-csv'.format(url_prefix))
config.add_view(cls, attr='row_results_csv', route_name='{}.row_results_csv'.format(route_prefix),
permission='{}.row_results_csv'.format(permission_prefix))
# create row
if cls.has_rows:
if cls.rows_creatable or cls.mobile_rows_creatable:

View file

@ -126,7 +126,7 @@ class MasterView2(MasterView):
if factory is None:
factory = self.get_row_grid_factory()
if key is None:
key = '{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
key = self.get_row_grid_key()
if data is None:
data = self.get_row_data(instance)
if columns is None: