Add new/flexible "download results" feature

This commit is contained in:
Lance Edgar 2020-08-22 12:25:02 -05:00
parent 43472c7eb6
commit 922cbe4451
3 changed files with 553 additions and 3 deletions

View file

@ -143,6 +143,155 @@
<%def name="grid_tools()">
## download search results
% if master.results_downloadable and master.has_perm('download_results'):
% if use_buefy:
<b-button type="is-primary"
icon-pack="fas"
icon-left="fas fa-download"
@click="showDownloadResultsDialog = true"
:disabled="!total">
Download Results
</b-button>
${h.form(url('{}.download_results'.format(route_prefix)), ref='download_results_form')}
${h.csrf_token(request)}
<input type="hidden" name="fmt" :value="downloadResultsFormat" />
<input type="hidden" name="fields" :value="downloadResultsFieldsIncluded" />
${h.end_form()}
<b-modal :active.sync="showDownloadResultsDialog">
<div class="card">
<div class="card-content">
<p>
There are
<span class="is-size-4 has-text-weight-bold">
{{ total.toLocaleString('en') }} ${model_title_plural}
</span>
matching your current filters.
</p>
<p>
You may download this set as a single data file if you like.
</p>
<br />
<b-notification type="is-warning" :closable="false"
v-if="downloadResultsFormat == 'xlsx' && total >= 1000">
Excel downloads for large data sets can take a long time to
generate, and bog down the server in the meantime. You are
encouraged to choose CSV for a large data set, even though
the end result (file size) may be larger with CSV.
</b-notification>
<div style="display: flex;">
<div style="flex-grow: 1;">
<b-field horizontal label="Format">
<b-select v-model="downloadResultsFormat">
% for key, label in six.iteritems(master.download_results_supported_formats()):
<option value="${key}">${label}</option>
% endfor
</b-select>
</b-field>
</div>
<div>
<div v-show="downloadResultsFieldsMode != 'choose'"
class="has-text-right">
<p v-if="downloadResultsFieldsMode == 'default'">
Will use DEFAULT fields.
</p>
<p v-if="downloadResultsFieldsMode == 'all'">
Will use ALL fields.
</p>
</div>
<br />
<div class="buttons">
<b-button type="is-primary"
v-show="downloadResultsFieldsMode != 'default'"
@click="downloadResultsUseDefaultFields()">
Use Default Fields
</b-button>
<b-button type="is-primary"
v-show="downloadResultsFieldsMode != 'all'"
@click="downloadResultsUseAllFields()">
Use All Fields
</b-button>
<b-button type="is-primary"
v-show="downloadResultsFieldsMode != 'choose'"
@click="downloadResultsFieldsMode = 'choose'">
Choose Fields
</b-button>
</div>
</div>
</div>
<br />
<div v-show="downloadResultsFieldsMode == 'choose'">
<div style="display: flex;">
<div>
<b-field label="Excluded Fields">
<b-select multiple native-size="8"
expanded
ref="downloadResultsExcludedFields">
<option v-for="field in downloadResultsFieldsAvailable"
v-if="!downloadResultsFieldsIncluded.includes(field)"
:key="field"
:value="field">
{{ field }}
</option>
</b-select>
</b-field>
</div>
<div>
<br /><br />
<b-button style="margin: 0.5rem;"
@click="downloadResultsExcludeFields()">
&lt;
</b-button>
<br />
<b-button style="margin: 0.5rem;"
@click="downloadResultsIncludeFields()">
&gt;
</b-button>
</div>
<div>
<b-field label="Included Fields">
<b-select multiple native-size="8"
expanded
ref="downloadResultsIncludedFields">
<option v-for="field in downloadResultsFieldsIncluded"
:key="field"
:value="field">
{{ field }}
</option>
</b-select>
</b-field>
</div>
</div>
</div>
</div> <!-- card-content -->
<footer class="modal-card-foot">
<b-button @click="showDownloadResultsDialog = false">
Cancel
</b-button>
<once-button type="is-primary"
@click="downloadResultsSubmit()"
icon-pack="fas"
icon-left="fas fa-download"
:disabled="!downloadResultsFieldsIncluded.length"
text="Download Results">
</once-button>
</footer>
</div>
</b-modal>
% endif
% endif
## merge 2 objects
% if master.mergeable and request.has_perm('{}.merge'.format(permission_prefix)):
@ -256,6 +405,14 @@
</%def>
<%def name="page_content()">
% if download_results_path:
<b-notification type="is-info">
Your download should start automatically, or you can
${h.link_to("click here", '{}?filename={}'.format(url('{}.download_results'.format(route_prefix)), h.os.path.basename(download_results_path)))}
</b-notification>
% endif
<${grid.component} :csrftoken="csrftoken"
% if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple':
@deleteActionClicked="deleteObject"
@ -295,6 +452,19 @@
${parent.modify_this_page_vars()}
<script type="text/javascript">
## maybe auto-redirect to download latest results file
% if download_results_path and use_buefy:
ThisPage.methods.downloadResultsRedirect = function() {
location.href = '${url('{}.download_results'.format(route_prefix))}?filename=${h.os.path.basename(download_results_path)}';
}
ThisPage.mounted = function() {
// we give this 1 second before attempting the redirect; otherwise
// the FontAwesome icons do not seem to load properly. so this way
// the page should fully render before redirecting
window.setTimeout(this.downloadResultsRedirect, 1000)
}
% endif
## TODO: stop checking for buefy here once we only have the one session.pop()
% if use_buefy and request.session.pop('{}.results_csv.generated'.format(route_prefix), False):
ThisPage.mounted = function() {
@ -318,6 +488,83 @@
}
% endif
## download results
% if master.results_downloadable and master.has_perm('download_results'):
${grid.component_studly}Data.downloadResultsFormat = '${master.download_results_default_format()}'
${grid.component_studly}Data.showDownloadResultsDialog = false
${grid.component_studly}Data.downloadResultsFieldsMode = 'default'
${grid.component_studly}Data.downloadResultsFieldsAvailable = ${json.dumps(download_results_fields_available)|n}
${grid.component_studly}Data.downloadResultsFieldsDefault = ${json.dumps(download_results_fields_default)|n}
${grid.component_studly}Data.downloadResultsFieldsIncluded = ${json.dumps(download_results_fields_default)|n}
${grid.component_studly}.computed.downloadResultsFieldsExcluded = function() {
let excluded = []
this.downloadResultsFieldsAvailable.forEach(field => {
if (!this.downloadResultsFieldsIncluded.includes(field)) {
excluded.push(field)
}
}, this)
return excluded
}
${grid.component_studly}.methods.downloadResultsExcludeFields = function() {
let selected = this.$refs.downloadResultsIncludedFields.selected
if (!selected) {
return
}
selected = Array.from(selected)
selected.forEach(field => {
// de-select the entry within "included" field input
let index = this.$refs.downloadResultsIncludedFields.selected.indexOf(field)
if (index > -1) {
this.$refs.downloadResultsIncludedFields.selected.splice(index, 1)
}
// remove field from official "included" list
index = this.downloadResultsFieldsIncluded.indexOf(field)
if (index > -1) {
this.downloadResultsFieldsIncluded.splice(index, 1)
}
}, this)
}
${grid.component_studly}.methods.downloadResultsIncludeFields = function() {
let selected = this.$refs.downloadResultsExcludedFields.selected
if (!selected) {
return
}
selected = Array.from(selected)
selected.forEach(field => {
// de-select the entry within "excluded" field input
let index = this.$refs.downloadResultsExcludedFields.selected.indexOf(field)
if (index > -1) {
this.$refs.downloadResultsExcludedFields.selected.splice(index, 1)
}
// add field to official "included" list
this.downloadResultsFieldsIncluded.push(field)
}, this)
}
${grid.component_studly}.methods.downloadResultsUseDefaultFields = function() {
this.downloadResultsFieldsIncluded = Array.from(this.downloadResultsFieldsDefault)
this.downloadResultsFieldsMode = 'default'
}
${grid.component_studly}.methods.downloadResultsUseAllFields = function() {
this.downloadResultsFieldsIncluded = Array.from(this.downloadResultsFieldsAvailable)
this.downloadResultsFieldsMode = 'all'
}
${grid.component_studly}.methods.downloadResultsSubmit = function() {
this.$refs.download_results_form.submit()
}
% endif
## enable / disable selected objects
% if master.supports_set_enabled_toggle and master.has_perm('enable_disable_set'):