diff --git a/tailbone/templates/batch/handheld/index.mako b/tailbone/templates/batch/handheld/index.mako new file mode 100644 index 00000000..647125a8 --- /dev/null +++ b/tailbone/templates/batch/handheld/index.mako @@ -0,0 +1,60 @@ +## -*- coding: utf-8; -*- +<%inherit file="/newbatch/index.mako" /> + +<%def name="extra_javascript()"> + ${parent.extra_javascript()} + +%def> + +<%def name="grid_tools()"> + +%def> + +${parent.body()} + +
diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py index e16563f1..4b197849 100644 --- a/tailbone/views/batch/core.py +++ b/tailbone/views/batch/core.py @@ -34,6 +34,7 @@ import datetime import logging from cStringIO import StringIO +import six from sqlalchemy import orm from rattail.db import model, Session as RattailSession @@ -110,7 +111,15 @@ class BatchMasterView(MasterView): kwargs['rendered_execution_options'] = self.render_execution_options(batch) return kwargs - def render_execution_options(self, batch): + def template_kwargs_index(self, **kwargs): + kwargs['execute_enabled'] = self.executable() + if kwargs['execute_enabled'] and self.has_execution_options: + kwargs['rendered_execution_options'] = self.render_execution_options() + return kwargs + + def render_execution_options(self, batch=None): + if batch is None: + batch = self.model_class form = self.make_execution_options_form(batch) kwargs = { 'batch': batch, @@ -434,7 +443,7 @@ class BatchMasterView(MasterView): def editable_instance(self, batch): return not bool(batch.executed) - def executable(self, batch): + def executable(self, batch=None): return self.handler.executable(batch) def batch_refreshable(self, batch): @@ -460,10 +469,12 @@ class BatchMasterView(MasterView): # TODO execution_options_schema = None - def make_execution_options_form(self, batch): + def make_execution_options_form(self, batch=None): """ Return a proper Form for execution options. """ + if batch is None: + batch = self.model_class defaults = {} for field in self.execution_options_schema.fields: key = 'batch.{}.execute_option.{}'.format(batch.batch_key, field) @@ -819,6 +830,82 @@ class BatchMasterView(MasterView): def get_execute_success_url(self, batch, result, **kwargs): return self.get_action_url('view', batch) + def execute_results(self): + """ + Execute all batches which are returned from the current index query. + Starts a separate thread for the execution, and displays a progress + indicator page. + """ + if self.request.method == 'POST': + + kwargs = {} + if self.has_execution_options: + form = self.make_execution_options_form() + if not form.validate(): + raise RuntimeError("Execution options form did not validate") + kwargs.update(form.data) + + # cache options to use as defaults next time + for key, value in form.data.iteritems(): + self.request.session['batch.{}.execute_option.{}'.format(self.model_class.batch_key, key)] = six.text_type(value) + + key = '{}.execute_results'.format(self.model_class.__tablename__) + batches = self.get_effective_data() + kwargs['progress'] = SessionProgress(self.request, key) + thread = Thread(target=self.execute_results_thread, args=(batches, self.request.user.uuid), kwargs=kwargs) + thread.start() + + return self.render_progress({ + 'key': key, + 'cancel_url': self.get_index_url(), + 'cancel_msg': "Batch execution was canceled", + }) + + self.request.session.flash("Sorry, you must POST to execute batches", 'error') + return self.redirect(self.get_index_url()) + + def execute_results_thread(self, batches, user_uuid, progress=None, **kwargs): + """ + Thread target for executing multiple batches with progress indicator. + """ + session = RattailSession() + batches = batches.with_session(session).all() + user = session.query(model.User).get(user_uuid) + try: + result = self.handler.execute_many(batches, user=user, progress=progress, **kwargs) + + # If anything goes wrong, rollback and log the error etc. + except Exception as error: + session.rollback() + log.exception("execution failed for batch: {}".format(batch)) + session.close() + if progress: + progress.session.load() + progress.session['error'] = True + progress.session['error_msg'] = self.execute_error_message(error) + progress.session.save() + + # If no error, check result flag (false means user canceled). + else: + if result: + now = datetime.datetime.utcnow() + for batch in batches: + batch.executed = now + batch.executed_by = user + session.commit() + # TODO: this doesn't always work...? + self.request.session.flash("{} {} were executed".format( + len(batches), self.get_model_title_plural())) + else: + session.rollback() + session.close() + + if progress: + progress.session.load() + progress.session['complete'] = True + progress.session['success_url'] = self.get_index_url() + progress.session.save() + def csv(self): """ Download batch data as CSV. @@ -913,6 +1000,13 @@ class BatchMasterView(MasterView): config.add_tailbone_permission(permission_prefix, '{}.execute'.format(permission_prefix), "Execute {}".format(model_title)) + # execute (multiple) batch results + config.add_route('{}.execute_results'.format(route_prefix), '{}/execute-results'.format(url_prefix)) + config.add_view(cls, attr='execute_results', route_name='{}.execute_results'.format(route_prefix), + permission='{}.execute_multiple'.format(permission_prefix)) + 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))