feat: add "has output file templates" config option for master view
this is a bit hacky, a quick copy/paste job from the equivalent feature for input file templates. i assume this will get cleaned up when moved to wuttaweb..
This commit is contained in:
parent
07871188aa
commit
1def26a35b
|
@ -143,6 +143,68 @@
|
|||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="output_file_template_field(key)">
|
||||
<% tmpl = output_file_templates[key] %>
|
||||
<b-field grouped>
|
||||
|
||||
<b-field label="${tmpl['label']}">
|
||||
<b-select name="${tmpl['setting_mode']}"
|
||||
v-model="outputFileTemplateSettings['${tmpl['setting_mode']}']"
|
||||
@input="settingsNeedSaved = true">
|
||||
<option value="default">use default</option>
|
||||
<option value="hosted">use uploaded file</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
|
||||
<b-field label="File"
|
||||
v-show="outputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted'"
|
||||
:message="outputFileTemplateSettings['${tmpl['setting_file']}'] ? 'This file lives on disk at: ${output_file_option_dirs[tmpl['key']]}' : null">
|
||||
<b-select name="${tmpl['setting_file']}"
|
||||
v-model="outputFileTemplateSettings['${tmpl['setting_file']}']"
|
||||
@input="settingsNeedSaved = true">
|
||||
<option :value="null">-new-</option>
|
||||
<option v-for="option in outputFileTemplateFileOptions['${tmpl['key']}']"
|
||||
:key="option"
|
||||
:value="option">
|
||||
{{ option }}
|
||||
</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Upload"
|
||||
v-show="outputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted' && !outputFileTemplateSettings['${tmpl['setting_file']}']">
|
||||
|
||||
<b-field class="file is-primary"
|
||||
:class="{'has-name': !!outputFileTemplateSettings['${tmpl['setting_file']}']}">
|
||||
<b-upload name="${tmpl['setting_file']}.upload"
|
||||
v-model="outputFileTemplateUploads['${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="outputFileTemplateUploads['${tmpl['key']}']"
|
||||
class="file-name">
|
||||
{{ outputFileTemplateUploads['${tmpl['key']}'].name }}
|
||||
</span>
|
||||
</b-field>
|
||||
|
||||
</b-field>
|
||||
|
||||
</b-field>
|
||||
</%def>
|
||||
|
||||
<%def name="output_file_templates_section()">
|
||||
<h3 class="block is-size-3">Output File Templates</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
% for key in output_file_templates:
|
||||
${self.output_file_template_field(key)}
|
||||
% endfor
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="form_content()"></%def>
|
||||
|
||||
<%def name="page_content()">
|
||||
|
@ -229,6 +291,7 @@
|
|||
ThisPageData.settingsNeedSaved = false
|
||||
ThisPageData.undoChanges = false
|
||||
ThisPageData.savingSettings = false
|
||||
ThisPageData.validators = []
|
||||
|
||||
ThisPage.methods.purgeSettingsInit = function() {
|
||||
this.purgeSettingsShowDialog = true
|
||||
|
@ -260,7 +323,19 @@
|
|||
}
|
||||
|
||||
ThisPage.methods.saveSettings = function() {
|
||||
let msg = this.validateSettings()
|
||||
let msg
|
||||
|
||||
// nb. this is the future
|
||||
for (let validator of this.validators) {
|
||||
msg = validator.call(this)
|
||||
if (msg) {
|
||||
alert(msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// nb. legacy method
|
||||
msg = this.validateSettings()
|
||||
if (msg) {
|
||||
alert(msg)
|
||||
return
|
||||
|
@ -291,5 +366,35 @@
|
|||
window.addEventListener('beforeunload', this.beforeWindowUnload)
|
||||
}
|
||||
|
||||
##############################
|
||||
## output file templates
|
||||
##############################
|
||||
|
||||
% if output_file_template_settings is not Undefined:
|
||||
|
||||
ThisPageData.outputFileTemplateSettings = ${json.dumps(output_file_template_settings)|n}
|
||||
ThisPageData.outputFileTemplateFileOptions = ${json.dumps(output_file_options)|n}
|
||||
ThisPageData.outputFileTemplateUploads = {
|
||||
% for key in output_file_templates:
|
||||
'${key}': null,
|
||||
% endfor
|
||||
}
|
||||
|
||||
ThisPage.methods.validateOutputFileTemplateSettings = function() {
|
||||
% for tmpl in output_file_templates.values():
|
||||
if (this.outputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted') {
|
||||
if (!this.outputFileTemplateSettings['${tmpl['setting_file']}']) {
|
||||
if (!this.outputFileTemplateUploads['${tmpl['key']}']) {
|
||||
return "You must provide a file to upload for the ${tmpl['label']} template."
|
||||
}
|
||||
}
|
||||
}
|
||||
% endfor
|
||||
}
|
||||
|
||||
ThisPageData.validators.push(ThisPage.methods.validateOutputFileTemplateSettings)
|
||||
|
||||
% endif
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
|
|
@ -1,2 +1,78 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="wuttaweb:templates/configure.mako" />
|
||||
<%namespace name="tailbone_base" file="tailbone:templates/configure.mako" />
|
||||
|
||||
<%def name="input_file_templates_section()">
|
||||
${tailbone_base.input_file_templates_section()}
|
||||
</%def>
|
||||
|
||||
<%def name="output_file_templates_section()">
|
||||
${tailbone_base.output_file_templates_section()}
|
||||
</%def>
|
||||
|
||||
<%def name="modify_vue_vars()">
|
||||
${parent.modify_vue_vars()}
|
||||
<script>
|
||||
|
||||
##############################
|
||||
## input file templates
|
||||
##############################
|
||||
|
||||
% 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
|
||||
}
|
||||
|
||||
ThisPage.methods.validateInputFileTemplateSettings = function() {
|
||||
% for tmpl in input_file_templates.values():
|
||||
if (this.inputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted') {
|
||||
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
|
||||
}
|
||||
|
||||
ThisPageData.validators.push(ThisPage.methods.validateInputFileTemplateSettings)
|
||||
|
||||
% endif
|
||||
|
||||
##############################
|
||||
## output file templates
|
||||
##############################
|
||||
|
||||
% if output_file_template_settings is not Undefined:
|
||||
|
||||
ThisPageData.outputFileTemplateSettings = ${json.dumps(output_file_template_settings)|n}
|
||||
ThisPageData.outputFileTemplateFileOptions = ${json.dumps(output_file_options)|n}
|
||||
ThisPageData.outputFileTemplateUploads = {
|
||||
% for key in output_file_templates:
|
||||
'${key}': null,
|
||||
% endfor
|
||||
}
|
||||
|
||||
ThisPage.methods.validateOutputFileTemplateSettings = function() {
|
||||
% for tmpl in output_file_templates.values():
|
||||
if (this.outputFileTemplateSettings['${tmpl['setting_mode']}'] == 'hosted') {
|
||||
if (!this.outputFileTemplateSettings['${tmpl['setting_file']}']) {
|
||||
if (!this.outputFileTemplateUploads['${tmpl['key']}']) {
|
||||
return "You must provide a file to upload for the ${tmpl['label']} template."
|
||||
}
|
||||
}
|
||||
}
|
||||
% endfor
|
||||
}
|
||||
|
||||
ThisPageData.validators.push(ThisPage.methods.validateOutputFileTemplateSettings)
|
||||
|
||||
% endif
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
|
|
@ -117,6 +117,7 @@ class MasterView(View):
|
|||
supports_prev_next = False
|
||||
supports_import_batch_from_file = False
|
||||
has_input_file_templates = False
|
||||
has_output_file_templates = False
|
||||
configurable = False
|
||||
|
||||
# set to True to add "View *global* Objects" permission, and
|
||||
|
@ -1820,6 +1821,26 @@ class MasterView(View):
|
|||
path = os.path.join(basedir, filespec)
|
||||
return self.file_response(path)
|
||||
|
||||
def download_output_file_template(self):
|
||||
"""
|
||||
View for downloading an output file template.
|
||||
"""
|
||||
key = self.request.GET['key']
|
||||
filespec = self.request.GET['file']
|
||||
|
||||
matches = [tmpl for tmpl in self.get_output_file_templates()
|
||||
if tmpl['key'] == key]
|
||||
if not matches:
|
||||
raise self.notfound()
|
||||
|
||||
template = matches[0]
|
||||
templatesdir = os.path.join(self.rattail_config.datadir(),
|
||||
'templates', 'output_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):
|
||||
"""
|
||||
View for editing an existing model record.
|
||||
|
@ -2848,6 +2869,12 @@ class MasterView(View):
|
|||
kwargs['input_file_templates'] = OrderedDict([(tmpl['key'], tmpl)
|
||||
for tmpl in templates])
|
||||
|
||||
# add info for downloadable output file templates, if any
|
||||
if self.has_output_file_templates:
|
||||
templates = self.normalize_output_file_templates()
|
||||
kwargs['output_file_templates'] = OrderedDict([(tmpl['key'], tmpl)
|
||||
for tmpl in templates])
|
||||
|
||||
return kwargs
|
||||
|
||||
def get_input_file_templates(self):
|
||||
|
@ -2922,6 +2949,81 @@ class MasterView(View):
|
|||
|
||||
return templates
|
||||
|
||||
def get_output_file_templates(self):
|
||||
return []
|
||||
|
||||
def normalize_output_file_templates(self, templates=None,
|
||||
include_file_options=False):
|
||||
if templates is None:
|
||||
templates = self.get_output_file_templates()
|
||||
|
||||
route_prefix = self.get_route_prefix()
|
||||
|
||||
if include_file_options:
|
||||
templatesdir = os.path.join(self.rattail_config.datadir(),
|
||||
'templates', 'output_files',
|
||||
route_prefix)
|
||||
|
||||
for template in templates:
|
||||
|
||||
if 'config_section' not in template:
|
||||
if hasattr(self, 'output_file_template_config_section'):
|
||||
template['config_section'] = self.output_file_template_config_section
|
||||
else:
|
||||
template['config_section'] = route_prefix
|
||||
section = template['config_section']
|
||||
|
||||
if 'config_prefix' not in template:
|
||||
template['config_prefix'] = '{}.{}'.format(
|
||||
self.output_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_output_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.
|
||||
|
@ -2969,6 +3071,12 @@ class MasterView(View):
|
|||
items.append(tags.link_to(f"Download {template['label']} Template",
|
||||
template['effective_url']))
|
||||
|
||||
if self.has_output_file_templates and self.has_perm('configure'):
|
||||
templates = self.normalize_output_file_templates()
|
||||
for template in templates:
|
||||
items.append(tags.link_to(f"Download {template['label']} Template",
|
||||
template['effective_url']))
|
||||
|
||||
# if self.viewing:
|
||||
|
||||
# # # TODO: either make this configurable, or just lose it.
|
||||
|
@ -5204,6 +5312,39 @@ class MasterView(View):
|
|||
data[template['setting_file']] = os.path.join(numdir,
|
||||
info['filename'])
|
||||
|
||||
if self.has_output_file_templates:
|
||||
templatesdir = os.path.join(self.rattail_config.datadir(),
|
||||
'templates', 'output_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_output_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):
|
||||
"""
|
||||
If you have some "simple" settings, each of which basically
|
||||
|
@ -5248,7 +5389,8 @@ class MasterView(View):
|
|||
simple['option'])
|
||||
|
||||
def configure_get_context(self, simple_settings=None,
|
||||
input_file_templates=True):
|
||||
input_file_templates=True,
|
||||
output_file_templates=True):
|
||||
"""
|
||||
Returns the full context dict, for rendering the configure
|
||||
page template.
|
||||
|
@ -5305,10 +5447,27 @@ class MasterView(View):
|
|||
context['input_file_options'] = file_options
|
||||
context['input_file_option_dirs'] = file_option_dirs
|
||||
|
||||
# add settings for output file templates, if any
|
||||
if output_file_templates and self.has_output_file_templates:
|
||||
settings = {}
|
||||
file_options = {}
|
||||
file_option_dirs = {}
|
||||
for template in self.normalize_output_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['output_file_template_settings'] = settings
|
||||
context['output_file_options'] = file_options
|
||||
context['output_file_option_dirs'] = file_option_dirs
|
||||
|
||||
return context
|
||||
|
||||
def configure_gather_settings(self, data, simple_settings=None,
|
||||
input_file_templates=True):
|
||||
input_file_templates=True,
|
||||
output_file_templates=True):
|
||||
settings = []
|
||||
|
||||
# maybe collect "simple" settings
|
||||
|
@ -5354,10 +5513,30 @@ class MasterView(View):
|
|||
settings.append({'name': template['setting_url'],
|
||||
'value': data.get(template['setting_url'])})
|
||||
|
||||
# maybe also collect output file template settings
|
||||
if output_file_templates and self.has_output_file_templates:
|
||||
for template in self.normalize_output_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
|
||||
|
||||
def configure_remove_settings(self, simple_settings=None,
|
||||
input_file_templates=True):
|
||||
input_file_templates=True,
|
||||
output_file_templates=True):
|
||||
app = self.get_rattail_app()
|
||||
model = self.model
|
||||
names = []
|
||||
|
@ -5376,6 +5555,14 @@ class MasterView(View):
|
|||
template['setting_url'],
|
||||
])
|
||||
|
||||
if output_file_templates and self.has_output_file_templates:
|
||||
for template in self.normalize_output_file_templates():
|
||||
names.extend([
|
||||
template['setting_mode'],
|
||||
template['setting_file'],
|
||||
template['setting_url'],
|
||||
])
|
||||
|
||||
if names:
|
||||
# nb. using thread-local session here; we do not use
|
||||
# self.Session b/c it may not point to Rattail
|
||||
|
@ -5638,6 +5825,15 @@ class MasterView(View):
|
|||
route_name='{}.download_input_file_template'.format(route_prefix),
|
||||
permission='{}.create'.format(permission_prefix))
|
||||
|
||||
# download output file template
|
||||
if cls.has_output_file_templates and cls.configurable:
|
||||
config.add_route(f'{route_prefix}.download_output_file_template',
|
||||
f'{url_prefix}/download-output-file-template')
|
||||
config.add_view(cls, attr='download_output_file_template',
|
||||
route_name=f'{route_prefix}.download_output_file_template',
|
||||
# TODO: this is different from input file, should change?
|
||||
permission=f'{permission_prefix}.configure')
|
||||
|
||||
# view
|
||||
if cls.viewable:
|
||||
cls._defaults_view(config)
|
||||
|
|
Loading…
Reference in a new issue