Add "download row results as CSV" feature to master view
This commit is contained in:
		
							parent
							
								
									f338a03c97
								
							
						
					
					
						commit
						c95e2dbb06
					
				
					 5 changed files with 67 additions and 53 deletions
				
			
		|  | @ -40,9 +40,6 @@ | ||||||
| 
 | 
 | ||||||
| <%def name="context_menu_items()"> | <%def name="context_menu_items()"> | ||||||
|   ${parent.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)): |   % 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> |       <li>${h.link_to("Clone as new batch", url('{}.clone'.format(route_prefix), uuid=batch.uuid))}</li> | ||||||
|   % endif |   % endif | ||||||
|  |  | ||||||
|  | @ -61,6 +61,9 @@ | ||||||
|   % if master.cloneable and request.has_perm('{}.clone'.format(permission_prefix)): |   % 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> |       <li>${h.link_to("Clone this as new {}".format(model_title), url('{}.clone'.format(route_prefix), uuid=instance.uuid))}</li> | ||||||
|   % endif |   % 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> | </%def> | ||||||
| 
 | 
 | ||||||
| <ul id="context-menu"> | <ul id="context-menu"> | ||||||
|  |  | ||||||
|  | @ -37,7 +37,6 @@ from sqlalchemy import orm | ||||||
| 
 | 
 | ||||||
| from rattail.db import model, Session as RattailSession | from rattail.db import model, Session as RattailSession | ||||||
| from rattail.threads import Thread | from rattail.threads import Thread | ||||||
| from rattail.csvutil import UnicodeDictWriter |  | ||||||
| from rattail.util import load_object, prettify | from rattail.util import load_object, prettify | ||||||
| 
 | 
 | ||||||
| import formalchemy as fa | import formalchemy as fa | ||||||
|  | @ -64,7 +63,7 @@ class BatchMasterView(MasterView): | ||||||
|     default_handler_spec = None |     default_handler_spec = None | ||||||
|     has_rows = True |     has_rows = True | ||||||
|     rows_deletable = True |     rows_deletable = True | ||||||
|     rows_downloadable = True |     rows_downloadable_csv = True | ||||||
|     refreshable = True |     refreshable = True | ||||||
|     refresh_after_create = False |     refresh_after_create = False | ||||||
|     edit_with_rows = False |     edit_with_rows = False | ||||||
|  | @ -905,47 +904,14 @@ class BatchMasterView(MasterView): | ||||||
|     def get_execute_results_success_url(self, result, **kwargs): |     def get_execute_results_success_url(self, result, **kwargs): | ||||||
|         return self.get_index_url() |         return self.get_index_url() | ||||||
| 
 | 
 | ||||||
|     def csv(self): |     def get_row_csv_fields(self): | ||||||
|         """ |         fields = super(BatchMasterView, self).get_row_csv_fields() | ||||||
|         Download batch data as CSV. |         fields = [field for field in fields | ||||||
|         """ |                   if field != 'removed' and not field.endswith('uuid')] | ||||||
|         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) |  | ||||||
|         return fields |         return fields | ||||||
| 
 | 
 | ||||||
|     def get_csv_row(self, row, fields): |     def get_row_results_csv_filename(self, batch): | ||||||
|         """ |         return '{}.{}.csv'.format(self.get_route_prefix(), batch.id_str) | ||||||
|         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 clone(self): |     def clone(self): | ||||||
|         """ |         """ | ||||||
|  | @ -1021,14 +987,6 @@ class BatchMasterView(MasterView): | ||||||
|         config.add_tailbone_permission(permission_prefix, '{}.execute_multiple'.format(permission_prefix), |         config.add_tailbone_permission(permission_prefix, '{}.execute_multiple'.format(permission_prefix), | ||||||
|                                        "Execute multiple {}".format(model_title_plural)) |                                        "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): | class FileBatchMasterView(BatchMasterView): | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|  | @ -112,6 +112,7 @@ class MasterView(View): | ||||||
|     rows_deletable_speedbump = False |     rows_deletable_speedbump = False | ||||||
|     rows_bulk_deletable = False |     rows_bulk_deletable = False | ||||||
|     rows_default_pagesize = 20 |     rows_default_pagesize = 20 | ||||||
|  |     rows_downloadable_csv = False | ||||||
| 
 | 
 | ||||||
|     mobile_rows_creatable = False |     mobile_rows_creatable = False | ||||||
|     mobile_rows_filterable = 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 |         # default previously came from cls.get_normalized_model_name() but this is hopefully better | ||||||
|         return cls.get_route_prefix() |         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): |     def get_grid_actions(self): | ||||||
|         main, more = self.get_main_actions(), self.get_more_actions() |         main, more = self.get_main_actions(), self.get_more_actions() | ||||||
|         if len(more) == 1: |         if len(more) == 1: | ||||||
|  | @ -1472,6 +1476,29 @@ class MasterView(View): | ||||||
|         response.content_disposition = b'attachment; filename={}.csv'.format(self.get_grid_key()) |         response.content_disposition = b'attachment; filename={}.csv'.format(self.get_grid_key()) | ||||||
|         return response |         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): |     def get_csv_fields(self): | ||||||
|         """ |         """ | ||||||
|         Return the list of fields to be written to CSV download. |         Return the list of fields to be written to CSV download. | ||||||
|  | @ -1483,6 +1510,17 @@ class MasterView(View): | ||||||
|                 fields.append(prop.key) |                 fields.append(prop.key) | ||||||
|         return fields |         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): |     def get_csv_row(self, obj, fields): | ||||||
|         """ |         """ | ||||||
|         Return a dict for use when writing the row's data to CSV download. |         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) |             csvrow[field] = '' if value is None else six.text_type(value) | ||||||
|         return csvrow |         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 |     # CRUD Stuff | ||||||
|     ############################## |     ############################## | ||||||
|  | @ -2026,6 +2074,14 @@ class MasterView(View): | ||||||
| 
 | 
 | ||||||
|         ### sub-rows stuff follows |         ### 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 |         # create row | ||||||
|         if cls.has_rows: |         if cls.has_rows: | ||||||
|             if cls.rows_creatable or cls.mobile_rows_creatable: |             if cls.rows_creatable or cls.mobile_rows_creatable: | ||||||
|  |  | ||||||
|  | @ -126,7 +126,7 @@ class MasterView2(MasterView): | ||||||
|         if factory is None: |         if factory is None: | ||||||
|             factory = self.get_row_grid_factory() |             factory = self.get_row_grid_factory() | ||||||
|         if key is None: |         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: |         if data is None: | ||||||
|             data = self.get_row_data(instance) |             data = self.get_row_data(instance) | ||||||
|         if columns is None: |         if columns is None: | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar