Add progress for generating "results as XLSX" file to download

This commit is contained in:
Lance Edgar 2020-08-21 17:42:01 -05:00
parent 32b98ae818
commit 7d8c57170f
3 changed files with 126 additions and 25 deletions

View file

@ -295,6 +295,13 @@
${parent.modify_this_page_vars()} ${parent.modify_this_page_vars()}
<script type="text/javascript"> <script type="text/javascript">
## TODO: stop checking for buefy here once we only have the one session.pop()
% if use_buefy and request.session.pop('{}.results_xlsx.generated'.format(route_prefix), False):
ThisPage.mounted = function() {
location.href = '${url('{}.results_xlsx_download'.format(route_prefix))}';
}
% endif
## delete single object ## delete single object
% if master.deletable and master.has_perm('delete') and master.delete_confirm == 'simple': % if master.deletable and master.has_perm('delete') and master.delete_confirm == 'simple':
ThisPage.methods.deleteObject = function(url) { ThisPage.methods.deleteObject = function(url) {
@ -453,4 +460,14 @@
${h.csrf_token(request)} ${h.csrf_token(request)}
${h.end_form()} ${h.end_form()}
% endif % endif
## TODO: can stop checking for buefy above once this legacy chunk is gone
% if request.session.pop('{}.results_xlsx.generated'.format(route_prefix), False):
<script type="text/javascript">
$(function() {
location.href = '${url('{}.results_xlsx_download'.format(route_prefix))}';
});
</script>
% endif
% endif % endif

View file

@ -2704,32 +2704,100 @@ class MasterView(View):
Download current list results as XLSX. Download current list results as XLSX.
""" """
results = self.get_effective_data() results = self.get_effective_data()
fields = self.get_xlsx_fields()
path = temp_path(suffix='.xlsx')
writer = ExcelWriter(path, fields, sheet_title=self.get_model_title_plural())
writer.write_header()
rows = [] # start thread to actually do work / generate progress data
for obj in results: route_prefix = self.get_route_prefix()
data = self.get_xlsx_row(obj, fields) key = '{}.results_xlsx'.format(route_prefix)
row = [data[field] for field in fields] progress = self.make_progress(key)
rows.append(row) thread = Thread(target=self.results_xlsx_thread,
args=(results, self.request.user.uuid, progress))
thread.start()
writer.write_rows(rows) # send user to progress page
writer.auto_freeze() return self.render_progress(progress, {
writer.auto_filter() 'cancel_url': self.get_index_url(),
writer.auto_resize() 'cancel_msg': "XLSX download was canceled.",
writer.save() })
response = self.request.response def results_xlsx_session(self):
with open(path, 'rb') as f: return RattailSession()
response.body = f.read()
os.remove(path)
response.content_length = len(response.body) def results_xlsx_thread(self, results, user_uuid, progress):
response.content_type = str('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') """
response.content_disposition = str('attachment; filename={}.xlsx').format(self.get_grid_key()) Thread target, responsible for actually generating the Excel file which
return response is to be presented for download.
"""
route_prefix = self.get_route_prefix()
session = self.results_xlsx_session()
try:
# create folder(s) for output; make sure file doesn't exist
path = os.path.join(self.rattail_config.datadir(), 'downloads',
'results-xlsx', route_prefix,
user_uuid[:2], user_uuid[2:])
if not os.path.exists(path):
os.makedirs(path)
path = os.path.join(path, '{}.xlsx'.format(route_prefix))
if os.path.exists(path):
os.remove(path)
results = results.with_session(session).all()
fields = self.get_xlsx_fields()
writer = ExcelWriter(path, fields, sheet_title=self.get_model_title_plural())
writer.write_header()
rows = []
def write(obj, i):
data = self.get_xlsx_row(obj, fields)
row = [data[field] for field in fields]
rows.append(row)
self.progress_loop(write, results, progress,
message="Collecting data for Excel")
def finalize(x, i):
writer.write_rows(rows)
writer.auto_freeze()
writer.auto_filter()
writer.auto_resize()
writer.save()
self.progress_loop(finalize, [1], progress,
message="Writing Excel file to disk")
except Exception as error:
msg = "generating XLSX file for download failed!"
log.warning(msg, exc_info=True)
session.rollback()
session.close()
if progress:
progress.session.load()
progress.session['error'] = True
progress.session['error_msg'] = "{}: {}".format(
msg, simple_error(error))
progress.session.save()
return
session.commit()
session.close()
if progress:
progress.session.load()
progress.session['complete'] = True
progress.session['success_url'] = self.get_index_url()
progress.session['extra_session_bits'] = {
'{}.results_xlsx.generated'.format(route_prefix): True,
}
progress.session.save()
def results_xlsx_download(self):
route_prefix = self.get_route_prefix()
user_uuid = self.request.user.uuid
path = os.path.join(self.rattail_config.datadir(), 'downloads',
'results-xlsx', route_prefix,
user_uuid[:2], user_uuid[2:],
'{}.xlsx'.format(route_prefix))
return self.file_response(path)
def get_xlsx_fields(self): def get_xlsx_fields(self):
""" """
@ -3679,6 +3747,9 @@ class MasterView(View):
config.add_route('{}.results_xlsx'.format(route_prefix), '{}/xlsx'.format(url_prefix)) config.add_route('{}.results_xlsx'.format(route_prefix), '{}/xlsx'.format(url_prefix))
config.add_view(cls, attr='results_xlsx', route_name='{}.results_xlsx'.format(route_prefix), config.add_view(cls, attr='results_xlsx', route_name='{}.results_xlsx'.format(route_prefix),
permission='{}.results_xlsx'.format(permission_prefix)) permission='{}.results_xlsx'.format(permission_prefix))
config.add_route('{}.results_xlsx_download'.format(route_prefix), '{}/xlsx/download'.format(url_prefix))
config.add_view(cls, attr='results_xlsx_download', route_name='{}.results_xlsx_download'.format(route_prefix),
permission='{}.results_xlsx'.format(permission_prefix))
# quickie (search) # quickie (search)
if cls.supports_quickie_search: if cls.supports_quickie_search:

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2017 Lance Edgar # Copyright © 2010-2020 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -26,18 +26,31 @@ Progress Views
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import six
from tailbone.progress import get_progress_session from tailbone.progress import get_progress_session
def progress(request): def progress(request):
key = request.matchdict['key'] key = request.matchdict['key']
session = get_progress_session(request, key, type=request.GET.get('sessiontype')) session = get_progress_session(request, key,
type=request.GET.get('sessiontype'))
if session.get('complete'): if session.get('complete'):
msg = session.get('success_msg') msg = session.get('success_msg')
if msg: if msg:
request.session.flash(msg) request.session.flash(msg)
bits = session.get('extra_session_bits')
if bits:
for key, value in six.iteritems(bits):
request.session[key] = value
elif session.get('error'): elif session.get('error'):
request.session.flash(session.get('error_msg', "An unspecified error occurred."), 'error') msg = session.get('error_msg', "An unspecified error occurred.")
request.session.flash(msg, 'error')
return session return session