Add common configuration logic for "input file templates"
just used in one batch so far but should be useful for many more..once can get around to migrating them had to rework the configuration logic to use HTML form instead of JSON, to allow for the file uploads
This commit is contained in:
parent
099b6915f4
commit
30f95e2f08
|
@ -53,6 +53,79 @@
|
||||||
</div>
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="input_file_template_field(key)">
|
||||||
|
<% tmpl = input_file_templates[key] %>
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="${tmpl['label']}">
|
||||||
|
<b-select name="${tmpl['setting_mode']}"
|
||||||
|
v-model="inputFileTemplateSettings['${tmpl['setting_mode']}']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
<option value="default">use default</option>
|
||||||
|
<option value="hosted">use uploaded file</option>
|
||||||
|
<option value="external">use other URL</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="File"
|
||||||
|
v-show="inputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted'"
|
||||||
|
:message="inputFileTemplateSettings['${tmpl['setting_file']}'] ? 'This file lives on disk at: ${input_file_option_dirs[tmpl['key']]}' : null">
|
||||||
|
<b-select name="${tmpl['setting_file']}"
|
||||||
|
v-model="inputFileTemplateSettings['${tmpl['setting_file']}']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
<option :value="null">-new-</option>
|
||||||
|
<option v-for="option in inputFileTemplateFileOptions['${tmpl['key']}']"
|
||||||
|
:key="option"
|
||||||
|
:value="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Upload"
|
||||||
|
v-show="inputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted' && !inputFileTemplateSettings['${tmpl['setting_file']}']">
|
||||||
|
|
||||||
|
<b-field class="file is-primary"
|
||||||
|
:class="{'has-name': !!inputFileTemplateSettings['${tmpl['setting_file']}']}">
|
||||||
|
<b-upload name="${tmpl['setting_file']}.upload"
|
||||||
|
v-model="inputFileTemplateUploads['${tmpl['key']}']"
|
||||||
|
class="file-label"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
<span class="file-cta">
|
||||||
|
<b-icon class="file-icon" pack="fas" icon="upload"></b-icon>
|
||||||
|
<span class="file-label">Click to upload</span>
|
||||||
|
</span>
|
||||||
|
</b-upload>
|
||||||
|
<span v-if="inputFileTemplateUploads['${tmpl['key']}']"
|
||||||
|
class="file-name">
|
||||||
|
{{ inputFileTemplateUploads['${tmpl['key']}'].name }}
|
||||||
|
</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="URL" expanded
|
||||||
|
v-show="inputFileTemplateSettings['${tmpl['setting_mode']}'] == 'external'">
|
||||||
|
<b-input name="${tmpl['setting_url']}"
|
||||||
|
v-model="inputFileTemplateSettings['${tmpl['setting_url']}']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="input_file_templates_section()">
|
||||||
|
<h3 class="block is-size-3">Input File Templates</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
% for key in input_file_templates:
|
||||||
|
${self.input_file_template_field(key)}
|
||||||
|
% endfor
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="form_content()"></%def>
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="page_content()">
|
||||||
${parent.page_content()}
|
${parent.page_content()}
|
||||||
|
|
||||||
|
@ -106,6 +179,11 @@
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
|
||||||
|
${h.form(request.current_route_url(), enctype='multipart/form-data', ref='saveSettingsForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${self.form_content()}
|
||||||
|
${h.end_form()}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="modify_this_page_vars()">
|
<%def name="modify_this_page_vars()">
|
||||||
|
@ -116,6 +194,16 @@
|
||||||
ThisPageData.simpleSettings = ${json.dumps(simple_settings)|n}
|
ThisPageData.simpleSettings = ${json.dumps(simple_settings)|n}
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
|
% if input_file_template_settings is not Undefined:
|
||||||
|
ThisPageData.inputFileTemplateSettings = ${json.dumps(input_file_template_settings)|n}
|
||||||
|
ThisPageData.inputFileTemplateFileOptions = ${json.dumps(input_file_options)|n}
|
||||||
|
ThisPageData.inputFileTemplateUploads = {
|
||||||
|
% for key in input_file_templates:
|
||||||
|
'${key}': null,
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
% endif
|
||||||
|
|
||||||
ThisPageData.purgeSettingsShowDialog = false
|
ThisPageData.purgeSettingsShowDialog = false
|
||||||
ThisPageData.purgingSettings = false
|
ThisPageData.purgingSettings = false
|
||||||
|
|
||||||
|
@ -127,41 +215,41 @@
|
||||||
this.purgeSettingsShowDialog = true
|
this.purgeSettingsShowDialog = true
|
||||||
}
|
}
|
||||||
|
|
||||||
ThisPage.methods.settingsCollectParams = function() {
|
% if input_file_template_settings is not Undefined:
|
||||||
% if simple_settings is not Undefined:
|
ThisPage.methods.validateInputFileTemplateSettings = function() {
|
||||||
return {simple_settings: this.simpleSettings}
|
% for tmpl in six.itervalues(input_file_templates):
|
||||||
% else:
|
if (this.inputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted') {
|
||||||
return {}
|
if (!this.inputFileTemplateSettings['${tmpl['setting_file']}']) {
|
||||||
|
if (!this.inputFileTemplateUploads['${tmpl['key']}']) {
|
||||||
|
return "You must provide a file to upload for the ${tmpl['label']} template."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
ThisPage.methods.validateSettings = function() {
|
||||||
|
let msg
|
||||||
|
|
||||||
|
% if input_file_template_settings is not Undefined:
|
||||||
|
msg = this.validateInputFileTemplateSettings()
|
||||||
|
if (msg) {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
% endif
|
% endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ThisPage.methods.saveSettings = function() {
|
ThisPage.methods.saveSettings = function() {
|
||||||
this.savingSettings = true
|
let msg = this.validateSettings()
|
||||||
|
if (msg) {
|
||||||
let url = ${json.dumps(request.current_route_url())|n}
|
alert(msg)
|
||||||
let params = this.settingsCollectParams()
|
return
|
||||||
let headers = {
|
|
||||||
'X-CSRF-TOKEN': this.csrftoken,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$http.post(url, params, {headers: headers}).then((response) => {
|
this.savingSettings = true
|
||||||
if (response.data.success) {
|
this.settingsNeedSaved = false
|
||||||
this.settingsNeedSaved = false
|
this.$refs.saveSettingsForm.submit()
|
||||||
location.href = url // reload page
|
|
||||||
} else {
|
|
||||||
this.$buefy.toast.open({
|
|
||||||
message: "Save failed: " + (response.data.error || "(unknown error)"),
|
|
||||||
type: 'is-danger',
|
|
||||||
duration: 4000, // 4 seconds
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
this.$buefy.toast.open({
|
|
||||||
message: "Save failed: (unknown error)",
|
|
||||||
type: 'is-danger',
|
|
||||||
duration: 4000, // 4 seconds
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cf. https://stackoverflow.com/a/56551646
|
// cf. https://stackoverflow.com/a/56551646
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
</div>
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
${h.hidden('profiles', **{':value': 'JSON.stringify(profilesData)'})}
|
||||||
|
|
||||||
<b-notification type="is-warning"
|
<b-notification type="is-warning"
|
||||||
:active.sync="showConfigFilesNote">
|
:active.sync="showConfigFilesNote">
|
||||||
|
@ -401,7 +401,8 @@
|
||||||
<b-field label="Restart Command"
|
<b-field label="Restart Command"
|
||||||
message="This will run as '${system_user}' system user - please configure sudoers as needed. Typical command is like: sudo supervisorctl restart poser:poser_datasync"
|
message="This will run as '${system_user}' system user - please configure sudoers as needed. Typical command is like: sudo supervisorctl restart poser:poser_datasync"
|
||||||
expanded>
|
expanded>
|
||||||
<b-input v-model="restartCommand"
|
<b-input name="restart_command"
|
||||||
|
v-model="restartCommand"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
</b-input>
|
</b-input>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
@ -675,13 +676,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThisPage.methods.settingsCollectParams = function() {
|
|
||||||
return {
|
|
||||||
profiles: this.profilesData,
|
|
||||||
restart_command: this.restartCommand,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
% if request.has_perm('datasync.restart'):
|
% if request.has_perm('datasync.restart'):
|
||||||
ThisPageData.restartingDatasync = false
|
ThisPageData.restartingDatasync = false
|
||||||
ThisPageData.restartDatasyncFormButtonText = "Restart Datasync"
|
ThisPageData.restartDatasyncFormButtonText = "Restart Datasync"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/configure.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
${h.hidden('handlers', **{':value': 'JSON.stringify(handlersData)'})}
|
||||||
|
|
||||||
<h3 class="is-size-3">Designated Handlers</h3>
|
<h3 class="is-size-3">Designated Handlers</h3>
|
||||||
|
|
||||||
|
@ -180,12 +180,6 @@
|
||||||
this.editHandlerShowDialog = false
|
this.editHandlerShowDialog = false
|
||||||
}
|
}
|
||||||
|
|
||||||
ThisPage.methods.settingsCollectParams = function() {
|
|
||||||
return {
|
|
||||||
handlers: this.handlersData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,11 @@
|
||||||
% if master.configurable and master.has_perm('configure'):
|
% if master.configurable and master.has_perm('configure'):
|
||||||
<li>${h.link_to("Configure {}".format(config_title), url('{}.configure'.format(route_prefix)))}</li>
|
<li>${h.link_to("Configure {}".format(config_title), url('{}.configure'.format(route_prefix)))}</li>
|
||||||
% endif
|
% endif
|
||||||
|
% if master.has_input_file_templates and master.has_perm('download_template'):
|
||||||
|
% for template in six.itervalues(input_file_templates):
|
||||||
|
<li>${h.link_to("Download {} Template".format(template['label']), template['effective_url'])}</li>
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="grid_tools()">
|
<%def name="grid_tools()">
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/configure.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
|
||||||
|
|
||||||
<h3 class="block is-size-3">Key Field</h3>
|
<h3 class="block is-size-3">Key Field</h3>
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
@ -10,7 +9,8 @@
|
||||||
<b-field grouped>
|
<b-field grouped>
|
||||||
|
|
||||||
<b-field label="Key Field">
|
<b-field label="Key Field">
|
||||||
<b-select v-model="simpleSettings['rattail.product.key']"
|
<b-select name="rattail.product.key"
|
||||||
|
v-model="simpleSettings['rattail.product.key']"
|
||||||
@input="updateKeyTitle()">
|
@input="updateKeyTitle()">
|
||||||
<option value="upc">upc</option>
|
<option value="upc">upc</option>
|
||||||
<option value="item_id">item_id</option>
|
<option value="item_id">item_id</option>
|
||||||
|
@ -19,7 +19,8 @@
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Key Field Label">
|
<b-field label="Key Field Label">
|
||||||
<b-input v-model="simpleSettings['rattail.product.key_title']"
|
<b-input name="rattail.product.key_title"
|
||||||
|
v-model="simpleSettings['rattail.product.key_title']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
</b-input>
|
</b-input>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
@ -32,7 +33,8 @@
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field message="If a product has an image in the DB, that will still be preferred.">
|
<b-field message="If a product has an image in the DB, that will still be preferred.">
|
||||||
<b-checkbox v-model="simpleSettings['tailbone.products.show_pod_image']"
|
<b-checkbox name="tailbone.products.show_pod_image"
|
||||||
|
v-model="simpleSettings['tailbone.products.show_pod_image']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Show "POD" Images as fallback
|
Show "POD" Images as fallback
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
|
|
@ -1,42 +1,46 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/configure.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
|
||||||
|
|
||||||
<h3 class="block is-size-3">Supported Workflows</h3>
|
<h3 class="block is-size-3">Supported Workflows</h3>
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_scratch']"
|
<b-checkbox name="rattail.batch.purchase.allow_receiving_from_scratch"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_scratch']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
From Scratch
|
From Scratch
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_invoice']"
|
<b-checkbox name="rattail.batch.purchase.allow_receiving_from_invoice"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_invoice']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
From Invoice
|
From Invoice
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_purchase_order']"
|
<b-checkbox name="rattail.batch.purchase.allow_receiving_from_purchase_order"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_purchase_order']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
From Purchase Order
|
From Purchase Order
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_purchase_order_with_invoice']"
|
<b-checkbox name="rattail.batch.purchase.allow_receiving_from_purchase_order_with_invoice"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_receiving_from_purchase_order_with_invoice']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
From Purchase Order, with Invoice
|
From Purchase Order, with Invoice
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_truck_dump_receiving']"
|
<b-checkbox name="rattail.batch.purchase.allow_truck_dump_receiving"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_truck_dump_receiving']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Truck Dump
|
Truck Dump
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
@ -48,14 +52,16 @@
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field message="NB. Allow Cases setting also affects Ordering behavior.">
|
<b-field message="NB. Allow Cases setting also affects Ordering behavior.">
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_cases']"
|
<b-checkbox name="rattail.batch.purchase.allow_cases"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_cases']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Allow Cases
|
Allow Cases
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.allow_expired_credits']"
|
<b-checkbox name="rattail.batch.purchase.allow_expired_credits"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.allow_expired_credits']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Allow "Expired" Credits
|
Allow "Expired" Credits
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
@ -67,21 +73,24 @@
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field message="TODO: this may also affect Ordering (?)">
|
<b-field message="TODO: this may also affect Ordering (?)">
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.mobile_images']"
|
<b-checkbox name="rattail.batch.purchase.mobile_images"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.mobile_images']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Show Product Images
|
Show Product Images
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.mobile_quick_receive']"
|
<b-checkbox name="rattail.batch.purchase.mobile_quick_receive"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.mobile_quick_receive']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Allow "Quick Receive"
|
Allow "Quick Receive"
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.batch.purchase.mobile_quick_receive_all']"
|
<b-checkbox name="rattail.batch.purchase.mobile_quick_receive_all"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.mobile_quick_receive_all']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Allow "Quick Receive All"
|
Allow "Quick Receive All"
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/configure.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
|
||||||
|
|
||||||
<h3 class="block is-size-3">Generating</h3>
|
<h3 class="block is-size-3">Generating</h3>
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field message="If not set, reports are shown as simple list of hyperlinks.">
|
<b-field message="If not set, reports are shown as simple list of hyperlinks.">
|
||||||
<b-checkbox v-model="simpleSettings['tailbone.reporting.choosing_uses_form']"
|
<b-checkbox name="tailbone.reporting.choosing_uses_form"
|
||||||
|
v-model="simpleSettings['tailbone.reporting.choosing_uses_form']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Show report chooser as form, with dropdown
|
Show report chooser as form, with dropdown
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/configure.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
|
||||||
|
|
||||||
<h3 class="block is-size-3">Sending</h3>
|
<h3 class="block is-size-3">Sending</h3>
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-checkbox v-model="simpleSettings['rattail.mail.record_attempts']"
|
<b-checkbox name="rattail.mail.record_attempts"
|
||||||
|
v-model="simpleSettings['rattail.mail.record_attempts']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Make record of all attempts to send email
|
Make record of all attempts to send email
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
|
6
tailbone/templates/vendors/configure.mako
vendored
6
tailbone/templates/vendors/configure.mako
vendored
|
@ -1,14 +1,14 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/configure.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="form_content()">
|
||||||
${parent.page_content()}
|
|
||||||
|
|
||||||
<h3 class="block is-size-3">Display</h3>
|
<h3 class="block is-size-3">Display</h3>
|
||||||
<div class="block" style="padding-left: 2rem;">
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
<b-field message="If not set, vendor chooser is a dropdown field.">
|
<b-field message="If not set, vendor chooser is a dropdown field.">
|
||||||
<b-checkbox v-model="simpleSettings['rattail.vendor.use_autocomplete']"
|
<b-checkbox name="rattail.vendor.use_autocomplete"
|
||||||
|
v-model="simpleSettings['rattail.vendor.use_autocomplete']"
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Show vendor chooser as autocomplete field
|
Show vendor chooser as autocomplete field
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
|
|
|
@ -83,6 +83,8 @@ class BatchMasterView(MasterView):
|
||||||
has_worksheet = False
|
has_worksheet = False
|
||||||
has_worksheet_file = False
|
has_worksheet_file = False
|
||||||
|
|
||||||
|
input_file_template_config_section = 'rattail.batch'
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'id',
|
'id',
|
||||||
'description',
|
'description',
|
||||||
|
@ -157,6 +159,10 @@ class BatchMasterView(MasterView):
|
||||||
factory = self.get_handler_factory(self.rattail_config)
|
factory = self.get_handler_factory(self.rattail_config)
|
||||||
return factory(self.rattail_config)
|
return factory(self.rattail_config)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def input_file_template_config_prefix(self):
|
||||||
|
return '{}.input_file_template'.format(self.batch_handler.batch_key)
|
||||||
|
|
||||||
def download_path(self, batch, filename):
|
def download_path(self, batch, filename):
|
||||||
return self.rattail_config.batch_filepath(batch.batch_key, batch.uuid, filename)
|
return self.rattail_config.batch_filepath(batch.batch_key, batch.uuid, filename)
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ DataSync Views
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
import getpass
|
import getpass
|
||||||
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ class DataSyncThreadView(MasterView):
|
||||||
settings = []
|
settings = []
|
||||||
watch = []
|
watch = []
|
||||||
|
|
||||||
for profile in data['profiles']:
|
for profile in json.loads(data['profiles']):
|
||||||
pkey = profile['key']
|
pkey = profile['key']
|
||||||
if profile['enabled']:
|
if profile['enabled']:
|
||||||
watch.append(pkey)
|
watch.append(pkey)
|
||||||
|
@ -181,12 +182,12 @@ class DataSyncThreadView(MasterView):
|
||||||
'value': ', '.join(consumers)},
|
'value': ', '.join(consumers)},
|
||||||
])
|
])
|
||||||
|
|
||||||
settings.extend([
|
if watch:
|
||||||
{'name': 'rattail.datasync.watch',
|
settings.append({'name': 'rattail.datasync.watch',
|
||||||
'value': ', '.join(watch)},
|
'value': ', '.join(watch)})
|
||||||
{'name': 'tailbone.datasync.restart',
|
|
||||||
'value': data['restart_command']},
|
settings.append({'name': 'tailbone.datasync.restart',
|
||||||
])
|
'value': data['restart_command']})
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
|
@ -560,7 +560,7 @@ cd {prefix}
|
||||||
def configure_gather_settings(self, data):
|
def configure_gather_settings(self, data):
|
||||||
settings = []
|
settings = []
|
||||||
|
|
||||||
for handler in data['handlers']:
|
for handler in json.loads(data['handlers']):
|
||||||
key = handler['key']
|
key = handler['key']
|
||||||
|
|
||||||
settings.extend([
|
settings.extend([
|
||||||
|
|
|
@ -29,6 +29,7 @@ from __future__ import unicode_literals, absolute_import
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
import datetime
|
import datetime
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -36,11 +37,9 @@ import json
|
||||||
import six
|
import six
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
import sqlalchemy_continuum as continuum
|
import sqlalchemy_continuum as continuum
|
||||||
from sqlalchemy_utils.functions import get_primary_keys, get_columns
|
from sqlalchemy_utils.functions import get_primary_keys, get_columns
|
||||||
|
|
||||||
|
|
||||||
from rattail.db import model, Session as RattailSession
|
from rattail.db import model, Session as RattailSession
|
||||||
from rattail.db.continuum import model_transaction_query
|
from rattail.db.continuum import model_transaction_query
|
||||||
from rattail.util import prettify, OrderedDict, simple_error
|
from rattail.util import prettify, OrderedDict, simple_error
|
||||||
|
@ -57,6 +56,7 @@ from pyramid import httpexceptions
|
||||||
from pyramid.renderers import get_renderer, render_to_response, render
|
from pyramid.renderers import get_renderer, render_to_response, render
|
||||||
from pyramid.response import FileResponse
|
from pyramid.response import FileResponse
|
||||||
from webhelpers2.html import HTML, tags
|
from webhelpers2.html import HTML, tags
|
||||||
|
from webob.compat import cgi_FieldStorage
|
||||||
|
|
||||||
from tailbone import forms, grids, diffs
|
from tailbone import forms, grids, diffs
|
||||||
from tailbone.views import View
|
from tailbone.views import View
|
||||||
|
@ -114,6 +114,7 @@ class MasterView(View):
|
||||||
execute_progress_initial_msg = None
|
execute_progress_initial_msg = None
|
||||||
supports_prev_next = False
|
supports_prev_next = False
|
||||||
supports_import_batch_from_file = False
|
supports_import_batch_from_file = False
|
||||||
|
has_input_file_templates = False
|
||||||
configurable = False
|
configurable = False
|
||||||
|
|
||||||
# set to True to add "View *global* Objects" permission, and
|
# set to True to add "View *global* Objects" permission, and
|
||||||
|
@ -1467,6 +1468,26 @@ class MasterView(View):
|
||||||
Return a content type for a file download, if known.
|
Return a content type for a file download, if known.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def download_input_file_template(self):
|
||||||
|
"""
|
||||||
|
View for downloading an input file template.
|
||||||
|
"""
|
||||||
|
key = self.request.GET['key']
|
||||||
|
filespec = self.request.GET['file']
|
||||||
|
|
||||||
|
matches = [tmpl for tmpl in self.get_input_file_templates()
|
||||||
|
if tmpl['key'] == key]
|
||||||
|
if not matches:
|
||||||
|
raise self.notfound()
|
||||||
|
|
||||||
|
template = matches[0]
|
||||||
|
templatesdir = os.path.join(self.rattail_config.datadir(),
|
||||||
|
'templates', 'input_files',
|
||||||
|
self.get_route_prefix())
|
||||||
|
basedir = os.path.join(templatesdir, template['key'])
|
||||||
|
path = os.path.join(basedir, filespec)
|
||||||
|
return self.file_response(path)
|
||||||
|
|
||||||
def edit(self):
|
def edit(self):
|
||||||
"""
|
"""
|
||||||
View for editing an existing model record.
|
View for editing an existing model record.
|
||||||
|
@ -2230,6 +2251,90 @@ class MasterView(View):
|
||||||
kwargs['db_picker_options'] = [tags.Option(k) for k in engines]
|
kwargs['db_picker_options'] = [tags.Option(k) for k in engines]
|
||||||
kwargs['db_picker_selected'] = selected
|
kwargs['db_picker_selected'] = selected
|
||||||
|
|
||||||
|
# add info for downloadable input file templates, if any
|
||||||
|
if self.has_input_file_templates:
|
||||||
|
templates = self.normalize_input_file_templates()
|
||||||
|
kwargs['input_file_templates'] = OrderedDict([(tmpl['key'], tmpl)
|
||||||
|
for tmpl in templates])
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_input_file_templates(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def normalize_input_file_templates(self, templates=None,
|
||||||
|
include_file_options=False):
|
||||||
|
if templates is None:
|
||||||
|
templates = self.get_input_file_templates()
|
||||||
|
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
|
||||||
|
if include_file_options:
|
||||||
|
templatesdir = os.path.join(self.rattail_config.datadir(),
|
||||||
|
'templates', 'input_files',
|
||||||
|
route_prefix)
|
||||||
|
|
||||||
|
for template in templates:
|
||||||
|
|
||||||
|
if 'config_section' not in template:
|
||||||
|
template['config_section'] = self.input_file_template_config_section
|
||||||
|
section = template['config_section']
|
||||||
|
|
||||||
|
if 'config_prefix' not in template:
|
||||||
|
template['config_prefix'] = '{}.{}'.format(
|
||||||
|
self.input_file_template_config_prefix,
|
||||||
|
template['key'])
|
||||||
|
prefix = template['config_prefix']
|
||||||
|
|
||||||
|
for key in ('mode', 'file', 'url'):
|
||||||
|
|
||||||
|
if 'option_{}'.format(key) not in template:
|
||||||
|
template['option_{}'.format(key)] = '{}.{}'.format(prefix, key)
|
||||||
|
|
||||||
|
if 'setting_{}'.format(key) not in template:
|
||||||
|
template['setting_{}'.format(key)] = '{}.{}'.format(
|
||||||
|
section,
|
||||||
|
template['option_{}'.format(key)])
|
||||||
|
|
||||||
|
if key not in template:
|
||||||
|
value = self.rattail_config.get(
|
||||||
|
section,
|
||||||
|
template['option_{}'.format(key)])
|
||||||
|
if value is not None:
|
||||||
|
template[key] = value
|
||||||
|
|
||||||
|
template.setdefault('mode', 'default')
|
||||||
|
template.setdefault('file', None)
|
||||||
|
template.setdefault('url', template['default_url'])
|
||||||
|
|
||||||
|
if include_file_options:
|
||||||
|
options = []
|
||||||
|
basedir = os.path.join(templatesdir, template['key'])
|
||||||
|
if os.path.exists(basedir):
|
||||||
|
for name in sorted(os.listdir(basedir)):
|
||||||
|
if len(name) == 4 and name.isdigit():
|
||||||
|
files = os.listdir(os.path.join(basedir, name))
|
||||||
|
if len(files) == 1:
|
||||||
|
options.append(os.path.join(name, files[0]))
|
||||||
|
template['file_options'] = options
|
||||||
|
template['file_options_dir'] = basedir
|
||||||
|
|
||||||
|
if template['mode'] == 'external':
|
||||||
|
template['effective_url'] = template['url']
|
||||||
|
elif template['mode'] == 'hosted':
|
||||||
|
template['effective_url'] = self.request.route_url(
|
||||||
|
'{}.download_input_file_template'.format(route_prefix),
|
||||||
|
_query={'key': template['key'],
|
||||||
|
'file': template['file']})
|
||||||
|
else:
|
||||||
|
template['effective_url'] = template['default_url']
|
||||||
|
|
||||||
|
return templates
|
||||||
|
|
||||||
|
def template_kwargs_index(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Method stub, so subclass can always invoke super() for it.
|
||||||
|
"""
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def template_kwargs_create(self, **kwargs):
|
def template_kwargs_create(self, **kwargs):
|
||||||
|
@ -4043,16 +4148,71 @@ class MasterView(View):
|
||||||
self.request.session.flash("Settings have been removed.")
|
self.request.session.flash("Settings have been removed.")
|
||||||
return self.redirect(self.request.current_route_url())
|
return self.redirect(self.request.current_route_url())
|
||||||
else:
|
else:
|
||||||
data = self.request.json_body
|
data = self.request.POST
|
||||||
|
|
||||||
|
# collect any uploaded files
|
||||||
|
uploads = {}
|
||||||
|
for key, value in six.iteritems(data):
|
||||||
|
if isinstance(value, cgi_FieldStorage):
|
||||||
|
tempdir = tempfile.mkdtemp()
|
||||||
|
filename = os.path.basename(value.filename)
|
||||||
|
filepath = os.path.join(tempdir, filename)
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
f.write(value.file.read())
|
||||||
|
uploads[key] = {
|
||||||
|
'filedir': tempdir,
|
||||||
|
'filename': filename,
|
||||||
|
'filepath': filepath,
|
||||||
|
}
|
||||||
|
|
||||||
|
# process any uploads first
|
||||||
|
if uploads:
|
||||||
|
self.configure_process_uploads(uploads, data)
|
||||||
|
|
||||||
|
# then gather/save settings
|
||||||
settings = self.configure_gather_settings(data)
|
settings = self.configure_gather_settings(data)
|
||||||
self.configure_remove_settings()
|
self.configure_remove_settings()
|
||||||
self.configure_save_settings(settings)
|
self.configure_save_settings(settings)
|
||||||
self.request.session.flash("Settings have been saved.")
|
self.request.session.flash("Settings have been saved.")
|
||||||
return self.json_response({'success': True})
|
return self.redirect(self.request.current_route_url())
|
||||||
|
|
||||||
context = self.configure_get_context()
|
context = self.configure_get_context()
|
||||||
return self.render_to_response('configure', context)
|
return self.render_to_response('configure', context)
|
||||||
|
|
||||||
|
def configure_process_uploads(self, uploads, data):
|
||||||
|
if self.has_input_file_templates:
|
||||||
|
templatesdir = os.path.join(self.rattail_config.datadir(),
|
||||||
|
'templates', 'input_files',
|
||||||
|
self.get_route_prefix())
|
||||||
|
|
||||||
|
def get_next_filedir(basedir):
|
||||||
|
nextid = 1
|
||||||
|
while True:
|
||||||
|
path = os.path.join(basedir, '{:04d}'.format(nextid))
|
||||||
|
if not os.path.exists(path):
|
||||||
|
# this should fail if there happens to be a race
|
||||||
|
# condition and someone else got to this id first
|
||||||
|
os.mkdir(path)
|
||||||
|
return path
|
||||||
|
nextid += 1
|
||||||
|
|
||||||
|
for template in self.normalize_input_file_templates():
|
||||||
|
key = '{}.upload'.format(template['setting_file'])
|
||||||
|
if key in uploads:
|
||||||
|
assert self.request.POST[template['setting_mode']] == 'hosted'
|
||||||
|
assert not self.request.POST[template['setting_file']]
|
||||||
|
info = uploads[key]
|
||||||
|
basedir = os.path.join(templatesdir, template['key'])
|
||||||
|
if not os.path.exists(basedir):
|
||||||
|
os.makedirs(basedir)
|
||||||
|
filedir = get_next_filedir(basedir)
|
||||||
|
filepath = os.path.join(filedir, info['filename'])
|
||||||
|
shutil.copyfile(info['filepath'], filepath)
|
||||||
|
shutil.rmtree(info['filedir'])
|
||||||
|
numdir = os.path.basename(filedir)
|
||||||
|
data[template['setting_file']] = os.path.join(numdir,
|
||||||
|
info['filename'])
|
||||||
|
|
||||||
def configure_get_simple_settings(self):
|
def configure_get_simple_settings(self):
|
||||||
"""
|
"""
|
||||||
If you have some "simple" settings, each of which basically
|
If you have some "simple" settings, each of which basically
|
||||||
|
@ -4120,22 +4280,34 @@ class MasterView(View):
|
||||||
|
|
||||||
context['simple_settings'] = settings
|
context['simple_settings'] = settings
|
||||||
|
|
||||||
|
# add settings for downloadable input file templates, if any
|
||||||
|
if self.has_input_file_templates:
|
||||||
|
settings = {}
|
||||||
|
file_options = {}
|
||||||
|
file_option_dirs = {}
|
||||||
|
for template in self.normalize_input_file_templates(
|
||||||
|
include_file_options=True):
|
||||||
|
settings[template['setting_mode']] = template['mode']
|
||||||
|
settings[template['setting_file']] = template['file']
|
||||||
|
settings[template['setting_url']] = template['url']
|
||||||
|
file_options[template['key']] = template['file_options']
|
||||||
|
file_option_dirs[template['key']] = template['file_options_dir']
|
||||||
|
context['input_file_template_settings'] = settings
|
||||||
|
context['input_file_options'] = file_options
|
||||||
|
context['input_file_option_dirs'] = file_option_dirs
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def configure_gather_settings(self, data):
|
def configure_gather_settings(self, data):
|
||||||
settings = []
|
settings = []
|
||||||
|
|
||||||
|
# maybe collect "simple" settings
|
||||||
simple_settings = self.configure_get_simple_settings()
|
simple_settings = self.configure_get_simple_settings()
|
||||||
if simple_settings and 'simple_settings' in data:
|
if simple_settings:
|
||||||
|
|
||||||
data_settings = data['simple_settings']
|
|
||||||
|
|
||||||
for simple in simple_settings:
|
for simple in simple_settings:
|
||||||
name = self.configure_get_name_for_simple_setting(simple)
|
name = self.configure_get_name_for_simple_setting(simple)
|
||||||
value = None
|
value = data.get(name)
|
||||||
|
|
||||||
if name in data_settings:
|
|
||||||
value = data_settings[name]
|
|
||||||
|
|
||||||
if simple.get('type') is bool:
|
if simple.get('type') is bool:
|
||||||
value = six.text_type(bool(value)).lower()
|
value = six.text_type(bool(value)).lower()
|
||||||
|
@ -4145,14 +4317,45 @@ class MasterView(View):
|
||||||
settings.append({'name': name,
|
settings.append({'name': name,
|
||||||
'value': value})
|
'value': value})
|
||||||
|
|
||||||
|
# maybe also collect input file template settings
|
||||||
|
if self.has_input_file_templates:
|
||||||
|
for template in self.normalize_input_file_templates():
|
||||||
|
|
||||||
|
# mode
|
||||||
|
settings.append({'name': template['setting_mode'],
|
||||||
|
'value': data.get(template['setting_mode'])})
|
||||||
|
|
||||||
|
# file
|
||||||
|
value = data.get(template['setting_file'])
|
||||||
|
if value:
|
||||||
|
# nb. avoid saving if empty, so can remain "null"
|
||||||
|
settings.append({'name': template['setting_file'],
|
||||||
|
'value': value})
|
||||||
|
|
||||||
|
# url
|
||||||
|
settings.append({'name': template['setting_url'],
|
||||||
|
'value': data.get(template['setting_url'])})
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
def configure_remove_settings(self):
|
def configure_remove_settings(self):
|
||||||
|
model = self.model
|
||||||
|
names = []
|
||||||
|
|
||||||
simple_settings = self.configure_get_simple_settings()
|
simple_settings = self.configure_get_simple_settings()
|
||||||
if simple_settings:
|
if simple_settings:
|
||||||
model = self.model
|
names.extend([self.configure_get_name_for_simple_setting(simple)
|
||||||
names = [self.configure_get_name_for_simple_setting(simple)
|
for simple in simple_settings])
|
||||||
for simple in simple_settings]
|
|
||||||
|
if self.has_input_file_templates:
|
||||||
|
for template in self.normalize_input_file_templates():
|
||||||
|
names.extend([
|
||||||
|
template['setting_mode'],
|
||||||
|
template['setting_file'],
|
||||||
|
template['setting_url'],
|
||||||
|
])
|
||||||
|
|
||||||
|
if names:
|
||||||
self.Session.query(model.Setting)\
|
self.Session.query(model.Setting)\
|
||||||
.filter(model.Setting.name.in_(names))\
|
.filter(model.Setting.name.in_(names))\
|
||||||
.delete(synchronize_session=False)
|
.delete(synchronize_session=False)
|
||||||
|
@ -4365,6 +4568,14 @@ class MasterView(View):
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.merge'.format(permission_prefix),
|
config.add_tailbone_permission(permission_prefix, '{}.merge'.format(permission_prefix),
|
||||||
"Merge 2 {}".format(model_title_plural))
|
"Merge 2 {}".format(model_title_plural))
|
||||||
|
|
||||||
|
# download input file template
|
||||||
|
if cls.has_input_file_templates and cls.creatable:
|
||||||
|
config.add_route('{}.download_input_file_template'.format(route_prefix),
|
||||||
|
'{}/download-input-file-template'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='download_input_file_template',
|
||||||
|
route_name='{}.download_input_file_template'.format(route_prefix),
|
||||||
|
permission='{}.create'.format(permission_prefix))
|
||||||
|
|
||||||
# view
|
# view
|
||||||
if cls.viewable:
|
if cls.viewable:
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.view'.format(permission_prefix),
|
config.add_tailbone_permission(permission_prefix, '{}.view'.format(permission_prefix),
|
||||||
|
|
Loading…
Reference in a new issue