diff --git a/tailbone/templates/batch/crud.mako b/tailbone/templates/batch/crud.mako index b4f1896d..ecc61021 100644 --- a/tailbone/templates/batch/crud.mako +++ b/tailbone/templates/batch/crud.mako @@ -42,21 +42,25 @@ +<%def name="context_menu_items()"> +
  • ${h.link_to("Back to {0}".format(batch_display_plural), url(route_prefix))}
  • + % if not batch.executed: + % if form.updating: +
  • ${h.link_to("View this {0}".format(batch_display), url('{0}.view'.format(route_prefix), uuid=batch.uuid))}
  • + % endif + % if form.readonly and request.has_perm('{0}.edit'.format(permission_prefix)): +
  • ${h.link_to("Edit this {0}".format(batch_display), url('{0}.edit'.format(route_prefix), uuid=batch.uuid))}
  • + % endif + % endif + % if request.has_perm('{0}.delete'.format(permission_prefix)): +
  • ${h.link_to("Delete this {0}".format(batch_display), url('{0}.delete'.format(route_prefix), uuid=batch.uuid))}
  • + % endif + +
    ${form.render(form_id='batch-form', buttons=capture(buttons))|n} diff --git a/tailbone/templates/batch/view.mako b/tailbone/templates/batch/view.mako index dd8ae649..74cb10d3 100644 --- a/tailbone/templates/batch/view.mako +++ b/tailbone/templates/batch/view.mako @@ -13,6 +13,13 @@ +<%def name="context_menu_items()"> + ${parent.context_menu_items()} + % if request.has_perm('{0}.csv'.format(permission_prefix)): +
  • ${h.link_to("Download this {0} as CSV".format(batch_display), url('{0}.csv'.format(route_prefix), uuid=batch.uuid))}
  • + % endif + + <%def name="buttons()">
    % if not form.readonly and batch.refreshable: diff --git a/tailbone/views/batch.py b/tailbone/views/batch.py index b824bece..68715341 100644 --- a/tailbone/views/batch.py +++ b/tailbone/views/batch.py @@ -32,6 +32,9 @@ from __future__ import unicode_literals import os import datetime import logging +from cStringIO import StringIO + +from sqlalchemy import orm import formalchemy from pyramid.renderers import render_to_response @@ -42,6 +45,7 @@ from webhelpers.html.tags import link_to, HTML from rattail.db import model from rattail.db import Session as RatSession from rattail.threads import Thread +from rattail.csvutil import UnicodeDictWriter from tailbone.db import Session from tailbone.views import SearchableAlchemyGridView, CrudView @@ -366,6 +370,10 @@ class BatchCrud(BaseCrud): def batch_class(self): raise NotImplementedError + @property + def batch_row_class(self): + raise NotImplementedError + @property def mapped_class(self): return self.batch_class @@ -705,6 +713,48 @@ class BatchCrud(BaseCrud): progress.session['success_url'] = self.view_url(batch.uuid) progress.session.save() + def csv(self): + """ + Download batch data as CSV. + """ + batch = self.current_batch() + 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.batch_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) + 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 + class FileBatchCrud(BatchCrud): """ @@ -1163,6 +1213,11 @@ def defaults(config, batch_grid, batch_crud, row_grid, row_crud, url_prefix, config.add_view(batch_crud, attr='execute', route_name='{0}.execute'.format(route_prefix), permission='{0}.execute'.format(permission_prefix)) + # Download batch row data as CSV + config.add_route('{0}.csv'.format(route_prefix), '{0}{{uuid}}/csv'.format(url_prefix)) + config.add_view(batch_crud, attr='csv', route_name='{0}.csv'.format(route_prefix), + permission='{0}.csv'.format(permission_prefix)) + # Download batch data file if hasattr(batch_crud, 'download'): config.add_route('{0}.download'.format(route_prefix), '{0}{{uuid}}/download'.format(url_prefix))