feat: add new 'waterpark' theme, based on wuttaweb w/ vue2 + buefy
hoping to eventually replace the 'default' view with this one, if all goes well. definitely needs more testing and is not exposed as an option yet, unless configured
This commit is contained in:
parent
1ec1eba496
commit
59bd58aca7
|
@ -321,7 +321,8 @@ def main(global_config, **settings):
|
||||||
"""
|
"""
|
||||||
This function returns a Pyramid WSGI application.
|
This function returns a Pyramid WSGI application.
|
||||||
"""
|
"""
|
||||||
settings.setdefault('mako.directories', ['tailbone:templates'])
|
settings.setdefault('mako.directories', ['tailbone:templates',
|
||||||
|
'wuttaweb:templates'])
|
||||||
rattail_config = make_rattail_config(settings)
|
rattail_config = make_rattail_config(settings)
|
||||||
pyramid_config = make_pyramid_config(settings)
|
pyramid_config = make_pyramid_config(settings)
|
||||||
pyramid_config.include('tailbone')
|
pyramid_config.include('tailbone')
|
||||||
|
|
|
@ -905,7 +905,8 @@ class Form(object):
|
||||||
|
|
||||||
def render_vue_template(self, template='/forms/deform.mako', **context):
|
def render_vue_template(self, template='/forms/deform.mako', **context):
|
||||||
""" """
|
""" """
|
||||||
return self.render_deform(template=template, **context)
|
output = self.render_deform(template=template, **context)
|
||||||
|
return HTML.literal(output)
|
||||||
|
|
||||||
def render_deform(self, dform=None, template=None, **kwargs):
|
def render_deform(self, dform=None, template=None, **kwargs):
|
||||||
if not template:
|
if not template:
|
||||||
|
@ -1220,6 +1221,18 @@ class Form(object):
|
||||||
# TODO: again, why does serialize() not return literal?
|
# TODO: again, why does serialize() not return literal?
|
||||||
return HTML.literal(field.serialize())
|
return HTML.literal(field.serialize())
|
||||||
|
|
||||||
|
# TODO: this was copied from wuttaweb; can remove when we align
|
||||||
|
# Form class structure
|
||||||
|
def render_vue_finalize(self):
|
||||||
|
""" """
|
||||||
|
set_data = f"{self.vue_component}.data = function() {{ return {self.vue_component}Data }}"
|
||||||
|
make_component = f"Vue.component('{self.vue_tagname}', {self.vue_component})"
|
||||||
|
return HTML.tag('script', c=['\n',
|
||||||
|
HTML.literal(set_data),
|
||||||
|
'\n',
|
||||||
|
HTML.literal(make_component),
|
||||||
|
'\n'])
|
||||||
|
|
||||||
def render_field_readonly(self, field_name, **kwargs):
|
def render_field_readonly(self, field_name, **kwargs):
|
||||||
"""
|
"""
|
||||||
Render the given field completely, but in read-only fashion.
|
Render the given field completely, but in read-only fashion.
|
||||||
|
|
|
@ -216,39 +216,39 @@ class Grid(WuttaGrid):
|
||||||
expose_direct_link=False,
|
expose_direct_link=False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if kwargs.get('component'):
|
if 'component' in kwargs:
|
||||||
warnings.warn("component param is deprecated for Grid(); "
|
warnings.warn("component param is deprecated for Grid(); "
|
||||||
"please use vue_tagname param instead",
|
"please use vue_tagname param instead",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
kwargs.setdefault('vue_tagname', kwargs.pop('component'))
|
kwargs.setdefault('vue_tagname', kwargs.pop('component'))
|
||||||
|
|
||||||
if kwargs.get('default_sortkey'):
|
if 'default_sortkey' in kwargs:
|
||||||
warnings.warn("default_sortkey param is deprecated for Grid(); "
|
warnings.warn("default_sortkey param is deprecated for Grid(); "
|
||||||
"please use sort_defaults param instead",
|
"please use sort_defaults param instead",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
if kwargs.get('default_sortdir'):
|
if 'default_sortdir' in kwargs:
|
||||||
warnings.warn("default_sortdir param is deprecated for Grid(); "
|
warnings.warn("default_sortdir param is deprecated for Grid(); "
|
||||||
"please use sort_defaults param instead",
|
"please use sort_defaults param instead",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
if kwargs.get('default_sortkey') or kwargs.get('default_sortdir'):
|
if 'default_sortkey' in kwargs or 'default_sortdir' in kwargs:
|
||||||
sortkey = kwargs.pop('default_sortkey', None)
|
sortkey = kwargs.pop('default_sortkey', None)
|
||||||
sortdir = kwargs.pop('default_sortdir', 'asc')
|
sortdir = kwargs.pop('default_sortdir', 'asc')
|
||||||
if sortkey:
|
if sortkey:
|
||||||
kwargs.setdefault('sort_defaults', [(sortkey, sortdir)])
|
kwargs.setdefault('sort_defaults', [(sortkey, sortdir)])
|
||||||
|
|
||||||
if kwargs.get('pageable'):
|
if 'pageable' in kwargs:
|
||||||
warnings.warn("pageable param is deprecated for Grid(); "
|
warnings.warn("pageable param is deprecated for Grid(); "
|
||||||
"please use vue_tagname param instead",
|
"please use vue_tagname param instead",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
kwargs.setdefault('paginated', kwargs.pop('pageable'))
|
kwargs.setdefault('paginated', kwargs.pop('pageable'))
|
||||||
|
|
||||||
if kwargs.get('default_pagesize'):
|
if 'default_pagesize' in kwargs:
|
||||||
warnings.warn("default_pagesize param is deprecated for Grid(); "
|
warnings.warn("default_pagesize param is deprecated for Grid(); "
|
||||||
"please use pagesize param instead",
|
"please use pagesize param instead",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
kwargs.setdefault('pagesize', kwargs.pop('default_pagesize'))
|
kwargs.setdefault('pagesize', kwargs.pop('default_pagesize'))
|
||||||
|
|
||||||
if kwargs.get('default_page'):
|
if 'default_page' in kwargs:
|
||||||
warnings.warn("default_page param is deprecated for Grid(); "
|
warnings.warn("default_page param is deprecated for Grid(); "
|
||||||
"please use page param instead",
|
"please use page param instead",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,9 +24,8 @@
|
||||||
Static Assets
|
Static Assets
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
config.include('wuttaweb.static')
|
||||||
config.add_static_view('tailbone', 'tailbone:static')
|
config.add_static_view('tailbone', 'tailbone:static')
|
||||||
config.add_static_view('deform', 'deform:static')
|
config.add_static_view('deform', 'deform:static')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/master/index.mako" />
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
<%def name="render_grid_component()">
|
<%def name="page_content()">
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
|
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div style="width: 100%;">
|
<div style="width: 100%;">
|
||||||
${parent.render_grid_component()}
|
${grid.render_vue_tag()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</${b}-collapse>
|
</${b}-collapse>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
|
<%namespace file="/wutta-components.mako" import="make_wutta_components" />
|
||||||
<%namespace file="/grids/nav.mako" import="grid_index_nav" />
|
<%namespace file="/grids/nav.mako" import="grid_index_nav" />
|
||||||
<%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
|
<%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
|
||||||
<%namespace name="base_meta" file="/base_meta.mako" />
|
<%namespace name="base_meta" file="/base_meta.mako" />
|
||||||
|
@ -955,6 +956,7 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="make_vue_components()">
|
<%def name="make_vue_components()">
|
||||||
|
${make_wutta_components()}
|
||||||
${make_grid_filter_components()}
|
${make_grid_filter_components()}
|
||||||
${page_help.make_component()}
|
${page_help.make_component()}
|
||||||
${multi_file_upload.make_component()}
|
${multi_file_upload.make_component()}
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<br />
|
<br />
|
||||||
<div class="form-wrapper">
|
<div class="form-wrapper">
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<${execute_form.component} ref="executeResultsForm"></${execute_form.component}>
|
${execute_form.render_vue_tag(ref='executeResultsForm')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<%def name="render_vue_templates()">
|
<%def name="render_vue_templates()">
|
||||||
${parent.render_vue_templates()}
|
${parent.render_vue_templates()}
|
||||||
% if master.results_executable and master.has_perm('execute_multiple'):
|
% if master.results_executable and master.has_perm('execute_multiple'):
|
||||||
${execute_form.render_deform(form_kwargs={'ref': 'actualExecuteForm'}, buttons=False)|n}
|
${execute_form.render_vue_template(form_kwargs={'ref': 'actualExecuteForm'}, buttons=False)}
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -128,9 +128,6 @@
|
||||||
<%def name="make_vue_components()">
|
<%def name="make_vue_components()">
|
||||||
${parent.make_vue_components()}
|
${parent.make_vue_components()}
|
||||||
% if master.results_executable and master.has_perm('execute_multiple'):
|
% if master.results_executable and master.has_perm('execute_multiple'):
|
||||||
<script>
|
${execute_form.render_vue_finalize()}
|
||||||
${execute_form.vue_component}.data = function() { return ${execute_form.vue_component}Data }
|
|
||||||
Vue.component('${execute_form.vue_tagname}', ${execute_form.vue_component})
|
|
||||||
</script>
|
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
|
@ -119,8 +119,7 @@
|
||||||
<div class="markdown">
|
<div class="markdown">
|
||||||
${execution_described|n}
|
${execution_described|n}
|
||||||
</div>
|
</div>
|
||||||
<${execute_form.component} ref="executeBatchForm">
|
${execute_form.render_vue_tag(ref='executeBatchForm')}
|
||||||
</${execute_form.component}>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer class="modal-card-foot">
|
<footer class="modal-card-foot">
|
||||||
|
@ -168,8 +167,7 @@
|
||||||
Please be certain to use the right one!
|
Please be certain to use the right one!
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<${upload_worksheet_form.component} ref="uploadForm">
|
${upload_worksheet_form.render_vue_tag(ref='uploadForm')}
|
||||||
</${upload_worksheet_form.component}>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer class="modal-card-foot">
|
<footer class="modal-card-foot">
|
||||||
|
@ -254,10 +252,10 @@
|
||||||
<%def name="render_vue_templates()">
|
<%def name="render_vue_templates()">
|
||||||
${parent.render_vue_templates()}
|
${parent.render_vue_templates()}
|
||||||
% if master.has_worksheet_file and master.allow_worksheet(batch) and master.has_perm('worksheet'):
|
% if master.has_worksheet_file and master.allow_worksheet(batch) and master.has_perm('worksheet'):
|
||||||
${upload_worksheet_form.render_deform(buttons=False, form_kwargs={'ref': 'actualUploadForm'})|n}
|
${upload_worksheet_form.render_vue_template(buttons=False, form_kwargs={'ref': 'actualUploadForm'})}
|
||||||
% endif
|
% endif
|
||||||
% if master.handler.executable(batch) and master.has_perm('execute'):
|
% if master.handler.executable(batch) and master.has_perm('execute'):
|
||||||
${execute_form.render_deform(form_kwargs={'ref': 'actualExecuteForm'}, buttons=False)|n}
|
${execute_form.render_vue_template(form_kwargs={'ref': 'actualExecuteForm'}, buttons=False)}
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -345,15 +343,9 @@
|
||||||
<%def name="make_vue_components()">
|
<%def name="make_vue_components()">
|
||||||
${parent.make_vue_components()}
|
${parent.make_vue_components()}
|
||||||
% if master.has_worksheet_file and master.allow_worksheet(batch) and master.has_perm('worksheet'):
|
% if master.has_worksheet_file and master.allow_worksheet(batch) and master.has_perm('worksheet'):
|
||||||
<script>
|
${upload_worksheet_form.render_vue_finalize()}
|
||||||
${upload_worksheet_form.vue_component}.data = function() { return ${upload_worksheet_form.vue_component}Data }
|
|
||||||
Vue.component('${upload_worksheet_form.component}', ${upload_worksheet_form.vue_component})
|
|
||||||
</script>
|
|
||||||
% endif
|
% endif
|
||||||
% if execute_enabled and master.has_perm('execute'):
|
% if execute_enabled and master.has_perm('execute'):
|
||||||
<script>
|
${execute_form.render_vue_finalize()}
|
||||||
${execute_form.vue_component}.data = function() { return ${execute_form.vue_component}Data }
|
|
||||||
Vue.component('${execute_form.component}', ${execute_form.vue_component})
|
|
||||||
</script>
|
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
|
@ -109,9 +109,6 @@
|
||||||
<%def name="make_vue_components()">
|
<%def name="make_vue_components()">
|
||||||
${parent.make_vue_components()}
|
${parent.make_vue_components()}
|
||||||
% if form is not Undefined:
|
% if form is not Undefined:
|
||||||
<script>
|
${form.render_vue_finalize()}
|
||||||
${form.vue_component}.data = function() { return ${form.vue_component}Data }
|
|
||||||
Vue.component('${form.vue_tagname}', ${form.vue_component})
|
|
||||||
</script>
|
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
486
tailbone/templates/themes/waterpark/base.mako
Normal file
486
tailbone/templates/themes/waterpark/base.mako
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/base.mako" />
|
||||||
|
<%namespace name="base_meta" file="/base_meta.mako" />
|
||||||
|
<%namespace file="/formposter.mako" import="declare_formposter_mixin" />
|
||||||
|
<%namespace file="/grids/filter-components.mako" import="make_grid_filter_components" />
|
||||||
|
<%namespace name="page_help" file="/page_help.mako" />
|
||||||
|
|
||||||
|
<%def name="base_styles()">
|
||||||
|
${parent.base_styles()}
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.filters .filter-fieldname .field,
|
||||||
|
.filters .filter-fieldname .field label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters .filter-fieldname,
|
||||||
|
.filters .filter-fieldname .field label,
|
||||||
|
.filters .filter-fieldname .button {
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters .filter-verb .select,
|
||||||
|
.filters .filter-verb .select select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
% if filter_fieldname_width is not Undefined:
|
||||||
|
|
||||||
|
.filters .filter-fieldname,
|
||||||
|
.filters .filter-fieldname .button {
|
||||||
|
min-width: ${filter_fieldname_width};
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters .filter-verb {
|
||||||
|
min-width: ${filter_verb_width};
|
||||||
|
}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="before_content()">
|
||||||
|
## TODO: this must come before the self.body() call..but why?
|
||||||
|
${declare_formposter_mixin()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_navbar_brand()">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="${url('home')}"
|
||||||
|
v-show="!menuSearchActive">
|
||||||
|
${base_meta.header_logo()}
|
||||||
|
<div id="global-header-title">
|
||||||
|
${base_meta.global_title()}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div v-show="menuSearchActive"
|
||||||
|
class="navbar-item">
|
||||||
|
<b-autocomplete ref="menuSearchAutocomplete"
|
||||||
|
v-model="menuSearchTerm"
|
||||||
|
:data="menuSearchFilteredData"
|
||||||
|
field="label"
|
||||||
|
open-on-focus
|
||||||
|
keep-first
|
||||||
|
icon-pack="fas"
|
||||||
|
clearable
|
||||||
|
@keydown.native="menuSearchKeydown"
|
||||||
|
@select="menuSearchSelect">
|
||||||
|
</b-autocomplete>
|
||||||
|
</div>
|
||||||
|
<a role="button" class="navbar-burger" data-target="navbar-menu" aria-label="menu" aria-expanded="false">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_navbar_start()">
|
||||||
|
<div class="navbar-start">
|
||||||
|
|
||||||
|
<div v-if="menuSearchData.length"
|
||||||
|
class="navbar-item">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
size="is-small"
|
||||||
|
@click="menuSearchInit()">
|
||||||
|
<span><i class="fa fa-search"></i></span>
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
% for topitem in menus:
|
||||||
|
% if topitem['is_link']:
|
||||||
|
${h.link_to(topitem['title'], topitem['url'], target=topitem['target'], class_='navbar-item')}
|
||||||
|
% else:
|
||||||
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<a class="navbar-link">${topitem['title']}</a>
|
||||||
|
<div class="navbar-dropdown">
|
||||||
|
% for item in topitem['items']:
|
||||||
|
% if item['is_menu']:
|
||||||
|
<% item_hash = id(item) %>
|
||||||
|
<% toggle = 'menu_{}_shown'.format(item_hash) %>
|
||||||
|
<div>
|
||||||
|
<a class="navbar-link" @click.prevent="toggleNestedMenu('${item_hash}')">
|
||||||
|
${item['title']}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
% for subitem in item['items']:
|
||||||
|
% if subitem['is_sep']:
|
||||||
|
<hr class="navbar-divider" v-show="${toggle}">
|
||||||
|
% else:
|
||||||
|
${h.link_to("{}".format(subitem['title']), subitem['url'], class_='navbar-item nested', target=subitem['target'], **{'v-show': toggle})}
|
||||||
|
% endif
|
||||||
|
% endfor
|
||||||
|
% else:
|
||||||
|
% if item['is_sep']:
|
||||||
|
<hr class="navbar-divider">
|
||||||
|
% else:
|
||||||
|
${h.link_to(item['title'], item['url'], class_='navbar-item', target=item['target'])}
|
||||||
|
% endif
|
||||||
|
% endif
|
||||||
|
% endfor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_theme_picker()">
|
||||||
|
% if expose_theme_picker and request.has_perm('common.change_app_theme'):
|
||||||
|
<div class="level-item">
|
||||||
|
${h.form(url('change_theme'), method="post", ref='themePickerForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
<input type="hidden" name="referrer" :value="referrer" />
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||||
|
<span>Theme:</span>
|
||||||
|
<b-select name="theme"
|
||||||
|
v-model="globalTheme"
|
||||||
|
@input="changeTheme()">
|
||||||
|
% for option in theme_picker_options:
|
||||||
|
<option value="${option.value}">
|
||||||
|
${option.label}
|
||||||
|
</option>
|
||||||
|
% endfor
|
||||||
|
</b-select>
|
||||||
|
</div>
|
||||||
|
${h.end_form()}
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_feedback_button()">
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<page-help
|
||||||
|
% if can_edit_help:
|
||||||
|
@configure-fields-help="configureFieldsHelp = true"
|
||||||
|
% endif
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
% if request.has_perm('common.feedback'):
|
||||||
|
<feedback-form
|
||||||
|
action="${url('feedback')}"
|
||||||
|
:message="feedbackMessage">
|
||||||
|
</feedback-form>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_this_page_component()">
|
||||||
|
<this-page @change-content-title="changeContentTitle"
|
||||||
|
% if can_edit_help:
|
||||||
|
:configure-fields-help="configureFieldsHelp"
|
||||||
|
% endif
|
||||||
|
/>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_vue_templates()">
|
||||||
|
${parent.render_vue_templates()}
|
||||||
|
|
||||||
|
${page_help.render_template()}
|
||||||
|
${page_help.declare_vars()}
|
||||||
|
|
||||||
|
% if request.has_perm('common.feedback'):
|
||||||
|
<script type="text/x-template" id="feedback-template">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="showFeedback()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="comment">
|
||||||
|
Feedback
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="showDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">User Feedback</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<p class="block">
|
||||||
|
Questions, suggestions, comments, complaints, etc.
|
||||||
|
<span class="red">regarding this website</span> are
|
||||||
|
welcome and may be submitted below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b-field label="User Name">
|
||||||
|
<b-input v-model="userName"
|
||||||
|
% if request.user:
|
||||||
|
disabled
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Referring URL">
|
||||||
|
<b-input
|
||||||
|
v-model="referrer"
|
||||||
|
disabled="true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Message">
|
||||||
|
<b-input type="textarea"
|
||||||
|
v-model="message"
|
||||||
|
ref="textarea">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<b-checkbox v-model="pleaseReply"
|
||||||
|
@input="pleaseReplyChanged">
|
||||||
|
Please email me back{{ pleaseReply ? " at: " : "" }}
|
||||||
|
</b-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="level-item" v-show="pleaseReply">
|
||||||
|
<b-input v-model="userEmail"
|
||||||
|
ref="userEmail">
|
||||||
|
</b-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="showDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="paper-plane"
|
||||||
|
@click="sendFeedback()"
|
||||||
|
:disabled="sendingFeedback || !message.trim()">
|
||||||
|
{{ sendingFeedback ? "Working, please wait..." : "Send Message" }}
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const FeedbackForm = {
|
||||||
|
template: '#feedback-template',
|
||||||
|
mixins: [SimpleRequestMixin],
|
||||||
|
props: [
|
||||||
|
'action',
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
showFeedback() {
|
||||||
|
this.referrer = location.href
|
||||||
|
this.showDialog = true
|
||||||
|
this.$nextTick(function() {
|
||||||
|
this.$refs.textarea.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||||
|
pleaseReplyChanged(value) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.userEmail.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
% endif
|
||||||
|
|
||||||
|
sendFeedback() {
|
||||||
|
this.sendingFeedback = true
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
referrer: this.referrer,
|
||||||
|
user: this.userUUID,
|
||||||
|
user_name: this.userName,
|
||||||
|
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||||
|
please_reply_to: this.pleaseReply ? this.userEmail : null,
|
||||||
|
% endif
|
||||||
|
message: this.message.trim(),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.simplePOST(this.action, params, response => {
|
||||||
|
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: "Message sent! Thank you for your feedback.",
|
||||||
|
type: 'is-info',
|
||||||
|
duration: 4000, // 4 seconds
|
||||||
|
})
|
||||||
|
|
||||||
|
this.showDialog = false
|
||||||
|
// clear out message, in case they need to send another
|
||||||
|
this.message = ""
|
||||||
|
this.sendingFeedback = false
|
||||||
|
|
||||||
|
}, response => { // failure
|
||||||
|
this.sendingFeedback = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FeedbackFormData = {
|
||||||
|
referrer: null,
|
||||||
|
userUUID: null,
|
||||||
|
userName: null,
|
||||||
|
userEmail: null,
|
||||||
|
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||||
|
pleaseReply: false,
|
||||||
|
% endif
|
||||||
|
showDialog: false,
|
||||||
|
sendingFeedback: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_vue_vars()">
|
||||||
|
${parent.modify_vue_vars()}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## menu search
|
||||||
|
##############################
|
||||||
|
|
||||||
|
WholePageData.menuSearchActive = false
|
||||||
|
WholePageData.menuSearchTerm = ''
|
||||||
|
WholePageData.menuSearchData = ${json.dumps(global_search_data or [])|n}
|
||||||
|
|
||||||
|
WholePage.computed.menuSearchFilteredData = function() {
|
||||||
|
if (!this.menuSearchTerm.length) {
|
||||||
|
return this.menuSearchData
|
||||||
|
}
|
||||||
|
|
||||||
|
const terms = []
|
||||||
|
for (let term of this.menuSearchTerm.toLowerCase().split(' ')) {
|
||||||
|
term = term.trim()
|
||||||
|
if (term) {
|
||||||
|
terms.push(term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!terms.length) {
|
||||||
|
return this.menuSearchData
|
||||||
|
}
|
||||||
|
|
||||||
|
// all terms must match
|
||||||
|
return this.menuSearchData.filter((option) => {
|
||||||
|
const label = option.label.toLowerCase()
|
||||||
|
for (const term of terms) {
|
||||||
|
if (label.indexOf(term) < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
WholePage.methods.globalKey = function(event) {
|
||||||
|
|
||||||
|
// Ctrl+8 opens menu search
|
||||||
|
if (event.target.tagName == 'BODY') {
|
||||||
|
if (event.ctrlKey && event.key == '8') {
|
||||||
|
this.menuSearchInit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WholePage.mounted = function() {
|
||||||
|
window.addEventListener('keydown', this.globalKey)
|
||||||
|
for (let hook of this.mountedHooks) {
|
||||||
|
hook(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WholePage.beforeDestroy = function() {
|
||||||
|
window.removeEventListener('keydown', this.globalKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
WholePage.methods.menuSearchInit = function() {
|
||||||
|
this.menuSearchTerm = ''
|
||||||
|
this.menuSearchActive = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.menuSearchAutocomplete.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
WholePage.methods.menuSearchKeydown = function(event) {
|
||||||
|
|
||||||
|
// ESC will dismiss searchbox
|
||||||
|
if (event.which == 27) {
|
||||||
|
this.menuSearchActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WholePage.methods.menuSearchSelect = function(option) {
|
||||||
|
location.href = option.url
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## theme picker
|
||||||
|
##############################
|
||||||
|
|
||||||
|
% if expose_theme_picker and request.has_perm('common.change_app_theme'):
|
||||||
|
|
||||||
|
WholePageData.globalTheme = ${json.dumps(theme or None)|n}
|
||||||
|
## WholePageData.referrer = location.href
|
||||||
|
|
||||||
|
WholePage.methods.changeTheme = function() {
|
||||||
|
this.$refs.themePickerForm.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## feedback
|
||||||
|
##############################
|
||||||
|
|
||||||
|
% if request.has_perm('common.feedback'):
|
||||||
|
|
||||||
|
WholePageData.feedbackMessage = ""
|
||||||
|
|
||||||
|
% if request.user:
|
||||||
|
FeedbackFormData.userUUID = ${json.dumps(request.user.uuid)|n}
|
||||||
|
FeedbackFormData.userName = ${json.dumps(str(request.user))|n}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## edit fields help
|
||||||
|
##############################
|
||||||
|
|
||||||
|
% if can_edit_help:
|
||||||
|
WholePageData.configureFieldsHelp = false
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="make_vue_components()">
|
||||||
|
${parent.make_vue_components()}
|
||||||
|
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.datepicker.js') + f'?ver={tailbone.__version__}')}
|
||||||
|
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.numericinput.js') + f'?ver={tailbone.__version__}')}
|
||||||
|
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.oncebutton.js') + f'?ver={tailbone.__version__}')}
|
||||||
|
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.timepicker.js') + f'?ver={tailbone.__version__}')}
|
||||||
|
${make_grid_filter_components()}
|
||||||
|
${page_help.make_component()}
|
||||||
|
% if request.has_perm('common.feedback'):
|
||||||
|
<script>
|
||||||
|
FeedbackForm.data = function() { return FeedbackFormData }
|
||||||
|
Vue.component('feedback-form', FeedbackForm)
|
||||||
|
</script>
|
||||||
|
% endif
|
||||||
|
</%def>
|
2
tailbone/templates/themes/waterpark/configure.mako
Normal file
2
tailbone/templates/themes/waterpark/configure.mako
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/configure.mako" />
|
2
tailbone/templates/themes/waterpark/form.mako
Normal file
2
tailbone/templates/themes/waterpark/form.mako
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/form.mako" />
|
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/master/configure.mako" />
|
2
tailbone/templates/themes/waterpark/master/create.mako
Normal file
2
tailbone/templates/themes/waterpark/master/create.mako
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/master/create.mako" />
|
46
tailbone/templates/themes/waterpark/master/delete.mako
Normal file
46
tailbone/templates/themes/waterpark/master/delete.mako
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="tailbone:templates/form.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Delete ${model_title}: ${instance_title}</%def>
|
||||||
|
|
||||||
|
<%def name="render_form()">
|
||||||
|
<br />
|
||||||
|
<b-notification type="is-danger" :closable="false">
|
||||||
|
You are about to delete the following ${model_title} and all associated data:
|
||||||
|
</b-notification>
|
||||||
|
${parent.render_form()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_form_buttons()">
|
||||||
|
<br />
|
||||||
|
<b-notification type="is-danger" :closable="false">
|
||||||
|
Are you sure about this?
|
||||||
|
</b-notification>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
${h.form(request.current_route_url(), **{'@submit': 'submitForm'})}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
<div class="buttons">
|
||||||
|
<wutta-button once tag="a" href="${form.cancel_url}"
|
||||||
|
label="Whoops, nevermind..." />
|
||||||
|
<b-button type="is-primary is-danger"
|
||||||
|
native-type="submit"
|
||||||
|
:disabled="formSubmitting">
|
||||||
|
{{ formSubmitting ? "Working, please wait..." : "${form.button_label_submit}" }}
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
${h.end_form()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_vue_vars()">
|
||||||
|
${parent.modify_vue_vars()}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
${form.vue_component}Data.formSubmitting = false
|
||||||
|
|
||||||
|
${form.vue_component}.methods.submitForm = function() {
|
||||||
|
this.formSubmitting = true
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
2
tailbone/templates/themes/waterpark/master/edit.mako
Normal file
2
tailbone/templates/themes/waterpark/master/edit.mako
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/master/edit.mako" />
|
2
tailbone/templates/themes/waterpark/master/form.mako
Normal file
2
tailbone/templates/themes/waterpark/master/form.mako
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/master/form.mako" />
|
294
tailbone/templates/themes/waterpark/master/index.mako
Normal file
294
tailbone/templates/themes/waterpark/master/index.mako
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="grid_tools()">
|
||||||
|
|
||||||
|
## grid totals
|
||||||
|
% if getattr(master, 'supports_grid_totals', False):
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<b-button v-if="gridTotalsDisplay == null"
|
||||||
|
:disabled="gridTotalsFetching"
|
||||||
|
@click="gridTotalsFetch()">
|
||||||
|
{{ gridTotalsFetching ? "Working, please wait..." : "Show Totals" }}
|
||||||
|
</b-button>
|
||||||
|
<div v-if="gridTotalsDisplay != null"
|
||||||
|
class="control">
|
||||||
|
Totals: {{ gridTotalsDisplay }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
## download search results
|
||||||
|
% if getattr(master, 'results_downloadable', False) and master.has_perm('download_results'):
|
||||||
|
<div>
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="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; justify-content: space-between">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<b-field label="Format">
|
||||||
|
<b-select v-model="downloadResultsFormat">
|
||||||
|
% for key, label in master.download_results_supported_formats().items():
|
||||||
|
<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>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons is-right">
|
||||||
|
<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 v-show="downloadResultsFieldsMode == 'choose'">
|
||||||
|
<div style="display: flex;">
|
||||||
|
<div>
|
||||||
|
<b-field label="Excluded Fields">
|
||||||
|
<b-select multiple native-size="8"
|
||||||
|
expanded
|
||||||
|
v-model="downloadResultsExcludedFieldsSelected"
|
||||||
|
ref="downloadResultsExcludedFields">
|
||||||
|
<option v-for="field in downloadResultsFieldsExcluded"
|
||||||
|
:key="field"
|
||||||
|
:value="field">
|
||||||
|
{{ field }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<br /><br />
|
||||||
|
<b-button style="margin: 0.5rem;"
|
||||||
|
@click="downloadResultsExcludeFields()">
|
||||||
|
<
|
||||||
|
</b-button>
|
||||||
|
<br />
|
||||||
|
<b-button style="margin: 0.5rem;"
|
||||||
|
@click="downloadResultsIncludeFields()">
|
||||||
|
>
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b-field label="Included Fields">
|
||||||
|
<b-select multiple native-size="8"
|
||||||
|
expanded
|
||||||
|
v-model="downloadResultsIncludedFieldsSelected"
|
||||||
|
ref="downloadResultsIncludedFields">
|
||||||
|
<option v-for="field in downloadResultsFieldsIncluded"
|
||||||
|
:key="field"
|
||||||
|
:value="field">
|
||||||
|
{{ field }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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="download"
|
||||||
|
:disabled="!downloadResultsFieldsIncluded.length"
|
||||||
|
text="Download Results">
|
||||||
|
</once-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
## download rows for search results
|
||||||
|
% if getattr(master, 'has_rows', False) and master.results_rows_downloadable and master.has_perm('download_results_rows'):
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="download"
|
||||||
|
@click="downloadResultsRows()"
|
||||||
|
:disabled="downloadResultsRowsButtonDisabled">
|
||||||
|
{{ downloadResultsRowsButtonText }}
|
||||||
|
</b-button>
|
||||||
|
${h.form(url('{}.download_results_rows'.format(route_prefix)), ref='downloadResultsRowsForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
## merge 2 objects
|
||||||
|
% if getattr(master, 'mergeable', False) and request.has_perm('{}.merge'.format(permission_prefix)):
|
||||||
|
|
||||||
|
${h.form(url('{}.merge'.format(route_prefix)), class_='control', **{'@submit': 'submitMergeForm'})}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
<input type="hidden"
|
||||||
|
name="uuids"
|
||||||
|
:value="checkedRowUUIDs()" />
|
||||||
|
<b-button type="is-primary"
|
||||||
|
native-type="submit"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="object-ungroup"
|
||||||
|
:disabled="mergeFormSubmitting || checkedRows.length != 2">
|
||||||
|
{{ mergeFormButtonText }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
## enable / disable selected objects
|
||||||
|
% if getattr(master, 'supports_set_enabled_toggle', False) and master.has_perm('enable_disable_set'):
|
||||||
|
|
||||||
|
${h.form(url('{}.enable_set'.format(route_prefix)), class_='control', ref='enable_selected_form')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('uuids', v_model='selected_uuids')}
|
||||||
|
<b-button :disabled="enableSelectedDisabled"
|
||||||
|
@click="enableSelectedSubmit()">
|
||||||
|
{{ enableSelectedText }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
|
||||||
|
${h.form(url('{}.disable_set'.format(route_prefix)), ref='disable_selected_form', class_='control')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('uuids', v_model='selected_uuids')}
|
||||||
|
<b-button :disabled="disableSelectedDisabled"
|
||||||
|
@click="disableSelectedSubmit()">
|
||||||
|
{{ disableSelectedText }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
## delete selected objects
|
||||||
|
% if getattr(master, 'set_deletable', False) and master.has_perm('delete_set'):
|
||||||
|
${h.form(url('{}.delete_set'.format(route_prefix)), ref='delete_selected_form', class_='control')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('uuids', v_model='selected_uuids')}
|
||||||
|
<b-button type="is-danger"
|
||||||
|
:disabled="deleteSelectedDisabled"
|
||||||
|
@click="deleteSelectedSubmit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="trash">
|
||||||
|
{{ deleteSelectedText }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
## delete search results
|
||||||
|
% if getattr(master, 'bulk_deletable', False) and request.has_perm('{}.bulk_delete'.format(permission_prefix)):
|
||||||
|
${h.form(url('{}.bulk_delete'.format(route_prefix)), ref='delete_results_form', class_='control')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
<b-button type="is-danger"
|
||||||
|
:disabled="deleteResultsDisabled"
|
||||||
|
:title="total ? null : 'There are no results to delete'"
|
||||||
|
@click="deleteResultsSubmit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="trash">
|
||||||
|
{{ deleteResultsText }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_vue_template_grid()">
|
||||||
|
${grid.render_vue_template(tools=capture(self.grid_tools).strip(), context_menu=capture(self.context_menu_items).strip())}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_vue_vars()">
|
||||||
|
${parent.modify_vue_vars()}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
% if getattr(master, 'bulk_deletable', False) and master.has_perm('bulk_delete'):
|
||||||
|
|
||||||
|
${grid.vue_component}Data.deleteResultsSubmitting = false
|
||||||
|
${grid.vue_component}Data.deleteResultsText = "Delete Results"
|
||||||
|
|
||||||
|
${grid.vue_component}.computed.deleteResultsDisabled = function() {
|
||||||
|
if (this.deleteResultsSubmitting) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!this.total) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
${grid.vue_component}.methods.deleteResultsSubmit = function() {
|
||||||
|
// TODO: show "plural model title" here?
|
||||||
|
if (!confirm("You are about to delete " + this.total.toLocaleString('en') + " objects.\n\nAre you sure?")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteResultsSubmitting = true
|
||||||
|
this.deleteResultsText = "Working, please wait..."
|
||||||
|
this.$refs.delete_results_form.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
2
tailbone/templates/themes/waterpark/master/view.mako
Normal file
2
tailbone/templates/themes/waterpark/master/view.mako
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/master/view.mako" />
|
48
tailbone/templates/themes/waterpark/page.mako
Normal file
48
tailbone/templates/themes/waterpark/page.mako
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="wuttaweb:templates/page.mako" />
|
||||||
|
|
||||||
|
<%def name="render_vue_template_this_page()">
|
||||||
|
<script type="text/x-template" id="this-page-template">
|
||||||
|
<div style="height: 100%;">
|
||||||
|
## DEPRECATED; called for back-compat
|
||||||
|
${self.render_this_page()}
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
## DEPRECATED; remains for back-compat
|
||||||
|
<%def name="render_this_page()">
|
||||||
|
<div style="display: flex;">
|
||||||
|
|
||||||
|
<div class="this-page-content" style="flex-grow: 1;">
|
||||||
|
${self.page_content()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## DEPRECATED; remains for back-compat
|
||||||
|
<ul id="context-menu">
|
||||||
|
${self.context_menu_items()}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
## DEPRECATED; remains for back-compat
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
% if context_menu_list_items is not Undefined:
|
||||||
|
% for item in context_menu_list_items:
|
||||||
|
<li>${item}</li>
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_vue_vars()">
|
||||||
|
${parent.modify_vue_vars()}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
ThisPageData.csrftoken = ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n}
|
||||||
|
|
||||||
|
% if can_edit_help:
|
||||||
|
ThisPage.props.configureFieldsHelp = Boolean
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
|
@ -137,6 +137,7 @@ class MasterView(View):
|
||||||
deleting = False
|
deleting = False
|
||||||
executing = False
|
executing = False
|
||||||
cloning = False
|
cloning = False
|
||||||
|
configuring = False
|
||||||
has_pk_fields = False
|
has_pk_fields = False
|
||||||
has_image = False
|
has_image = False
|
||||||
has_thumbnail = False
|
has_thumbnail = False
|
||||||
|
@ -350,6 +351,7 @@ class MasterView(View):
|
||||||
return self.json_response(context)
|
return self.json_response(context)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
|
'index_url': None, # nb. avoid title link since this *is* the index
|
||||||
'grid': grid,
|
'grid': grid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +382,7 @@ class MasterView(View):
|
||||||
grid contents etc.
|
grid contents etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def make_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
|
def make_grid(self, factory=None, key=None, data=None, columns=None, session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Creates a new grid instance
|
Creates a new grid instance
|
||||||
"""
|
"""
|
||||||
|
@ -389,7 +391,7 @@ class MasterView(View):
|
||||||
if key is None:
|
if key is None:
|
||||||
key = self.get_grid_key()
|
key = self.get_grid_key()
|
||||||
if data is None:
|
if data is None:
|
||||||
data = self.get_data(session=kwargs.get('session'))
|
data = self.get_data(session=session)
|
||||||
if columns is None:
|
if columns is None:
|
||||||
columns = self.get_grid_columns()
|
columns = self.get_grid_columns()
|
||||||
|
|
||||||
|
@ -407,7 +409,7 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
if session is None:
|
if session is None:
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
kwargs.setdefault('pageable', False)
|
kwargs.setdefault('paginated', False)
|
||||||
grid = self.make_grid(session=session, **kwargs)
|
grid = self.make_grid(session=session, **kwargs)
|
||||||
return grid.make_visible_data()
|
return grid.make_visible_data()
|
||||||
|
|
||||||
|
@ -1701,7 +1703,7 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
if session is None:
|
if session is None:
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
kwargs.setdefault('pageable', False)
|
kwargs.setdefault('paginated', False)
|
||||||
kwargs.setdefault('sortable', sort)
|
kwargs.setdefault('sortable', sort)
|
||||||
grid = self.make_row_grid(session=session, **kwargs)
|
grid = self.make_row_grid(session=session, **kwargs)
|
||||||
return grid.make_visible_data()
|
return grid.make_visible_data()
|
||||||
|
@ -1879,6 +1881,7 @@ class MasterView(View):
|
||||||
return self.redirect(self.get_action_url('view', instance))
|
return self.redirect(self.get_action_url('view', instance))
|
||||||
|
|
||||||
form = self.make_form(instance)
|
form = self.make_form(instance)
|
||||||
|
form.save_label = "DELETE Forever"
|
||||||
|
|
||||||
# TODO: Add better validation, ideally CSRF etc.
|
# TODO: Add better validation, ideally CSRF etc.
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
|
@ -5119,6 +5122,7 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
Generic view for configuring some aspect of the software.
|
Generic view for configuring some aspect of the software.
|
||||||
"""
|
"""
|
||||||
|
self.configuring = True
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
if self.request.POST.get('remove_settings'):
|
if self.request.POST.get('remove_settings'):
|
||||||
|
|
|
@ -543,7 +543,7 @@ class PersonView(MasterView):
|
||||||
},
|
},
|
||||||
filterable=True,
|
filterable=True,
|
||||||
sortable=True,
|
sortable=True,
|
||||||
pageable=True,
|
paginated=True,
|
||||||
default_sortkey='end_time',
|
default_sortkey='end_time',
|
||||||
default_sortdir='desc',
|
default_sortdir='desc',
|
||||||
component='transactions-grid',
|
component='transactions-grid',
|
||||||
|
|
|
@ -24,7 +24,7 @@ class WebTestCase(DataTestCase):
|
||||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||||
'wutta_config': self.config,
|
'wutta_config': self.config,
|
||||||
'rattail_config': self.config,
|
'rattail_config': self.config,
|
||||||
'mako.directories': ['tailbone:templates'],
|
'mako.directories': ['tailbone:templates', 'wuttaweb:templates'],
|
||||||
# 'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
# 'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue