Add new views for App Info, and Configure App

and a way to specify version/url overrides for buefy, vue etc.

also, begin logic for "standard" admin menu
This commit is contained in:
Lance Edgar 2023-01-12 15:19:46 -06:00
parent 2163522e7c
commit d842a3d8e0
11 changed files with 752 additions and 26 deletions

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -26,6 +26,7 @@ Rattail config extension for Tailbone
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import warnings
from pkg_resources import parse_version from pkg_resources import parse_version
from rattail.config import ConfigExtension as BaseExtension from rattail.config import ConfigExtension as BaseExtension
@ -64,7 +65,16 @@ def csrf_header_name(config):
def get_buefy_version(config): def get_buefy_version(config):
return config.get('tailbone', 'buefy_version') or '0.8.17' warnings.warn("get_buefy_version() is deprecated; please use "
"tailbone.util.get_libver() instead",
DeprecationWarning, stacklevel=2)
version = config.get('tailbone', 'libver.buefy')
if version:
return version
return config.get('tailbone', 'buefy_version',
default='latest')
def get_buefy_0_8(config, version=None): def get_buefy_0_8(config, version=None):

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -453,10 +453,18 @@ class Grid(object):
return pretty_boolean(value) return pretty_boolean(value)
def obtain_value(self, obj, column_name): def obtain_value(self, obj, column_name):
"""
Try to obtain and return the value from the given object, for
the given column name.
:returns: The value, or ``None`` if no value was found.
"""
try: try:
return obj[column_name] return obj[column_name]
except KeyError:
pass
except TypeError: except TypeError:
return getattr(obj, column_name) return getattr(obj, column_name, None)
def render_currency(self, obj, column_name): def render_currency(self, obj, column_name):
value = self.obtain_value(obj, column_name) value = self.obtain_value(obj, column_name)

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -41,7 +41,8 @@ from webhelpers2.html.tags import *
from tailbone.util import (csrf_token, get_csrf_token, from tailbone.util import (csrf_token, get_csrf_token,
pretty_datetime, raw_datetime, pretty_datetime, raw_datetime,
render_markdown, render_markdown,
route_exists) route_exists,
get_liburl)
def pretty_date(date): def pretty_date(date):

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -403,3 +403,103 @@ def mark_allowed(request, menus):
if item['allowed'] and item.get('type') != 'sep': if item['allowed'] and item.get('type') != 'sep':
topitem['allowed'] = True topitem['allowed'] = True
break break
def make_admin_menu(request, include_stores=False):
"""
Generate a typical Admin menu
"""
items = []
if include_stores:
items.append({
'title': "Stores",
'route': 'stores',
'perm': 'stores.list',
})
items.extend([
{
'title': "Users",
'route': 'users',
'perm': 'users.list',
},
{
'title': "User Events",
'route': 'userevents',
'perm': 'userevents.list',
},
{
'title': "Roles",
'route': 'roles',
'perm': 'roles.list',
},
{'type': 'sep'},
{
'title': "App Settings",
'route': 'appsettings',
'perm': 'settings.list',
},
{
'title': "Email Settings",
'route': 'emailprofiles',
'perm': 'emailprofiles.list',
},
{
'title': "Email Attempts",
'route': 'email_attempts',
'perm': 'email_attempts.list',
},
{
'title': "Raw Settings",
'route': 'settings',
'perm': 'settings.list',
},
{'type': 'sep'},
{
'title': "DataSync Changes",
'route': 'datasyncchanges',
'perm': 'datasync_changes.list',
},
{
'title': "DataSync Status",
'route': 'datasync.status',
'perm': 'datasync.status',
},
{
'title': "Importing / Exporting",
'route': 'importing',
'perm': 'importing.list',
},
{
'title': "Luigi Tasks",
'route': 'luigi',
'perm': 'luigi.list',
},
{
'title': "Tables",
'route': 'tables',
'perm': 'tables.list',
},
{
'title': "App Info",
'route': 'appinfo',
'perm': 'appinfo.list',
},
{
'title': "Configure App",
'route': 'appinfo.configure',
'perm': 'appinfo.configure',
},
{
'title': "Upgrades",
'route': 'upgrades',
'perm': 'upgrades.list',
},
])
return {
'title': "Admin",
'type': 'menu',
'items': items,
}

View file

@ -41,9 +41,9 @@ import tailbone
from tailbone import helpers from tailbone import helpers
from tailbone.db import Session from tailbone.db import Session
from tailbone.config import (csrf_header_name, should_expose_websockets, from tailbone.config import (csrf_header_name, should_expose_websockets,
get_buefy_version, get_buefy_0_8) get_buefy_0_8)
from tailbone.menus import make_simple_menus from tailbone.menus import make_simple_menus
from tailbone.util import should_use_buefy, get_global_search_options from tailbone.util import should_use_buefy, get_global_search_options, get_libver
def new_request(event): def new_request(event):
@ -160,13 +160,8 @@ def before_render(event):
# buefy themes get some extra treatment # buefy themes get some extra treatment
if should_use_buefy(request): if should_use_buefy(request):
# declare vue.js and buefy versions to use. the default # TODO: remove this hack once all nodes safely on buefy 0.9
# values here are "quite conservative" as of this writing, version = get_libver(request, 'buefy')
# perhaps too much so, but at least they should work fine.
renderer_globals['vue_version'] = request.rattail_config.get(
'tailbone', 'vue_version') or '2.6.10'
version = get_buefy_version(rattail_config)
renderer_globals['buefy_version'] = version
renderer_globals['buefy_0_8'] = get_buefy_0_8(rattail_config, renderer_globals['buefy_0_8'] = get_buefy_0_8(rattail_config,
version=version) version=version)

View file

@ -0,0 +1,242 @@
## -*- coding: utf-8; -*-
<%inherit file="/configure.mako" />
<%def name="form_content()">
<h3 class="block is-size-3">Basics</h3>
<div class="block" style="padding-left: 2rem;">
<b-field grouped>
<b-field label="App Title">
<b-input name="rattail.app_title"
v-model="simpleSettings['rattail.app_title']"
@input="settingsNeedSaved = true">
</b-input>
</b-field>
<b-field label="Node Type">
## TODO: should be a dropdown, app handler defines choices
<b-input name="rattail.node_type"
v-model="simpleSettings['rattail.node_type']"
@input="settingsNeedSaved = true">
</b-input>
</b-field>
<b-field label="Node Title">
<b-input name="rattail.node_title"
v-model="simpleSettings['rattail.node_title']"
@input="settingsNeedSaved = true">
</b-input>
</b-field>
</b-field>
<b-field>
<b-checkbox name="rattail.production"
v-model="simpleSettings['rattail.production']"
native-value="true"
@input="settingsNeedSaved = true">
Production Mode
</b-checkbox>
</b-field>
</div>
<h3 class="block is-size-3">Display</h3>
<div class="block" style="padding-left: 2rem;">
<b-field grouped>
<b-field label="Background Color">
<b-input name="tailbone.background_color"
v-model="simpleSettings['tailbone.background_color']"
@input="settingsNeedSaved = true">
</b-input>
</b-field>
</b-field>
</div>
<h3 class="block is-size-3">Grids</h3>
<div class="block" style="padding-left: 2rem;">
<b-field grouped>
<b-field label="Default Page Size">
<b-input name="tailbone.grid.default_pagesize"
v-model="simpleSettings['tailbone.grid.default_pagesize']"
@input="settingsNeedSaved = true">
</b-input>
</b-field>
</b-field>
</div>
<h3 class="block is-size-3">Web Libraries</h3>
<div class="block" style="padding-left: 2rem;">
<b-table :data="weblibs">
% if buefy_0_8:
<template slot-scope="props">
% endif
<b-table-column field="title"
label="Name"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.title }}
</b-table-column>
<b-table-column field="configured_version"
label="Version"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.configured_version || props.row.default_version }}
</b-table-column>
<b-table-column field="configured_url"
label="URL Override"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.configured_url }}
</b-table-column>
<b-table-column field="live_url"
label="Effective (Live) URL"
% if not buefy_0_8:
v-slot="props"
% endif
>
<span v-if="props.row.modified"
class="has-text-warning">
save settings and refresh page to see new URL
</span>
<span v-if="!props.row.modified">
{{ props.row.live_url }}
</span>
</b-table-column>
<b-table-column field="actions"
label="Actions"
% if not buefy_0_8:
v-slot="props"
% endif
>
<a href="#"
@click.prevent="editWebLibraryInit(props.row)">
<i class="fas fa-edit"></i>
Edit
</a>
</b-table-column>
% if buefy_0_8:
</template>
% endif
</b-table>
% for weblib in weblibs:
${h.hidden('tailbone.libver.{}'.format(weblib['key']), **{':value': "simpleSettings['tailbone.libver.{}']".format(weblib['key'])})}
${h.hidden('tailbone.liburl.{}'.format(weblib['key']), **{':value': "simpleSettings['tailbone.liburl.{}']".format(weblib['key'])})}
% endfor
<b-modal has-modal-card
:active.sync="editWebLibraryShowDialog">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Web Library: {{ editWebLibraryRecord.title }}</p>
</header>
<section class="modal-card-body">
<b-field grouped>
<b-field label="Default Version">
<b-input v-model="editWebLibraryRecord.default_version"
disabled>
</b-input>
</b-field>
<b-field label="Override Version">
<b-input v-model="editWebLibraryVersion">
</b-input>
</b-field>
</b-field>
<b-field label="Override URL">
<b-input v-model="editWebLibraryURL">
</b-input>
</b-field>
<b-field label="Effective URL (as of last page load)">
<b-input v-model="editWebLibraryRecord.live_url"
disabled>
</b-input>
</b-field>
</section>
<footer class="modal-card-foot">
<b-button type="is-primary"
@click="editWebLibrarySave()"
icon-pack="fas"
icon-left="save">
Save
</b-button>
<b-button @click="editWebLibraryShowDialog = false">
Cancel
</b-button>
</footer>
</div>
</b-modal>
</div>
</%def>
<%def name="modify_this_page_vars()">
${parent.modify_this_page_vars()}
<script type="text/javascript">
ThisPageData.weblibs = ${json.dumps(weblibs)|n}
ThisPageData.editWebLibraryShowDialog = false
ThisPageData.editWebLibraryRecord = {}
ThisPageData.editWebLibraryVersion = null
ThisPageData.editWebLibraryURL = null
ThisPage.methods.editWebLibraryInit = function(row) {
this.editWebLibraryRecord = row
this.editWebLibraryVersion = row.configured_version
this.editWebLibraryURL = row.configured_url
this.editWebLibraryShowDialog = true
}
ThisPage.methods.editWebLibrarySave = function() {
this.editWebLibraryRecord.configured_version = this.editWebLibraryVersion
this.editWebLibraryRecord.configured_url = this.editWebLibraryURL
this.editWebLibraryRecord.modified = true
this.simpleSettings[`tailbone.libver.${'$'}{this.editWebLibraryRecord.key}`] = this.editWebLibraryVersion
this.simpleSettings[`tailbone.liburl.${'$'}{this.editWebLibraryRecord.key}`] = this.editWebLibraryURL
this.settingsNeedSaved = true
this.editWebLibraryShowDialog = false
}
</script>
</%def>
${parent.body()}

View file

@ -0,0 +1,114 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/index.mako" />
<%def name="render_grid_component()">
<b-collapse class="panel" open>
<template #trigger="props">
<div class="panel-heading"
role="button">
## TODO: for some reason buefy will "reuse" the icon
## element in such a way that its display does not
## refresh. so to work around that, we use different
## structure for the two icons, so buefy is forced to
## re-draw
<b-icon v-if="props.open"
pack="fas"
icon="angle-down">
</b-icon>
<span v-if="!props.open">
<b-icon pack="fas"
icon="angle-right">
</b-icon>
</span>
<strong>Configuration Files</strong>
</div>
</template>
<div class="panel-block">
<div style="width: 100%;">
<b-table :data="configFiles">
% if buefy_0_8:
<template slot-scope="props">
% endif
<b-table-column field="priority"
label="Priority"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.priority }}
</b-table-column>
<b-table-column field="path"
label="File Path"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.path }}
</b-table-column>
% if buefy_0_8:
</template>
% endif
</b-table>
</div>
</div>
</b-collapse>
<b-collapse class="panel"
:open="false">
<template #trigger="props">
<div class="panel-heading"
role="button">
## TODO: for some reason buefy will "reuse" the icon
## element in such a way that its display does not
## refresh. so to work around that, we use different
## structure for the two icons, so buefy is forced to
## re-draw
<b-icon v-if="props.open"
pack="fas"
icon="angle-down">
</b-icon>
<span v-if="!props.open">
<b-icon pack="fas"
icon="angle-right">
</b-icon>
</span>
<strong>Installed Packages</strong>
</div>
</template>
<div class="panel-block">
<div style="width: 100%;">
${parent.render_grid_component()}
</div>
</div>
</b-collapse>
</%def>
<%def name="modify_this_page_vars()">
${parent.modify_this_page_vars()}
<script type="text/javascript">
ThisPageData.configFiles = ${json.dumps([dict(path=p, priority=i) for i, p in enumerate(reversed(request.rattail_config.files_read), 1)])|n}
</script>
</%def>
${parent.body()}

View file

@ -107,21 +107,20 @@
</%def> </%def>
<%def name="jquery()"> <%def name="jquery()">
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.jquery', default='https://code.jquery.com/jquery-1.12.4.min.js'))} ${h.javascript_link(h.get_liburl(request, 'jquery'))}
</%def> </%def>
<%def name="vuejs()"> <%def name="vuejs()">
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.vue', default='https://unpkg.com/vue@{}/dist/vue.min.js'.format(vue_version)))} ${h.javascript_link(h.get_liburl(request, 'vue'))}
## TODO: make this version configurable also ${h.javascript_link(h.get_liburl(request, 'vue_resource'))}
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.vue_resource', default='https://cdn.jsdelivr.net/npm/vue-resource@1.5.1'))}
</%def> </%def>
<%def name="buefy()"> <%def name="buefy()">
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.buefy', default='https://unpkg.com/buefy@{}/dist/buefy.min.js'.format(buefy_version)))} ${h.javascript_link(h.get_liburl(request, 'buefy'))}
</%def> </%def>
<%def name="fontawesome()"> <%def name="fontawesome()">
<script defer src="${request.rattail_config.get('tailbone', 'liburl.fontawesome', default='https://use.fontawesome.com/releases/v5.3.1/js/all.js')}"></script> <script defer src="${h.get_liburl(request, 'fontawesome')}"></script>
</%def> </%def>
<%def name="extra_javascript()"></%def> <%def name="extra_javascript()"></%def>
@ -159,14 +158,14 @@
${h.stylesheet_link(buefy_css)} ${h.stylesheet_link(buefy_css)}
% else: % else:
## upstream Buefy CSS ## upstream Buefy CSS
${h.stylesheet_link(request.rattail_config.get('tailbone', 'liburl.buefy.css', default='https://unpkg.com/buefy@{}/dist/buefy.min.css'.format(buefy_version)))} ${h.stylesheet_link(h.get_liburl(request, 'buefy.css'))}
% endif % endif
</%def> </%def>
## TODO: this is only being referenced by the progress template i think? ## TODO: this is only being referenced by the progress template i think?
## (so, should make a Buefy progress page at least) ## (so, should make a Buefy progress page at least)
<%def name="jquery_theme()"> <%def name="jquery_theme()">
${h.stylesheet_link(request.rattail_config.get('tailbone', 'liburl.jquery.css', default='https://code.jquery.com/ui/1.11.4/themes/dark-hive/jquery-ui.css'))} ${h.stylesheet_link(h.get_liburl(request, 'jquery_ui'))}
</%def> </%def>
<%def name="extra_styles()"></%def> <%def name="extra_styles()"></%def>

View file

@ -98,6 +98,106 @@ def get_global_search_options(request):
return options return options
def get_libver(request, key, fallback=True, default_only=False):
"""
Return the appropriate URL for the library identified by ``key``.
"""
config = request.rattail_config
if not default_only:
version = config.get('tailbone', 'libver.{}'.format(key))
if version:
return version
if not fallback and not default_only:
if key == 'buefy':
version = config.get('tailbone', 'buefy_version')
if version:
return version
elif key == 'buefy.css':
version = get_libver(request, 'buefy', fallback=False)
if version:
return version
elif key == 'vue':
version = config.get('tailbone', 'vue_version')
if version:
return version
return
if key == 'buefy':
if not default_only:
version = config.get('tailbone', 'buefy_version')
if version:
return version
return 'latest'
elif key == 'buefy.css':
version = get_libver(request, 'buefy', default_only=default_only)
if version:
return version
return 'latest'
elif key == 'vue':
if not default_only:
version = config.get('tailbone', 'vue_version')
if version:
return version
return '2.6.14'
elif key == 'vue_resource':
return 'latest'
elif key == 'fontawesome':
return '5.3.1'
elif key == 'jquery':
return '1.12.4'
elif key == 'jquery_ui':
return '1.11.4'
def get_liburl(request, key, fallback=True):
"""
Return the appropriate URL for the library identified by ``key``.
"""
config = request.rattail_config
url = config.get('tailbone', 'liburl.{}'.format(key))
if url:
return url
if not fallback:
return
version = get_libver(request, key)
if key == 'buefy':
return 'https://unpkg.com/buefy@{}/dist/buefy.min.js'.format(version)
elif key == 'buefy.css':
return 'https://unpkg.com/buefy@{}/dist/buefy.min.css'.format(version)
elif key == 'vue':
return 'https://unpkg.com/vue@{}/dist/vue.min.js'.format(version)
elif key == 'vue_resource':
return 'https://cdn.jsdelivr.net/npm/vue-resource@{}'.format(version)
elif key == 'fontawesome':
return 'https://use.fontawesome.com/releases/v{}/js/all.js'.format(version)
elif key == 'jquery':
return 'https://code.jquery.com/jquery-{}.min.js'.format(version)
elif key == 'jquery_ui':
return 'https://code.jquery.com/ui/{}/themes/dark-hive/jquery-ui.css'.format(version)
def should_use_buefy(request): def should_use_buefy(request):
""" """
Returns a flag indicating whether or not the current theme supports (and Returns a flag indicating whether or not the current theme supports (and

View file

@ -2248,6 +2248,8 @@ class MasterView(View):
route = self.get_route_prefix() route = self.get_route_prefix()
return self.request.route_url(route, **kwargs) return self.request.route_url(route, **kwargs)
# TODO: this should not be class method, if possible
# (pretty sure overriding as instance method works fine)
@classmethod @classmethod
def get_index_title(cls): def get_index_title(cls):
""" """
@ -4822,6 +4824,8 @@ class MasterView(View):
value = six.text_type(bool(value)).lower() value = six.text_type(bool(value)).lower()
elif simple.get('type') is int: elif simple.get('type') is int:
value = six.text_type(int(value or '0')) value = six.text_type(int(value or '0'))
elif value is None:
value = ''
else: else:
value = six.text_type(value) value = six.text_type(value)

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -26,14 +26,17 @@ Settings Views
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import os
import re import re
import subprocess
import sys
import json import json
import six import six
from rattail.db import model from rattail.db import model
from rattail.settings import Setting from rattail.settings import Setting
from rattail.util import import_module_path from rattail.util import import_module_path, OrderedDict
import colander import colander
from webhelpers2.html import tags from webhelpers2.html import tags
@ -41,6 +44,153 @@ from webhelpers2.html import tags
from tailbone import forms from tailbone import forms
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView, View from tailbone.views import MasterView, View
from tailbone.util import get_libver, get_liburl
class AppInfoView(MasterView):
"""
Master view for the overall app, to show/edit config etc.
"""
route_prefix = 'appinfo'
model_key = 'UNUSED'
model_title = "UNUSED"
model_title_plural = "App Info"
creatable = False
viewable = False
editable = False
deletable = False
filterable = False
pageable = False
configurable = True
grid_columns = [
'name',
'version',
'editable_project_location',
]
def get_index_title(self):
return "App Info for {}".format(self.rattail_config.app_title())
def get_data(self, session=None):
pip = os.path.join(sys.prefix, 'bin', 'pip')
output = subprocess.check_output([pip, 'list', '--format=json'])
data = json.loads(output.decode('utf_8').strip())
for pkg in data:
pkg.setdefault('editable_project_location', '')
return data
def configure_grid(self, g):
super(AppInfoView, self).configure_grid(g)
g.sorters['name'] = g.make_simple_sorter('name', foldcase=True)
g.set_sort_defaults('name')
g.set_searchable('name')
g.sorters['version'] = g.make_simple_sorter('version', foldcase=True)
g.sorters['editable_project_location'] = g.make_simple_sorter(
'editable_project_location', foldcase=True)
g.set_searchable('editable_project_location')
def configure_get_context(self, **kwargs):
context = super(AppInfoView, self).configure_get_context(**kwargs)
weblibs = OrderedDict([
('vue', "Vue"),
('vue_resource', "vue-resource"),
('buefy', "Buefy"),
('buefy.css', "Buefy CSS"),
('fontawesome', "FontAwesome"),
('jquery', "jQuery"),
('jquery_ui', "jQuery UI"),
])
for key in weblibs:
title = weblibs[key]
weblibs[key] = {
'key': key,
'title': title,
# nb. these values are exactly as configured, and are
# used for editing the settings
'configured_version': get_libver(self.request, key, fallback=False),
'configured_url': get_liburl(self.request, key, fallback=False),
# these are for informational purposes only
'default_version': get_libver(self.request, key, default_only=True),
'live_url': get_liburl(self.request, key),
}
context['weblibs'] = list(weblibs.values())
return context
def configure_get_simple_settings(self):
return [
# basics
{'section': 'rattail',
'option': 'app_title'},
{'section': 'rattail',
'option': 'node_type'},
{'section': 'rattail',
'option': 'node_title'},
{'section': 'rattail',
'option': 'production',
'type': bool},
# display
{'section': 'tailbone',
'option': 'background_color'},
# grids
{'section': 'tailbone',
'option': 'grid.default_pagesize',
# TODO: seems like should enforce this, but validation is
# not setup yet
# 'type': int
},
# web libs
{'section': 'tailbone',
'option': 'libver.vue'},
{'section': 'tailbone',
'option': 'liburl.vue'},
{'section': 'tailbone',
'option': 'libver.vue_resource'},
{'section': 'tailbone',
'option': 'liburl.vue_resource'},
{'section': 'tailbone',
'option': 'libver.buefy'},
{'section': 'tailbone',
'option': 'liburl.buefy'},
{'section': 'tailbone',
'option': 'libver.buefy.css'},
{'section': 'tailbone',
'option': 'liburl.buefy.css'},
{'section': 'tailbone',
'option': 'libver.fontawesome'},
{'section': 'tailbone',
'option': 'liburl.fontawesome'},
{'section': 'tailbone',
'option': 'libver.jquery'},
{'section': 'tailbone',
'option': 'liburl.jquery'},
{'section': 'tailbone',
'option': 'libver.jquery_ui'},
{'section': 'tailbone',
'option': 'liburl.jquery_ui'},
# nb. these are no longer used (deprecated), but we keep
# them defined here so the tool auto-deletes them
{'section': 'tailbone',
'option': 'buefy_version'},
{'section': 'tailbone',
'option': 'vue_version'},
]
class SettingView(MasterView): class SettingView(MasterView):
@ -322,6 +472,9 @@ class AppSettingsView(View):
def defaults(config, **kwargs): def defaults(config, **kwargs):
base = globals() base = globals()
AppInfoView = kwargs.get('AppInfoView', base['AppInfoView'])
AppInfoView.defaults(config)
AppSettingsView = kwargs.get('AppSettingsView', base['AppSettingsView']) AppSettingsView = kwargs.get('AppSettingsView', base['AppSettingsView'])
AppSettingsView.defaults(config) AppSettingsView.defaults(config)