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):