From 16be06821aa70d522fa03d17c6cab07acb7d4358 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 12 Feb 2015 21:35:28 -0600 Subject: [PATCH] Wrap up initial vendor catalog batch support etc. * Adds the ability to delete all batch rows matching current query. * Refactors some progress factory args. * If batch initialization fails, don't persist batch. --- tailbone/templates/batch/rows.mako | 5 +++ tailbone/views/batch.py | 58 +++++++++++++++++++++++++----- tailbone/views/grids/alchemy.py | 2 ++ tailbone/views/vendors/catalogs.py | 9 ++++- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/tailbone/templates/batch/rows.mako b/tailbone/templates/batch/rows.mako index 756606e6..c7d04e57 100644 --- a/tailbone/templates/batch/rows.mako +++ b/tailbone/templates/batch/rows.mako @@ -7,6 +7,11 @@ ${search.render()} + + +

${h.link_to("Delete all rows matching current search", url('{0}.rows.bulk_delete'.format(route_prefix), uuid=batch.uuid))}

+ + ${grid} diff --git a/tailbone/views/batch.py b/tailbone/views/batch.py index 57bcc404..7329d3bf 100644 --- a/tailbone/views/batch.py +++ b/tailbone/views/batch.py @@ -473,11 +473,11 @@ class BatchCrud(BaseCrud): } return render_to_response('/progress.mako', kwargs, request=self.request) - def refresh_data(self, session, batch, progress_factory=None): + def refresh_data(self, session, batch, progress=None): """ Instruct the batch handler to refresh all data for the batch. """ - self.handler.refresh_data(session, batch, progress_factory=progress_factory) + self.handler.refresh_data(session, batch, progress=progress) batch.cognized = datetime.datetime.utcnow() batch.cognized_by = self.request.user @@ -490,7 +490,7 @@ class BatchCrud(BaseCrud): # transaction binding etc. session = RatSession() batch = session.query(self.batch_class).get(batch_uuid) - self.refresh_data(session, batch, progress_factory=progress) + self.refresh_data(session, batch, progress=progress) session.commit() session.refresh(batch) session.close() @@ -591,7 +591,9 @@ class FileBatchCrud(BatchCrud): def save_form(self, form): """ - Save the uploaded data file if necessary, etc. + Save the uploaded data file if necessary, etc. If batch initialization + fails, don't persist the batch at all; the user will be sent back to + the "create batch" page in that case. """ # Transfer form data to batch instance. form.fieldset.sync() @@ -603,9 +605,10 @@ class FileBatchCrud(BatchCrud): batch.filename = form.fieldset.data_file.renderer._filename # Expunge batch from session to prevent it from being flushed. Session.expunge(batch) - self.init_batch(batch) - Session.add(batch) - batch.write_file(self.request.rattail_config, form.fieldset.data_file.value) + self.batch_inited = self.init_batch(batch) + if self.batch_inited: + Session.add(batch) + batch.write_file(self.request.rattail_config, form.fieldset.data_file.value) def init_batch(self, batch): """ @@ -613,7 +616,24 @@ class FileBatchCrud(BatchCrud): effectively provide default values for a batch, etc. This method is invoked after a batch has been fully prepared for insertion to the database, but before the push to the database occurs. + + Note that the return value of this function matters; if it is boolean + false then the batch will not be persisted at all, and the user will be + redirected to the "create batch" page. """ + return True + + def post_save(self, form): + """ + This checks for failed batch initialization when creating a new batch. + If a failure is detected, the user is redirected to the page for + creating new batches. The assumption here is that the + :meth:`init_batch()` method responsible for indicating the failure will + have set a flash message for the user with more info. + """ + if self.creating and not self.batch_inited: + return HTTPFound(location=self.request.route_url( + '{0}.create'.format(self.route_prefix))) def post_save_url(self, form): """ @@ -632,7 +652,8 @@ class FileBatchCrud(BatchCrud): class BatchRowGrid(BaseGrid): """ - Base grid view for batch rows, which can be filtered and sorted. + Base grid view for batch rows, which can be filtered and sorted. Also it + can delete all rows matching the current list view query. """ @property @@ -734,6 +755,22 @@ class BatchRowGrid(BaseGrid): def tr_class(self, row, i): pass + def render_kwargs(self): + """ + Add the current batch and route prefix to the template context. + """ + return {'batch': self.current_batch(), + 'route_prefix': self.route_prefix} + + def bulk_delete(self): + """ + Delete all rows matching the current row grid view query. + """ + self.query().delete() + return HTTPFound(location=self.request.route_url( + '{0}.view'.format(self.route_prefix), + uuid=self.request.matchdict['uuid'])) + class ProductBatchRowGrid(BatchRowGrid): """ @@ -851,6 +888,11 @@ def defaults(config, batch_grid, batch_crud, row_grid, row_crud, url_prefix, renderer='/batch/rows.mako', permission='{0}.view'.format(permission_prefix)) + # Bulk delete batch rows + config.add_route('{0}.rows.bulk_delete'.format(route_prefix), '{0}{{uuid}}/rows/delete'.format(url_prefix)) + config.add_view(row_grid, attr='bulk_delete', route_name='{0}.rows.bulk_delete'.format(route_prefix), + permission='{0}.delete'.format(permission_prefix)) + # Delete batch row config.add_route('{0}.rows.delete'.format(route_prefix), '{0}delete-row/{{uuid}}'.format(url_prefix)) config.add_view(row_crud, attr='delete', route_name='{0}.rows.delete'.format(route_prefix), diff --git a/tailbone/views/grids/alchemy.py b/tailbone/views/grids/alchemy.py index ffb4ed48..389f1332 100644 --- a/tailbone/views/grids/alchemy.py +++ b/tailbone/views/grids/alchemy.py @@ -169,6 +169,8 @@ class SearchableAlchemyGridView(PagedAlchemyGridView): def modify_query(self, query): join_map = self.join_map() + if not hasattr(self, '_filter_config'): + self._filter_config = self.filter_config() query = grids.search.filter_query( query, self._filter_config, self.filter_map(), join_map) if hasattr(self, '_sort_config'): diff --git a/tailbone/views/vendors/catalogs.py b/tailbone/views/vendors/catalogs.py index e790ee68..e31e7572 100644 --- a/tailbone/views/vendors/catalogs.py +++ b/tailbone/views/vendors/catalogs.py @@ -26,6 +26,8 @@ Views for maintaining vendor catalogs from __future__ import unicode_literals +from pyramid.httpexceptions import HTTPFound + from rattail.db import model from rattail.db.api import get_setting, get_vendor from rattail.db.batch.vendorcatalog import VendorCatalog, VendorCatalogRow @@ -129,7 +131,12 @@ class VendorCatalogCrud(FileBatchCrud): def init_batch(self, batch): parser = require_catalog_parser(batch.parser_key) - batch.vendor = get_vendor(Session, parser.vendor_key) + vendor = get_vendor(Session, parser.vendor_key) + if not vendor: + self.request.session.flash("No vendor setting found in database for key: {0}".format(parser.vendor_key)) + return False + batch.vendor = vendor + return True class VendorCatalogRowGrid(BatchRowGrid):