Add "worksheet file" pattern for editing batches
lets user download a worksheet, edit, then upload back to update the batch
This commit is contained in:
parent
37a60592f6
commit
711ed947a3
5 changed files with 262 additions and 14 deletions
|
@ -87,6 +87,7 @@ class BatchMasterView(MasterView):
|
|||
mobile_filterable = True
|
||||
mobile_rows_viewable = True
|
||||
has_worksheet = False
|
||||
has_worksheet_file = False
|
||||
|
||||
grid_columns = [
|
||||
'id',
|
||||
|
@ -168,6 +169,10 @@ class BatchMasterView(MasterView):
|
|||
batch = kwargs['instance']
|
||||
kwargs['batch'] = batch
|
||||
kwargs['handler'] = self.handler
|
||||
|
||||
if self.has_worksheet_file and self.allow_worksheet(batch) and self.has_perm('worksheet'):
|
||||
kwargs['upload_worksheet_form'] = self.make_upload_worksheet_form(batch)
|
||||
|
||||
kwargs['execute_title'] = self.get_execute_title(batch)
|
||||
kwargs['execute_enabled'] = self.instance_executable(batch)
|
||||
if kwargs['mobile']:
|
||||
|
@ -181,6 +186,7 @@ class BatchMasterView(MasterView):
|
|||
kwargs['execute_form'] = self.make_execute_form(batch, action_url=url)
|
||||
else:
|
||||
kwargs['why_not_execute'] = self.handler.why_not_execute(batch)
|
||||
|
||||
kwargs['status_breakdown'] = self.make_status_breakdown(batch)
|
||||
if use_buefy:
|
||||
data = [{'title': title, 'count': count}
|
||||
|
@ -190,6 +196,71 @@ class BatchMasterView(MasterView):
|
|||
data, ['title', 'count'])
|
||||
return kwargs
|
||||
|
||||
def make_upload_worksheet_form(self, batch):
|
||||
action_url = self.get_action_url('upload_worksheet', batch)
|
||||
use_buefy = self.get_use_buefy()
|
||||
form = forms.Form(schema=UploadWorksheet(),
|
||||
request=self.request,
|
||||
action_url=action_url,
|
||||
use_buefy=use_buefy,
|
||||
component='upload-worksheet-form')
|
||||
form.set_type('worksheet_file', 'file')
|
||||
# TODO: must set these to avoid some default Buefy code
|
||||
form.auto_disable = False
|
||||
form.auto_disable_save = False
|
||||
return form
|
||||
|
||||
def download_worksheet(self):
|
||||
batch = self.get_instance()
|
||||
path = self.handler.write_worksheet(batch)
|
||||
root, ext = os.path.splitext(path)
|
||||
# we present a more descriptive filename for download
|
||||
filename = '{}.worksheet.{}{}'.format(batch.batch_key, batch.id_str, ext)
|
||||
return self.file_response(path, filename=filename)
|
||||
|
||||
def upload_worksheet(self):
|
||||
batch = self.get_instance()
|
||||
form = self.make_upload_worksheet_form(batch)
|
||||
if self.validate_form(form):
|
||||
uploads = self.normalize_uploads(form)
|
||||
path = uploads['worksheet_file']['temp_path']
|
||||
return self.handler_action(batch, 'update_from_worksheet', path=path)
|
||||
self.request.session.flash("Upload form did not validate!", 'error')
|
||||
return self.redirect(self.get_action_url('view', batch))
|
||||
|
||||
def update_from_worksheet_thread(self, batch_uuid, user_uuid, progress, path=None):
|
||||
"""
|
||||
Thread target for updating a batch from worksheet.
|
||||
"""
|
||||
session = self.make_isolated_session()
|
||||
batch = session.query(self.model_class).get(batch_uuid)
|
||||
try:
|
||||
self.handler.update_from_worksheet(batch, path, progress=progress)
|
||||
|
||||
except Exception as error:
|
||||
session.rollback()
|
||||
log.exception("upload/update failed for '{}' batch: {}".format(self.batch_key, batch))
|
||||
session.close()
|
||||
if progress:
|
||||
progress.session.load()
|
||||
progress.session['error'] = True
|
||||
progress.session['error_msg'] = "Upload processing failed: {}".format(
|
||||
simple_error(error))
|
||||
progress.session.save()
|
||||
|
||||
else:
|
||||
session.commit()
|
||||
success_msg = "Batch has been updated: {}".format(batch)
|
||||
success_url = self.get_action_url('view', batch)
|
||||
session.close()
|
||||
|
||||
if progress:
|
||||
progress.session.load()
|
||||
progress.session['complete'] = True
|
||||
progress.session['success_msg'] = success_msg
|
||||
progress.session['success_url'] = success_url
|
||||
progress.session.save()
|
||||
|
||||
def make_status_breakdown(self, batch, rows=None, status_enum=None):
|
||||
"""
|
||||
Returns a simple list of 2-tuples, each of which has the status display
|
||||
|
@ -1375,11 +1446,11 @@ class BatchMasterView(MasterView):
|
|||
|
||||
# If no error, check result flag (false means user canceled).
|
||||
else:
|
||||
success_msg = None
|
||||
if result:
|
||||
session.commit()
|
||||
# TODO: this doesn't always work...?
|
||||
self.request.session.flash("{} has been executed: {}".format(
|
||||
self.get_model_title(), batch.id_str))
|
||||
success_msg = "{} has been executed: {}".format(
|
||||
self.get_model_title(), batch.id_str)
|
||||
else:
|
||||
session.rollback()
|
||||
|
||||
|
@ -1391,6 +1462,8 @@ class BatchMasterView(MasterView):
|
|||
progress.session.load()
|
||||
progress.session['complete'] = True
|
||||
progress.session['success_url'] = success_url
|
||||
if success_msg:
|
||||
progress.session['success_msg'] = success_msg
|
||||
progress.session.save()
|
||||
|
||||
def get_execute_success_url(self, batch, result, **kwargs):
|
||||
|
@ -1499,6 +1572,7 @@ class BatchMasterView(MasterView):
|
|||
model_key = cls.get_model_key()
|
||||
route_prefix = cls.get_route_prefix()
|
||||
url_prefix = cls.get_url_prefix()
|
||||
instance_url_prefix = cls.get_instance_url_prefix()
|
||||
permission_prefix = cls.get_permission_prefix()
|
||||
model_title = cls.get_model_title()
|
||||
model_title_plural = cls.get_model_title_plural()
|
||||
|
@ -1514,9 +1588,10 @@ class BatchMasterView(MasterView):
|
|||
permission='{}.create'.format(permission_prefix))
|
||||
|
||||
# worksheet
|
||||
if cls.has_worksheet:
|
||||
if cls.has_worksheet or cls.has_worksheet_file:
|
||||
config.add_tailbone_permission(permission_prefix, '{}.worksheet'.format(permission_prefix),
|
||||
"Edit {} data as worksheet".format(model_title))
|
||||
if cls.has_worksheet:
|
||||
config.add_route('{}.worksheet'.format(route_prefix), '{}/{{{}}}/worksheet'.format(url_prefix, model_key))
|
||||
config.add_view(cls, attr='worksheet', route_name='{}.worksheet'.format(route_prefix),
|
||||
permission='{}.worksheet'.format(permission_prefix))
|
||||
|
@ -1525,6 +1600,20 @@ class BatchMasterView(MasterView):
|
|||
config.add_view(cls, attr='worksheet_update', route_name='{}.worksheet_update'.format(route_prefix),
|
||||
renderer='json', permission='{}.worksheet'.format(permission_prefix))
|
||||
|
||||
# worksheet file
|
||||
if cls.has_worksheet_file:
|
||||
|
||||
# download worksheet
|
||||
config.add_route('{}.download_worksheet'.format(route_prefix), '{}/download-worksheet'.format(instance_url_prefix))
|
||||
config.add_view(cls, attr='download_worksheet', route_name='{}.download_worksheet'.format(route_prefix),
|
||||
permission='{}.worksheet'.format(permission_prefix))
|
||||
|
||||
# upload worksheet
|
||||
config.add_route('{}.upload_worksheet'.format(route_prefix), '{}/upload-worksheet'.format(instance_url_prefix),
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='upload_worksheet', route_name='{}.upload_worksheet'.format(route_prefix),
|
||||
permission='{}.worksheet'.format(permission_prefix))
|
||||
|
||||
# refresh batch data
|
||||
if cls.refreshable:
|
||||
config.add_route('{}.refresh'.format(route_prefix), '{}/{{uuid}}/refresh'.format(url_prefix))
|
||||
|
@ -1611,6 +1700,12 @@ class FileBatchMasterView(BatchMasterView):
|
|||
f.set_renderer('filename', self.render_downloadable_file)
|
||||
|
||||
|
||||
class UploadWorksheet(colander.Schema):
|
||||
|
||||
# this node is actually "replaced" when form is configured
|
||||
worksheet_file = colander.SchemaNode(colander.String())
|
||||
|
||||
|
||||
class ToggleComplete(colander.MappingSchema):
|
||||
|
||||
complete = colander.SchemaNode(colander.Boolean())
|
||||
|
|
|
@ -144,7 +144,7 @@ class View(object):
|
|||
return render_to_response('json', data,
|
||||
request=self.request)
|
||||
|
||||
def file_response(self, path):
|
||||
def file_response(self, path, filename=None):
|
||||
"""
|
||||
Returns a generic FileResponse from the given path
|
||||
"""
|
||||
|
@ -152,9 +152,10 @@ class View(object):
|
|||
return self.notfound()
|
||||
response = FileResponse(path, request=self.request)
|
||||
response.content_length = os.path.getsize(path)
|
||||
filename = os.path.basename(path)
|
||||
if six.PY2:
|
||||
filename = filename.encode('ascii', 'replace')
|
||||
if not filename:
|
||||
filename = os.path.basename(path)
|
||||
if six.PY2:
|
||||
filename = filename.encode('ascii', 'replace')
|
||||
response.content_disposition = str('attachment; filename="{}"'.format(filename))
|
||||
return response
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue