Compare commits
No commits in common. "ccdb6d746344af69beb21d912a7c42c8bb7879b8" and "749aca560adb9e3873dfe25e70f2926fe95d53ff" have entirely different histories.
ccdb6d7463
...
749aca560a
22 changed files with 55 additions and 1618 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -5,17 +5,6 @@ All notable changes to wuttaweb will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## v0.22.0 (2025-06-29)
|
|
||||||
|
|
||||||
### Feat
|
|
||||||
|
|
||||||
- add basic theme system
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- improve styles for testing watermark background image
|
|
||||||
- fix timezone offset bug for datepicker
|
|
||||||
|
|
||||||
## v0.21.5 (2025-02-21)
|
## v0.21.5 (2025-02-21)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
|
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "WuttaWeb"
|
name = "WuttaWeb"
|
||||||
version = "0.22.0"
|
version = "0.21.5"
|
||||||
description = "Web App for Wutta Framework"
|
description = "Web App for Wutta Framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
||||||
|
@ -44,7 +44,7 @@ dependencies = [
|
||||||
"pyramid_tm",
|
"pyramid_tm",
|
||||||
"waitress",
|
"waitress",
|
||||||
"WebHelpers2",
|
"WebHelpers2",
|
||||||
"WuttJamaican[db]>=0.20.6",
|
"WuttJamaican[db]>=0.20.4",
|
||||||
"zope.sqlalchemy>=1.5",
|
"zope.sqlalchemy>=1.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
@ -35,7 +35,6 @@ from pyramid.config import Configurator
|
||||||
|
|
||||||
import wuttaweb.db
|
import wuttaweb.db
|
||||||
from wuttaweb.auth import WuttaSecurityPolicy
|
from wuttaweb.auth import WuttaSecurityPolicy
|
||||||
from wuttaweb.util import get_effective_theme, get_theme_template_path
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -133,9 +132,6 @@ def make_pyramid_config(settings):
|
||||||
settings.setdefault('pyramid_deform.template_search_path',
|
settings.setdefault('pyramid_deform.template_search_path',
|
||||||
'wuttaweb:templates/deform')
|
'wuttaweb:templates/deform')
|
||||||
|
|
||||||
# update settings per current theme
|
|
||||||
establish_theme(settings)
|
|
||||||
|
|
||||||
pyramid_config = Configurator(settings=settings)
|
pyramid_config = Configurator(settings=settings)
|
||||||
|
|
||||||
# configure user authorization / authentication
|
# configure user authorization / authentication
|
||||||
|
@ -252,33 +248,3 @@ def make_asgi_app(main_app=None, config=None):
|
||||||
"""
|
"""
|
||||||
wsgi_app = make_wsgi_app(main_app, config=config)
|
wsgi_app = make_wsgi_app(main_app, config=config)
|
||||||
return WsgiToAsgi(wsgi_app)
|
return WsgiToAsgi(wsgi_app)
|
||||||
|
|
||||||
|
|
||||||
def establish_theme(settings):
|
|
||||||
"""
|
|
||||||
Establishes initial theme on app startup. This mostly involves
|
|
||||||
updating the given ``settings`` dict.
|
|
||||||
|
|
||||||
This function is called automatically from within
|
|
||||||
:func:`make_pyramid_config()`.
|
|
||||||
|
|
||||||
It will first call :func:`~wuttaweb.util.get_effective_theme()` to
|
|
||||||
read the current theme from the :term:`settings table`, and store
|
|
||||||
this within ``settings['wuttaweb.theme']``.
|
|
||||||
|
|
||||||
It then calls :func:`~wuttaweb.util.get_theme_template_path()` and
|
|
||||||
will update ``settings['mako.directories']`` such that the theme's
|
|
||||||
template path is listed first.
|
|
||||||
"""
|
|
||||||
config = settings['wutta_config']
|
|
||||||
|
|
||||||
theme = get_effective_theme(config)
|
|
||||||
settings['wuttaweb.theme'] = theme
|
|
||||||
|
|
||||||
directories = settings['mako.directories']
|
|
||||||
if isinstance(directories, str):
|
|
||||||
directories = config.parse_list(directories)
|
|
||||||
|
|
||||||
path = get_theme_template_path(config)
|
|
||||||
directories.insert(0, path)
|
|
||||||
settings['mako.directories'] = directories
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
@ -38,13 +38,11 @@ hooks contained here, depending on the circumstance.
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from pyramid import threadlocal
|
from pyramid import threadlocal
|
||||||
|
|
||||||
from wuttaweb import helpers
|
from wuttaweb import helpers
|
||||||
from wuttaweb.db import Session
|
from wuttaweb.db import Session
|
||||||
from wuttaweb.util import get_available_themes
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -81,43 +79,6 @@ def new_request(event):
|
||||||
Flag indicating whether the frontend should be displayed using
|
Flag indicating whether the frontend should be displayed using
|
||||||
Vue 3 + Oruga (if ``True``), or else Vue 2 + Buefy (if
|
Vue 3 + Oruga (if ``True``), or else Vue 2 + Buefy (if
|
||||||
``False``). This flag is ``False`` by default.
|
``False``). This flag is ``False`` by default.
|
||||||
|
|
||||||
.. function:: request.register_component(tagname, classname)
|
|
||||||
|
|
||||||
Request method which registers a Vue component for use within
|
|
||||||
the app templates.
|
|
||||||
|
|
||||||
:param tagname: Component tag name as string.
|
|
||||||
|
|
||||||
:param classname: Component class name as string.
|
|
||||||
|
|
||||||
This is meant to be analogous to the ``Vue.component()`` call
|
|
||||||
which is part of Vue 2. It is good practice to always call
|
|
||||||
both at the same time/place:
|
|
||||||
|
|
||||||
.. code-block:: mako
|
|
||||||
|
|
||||||
## define component template
|
|
||||||
<script type="text/x-template" id="my-example-template">
|
|
||||||
<div>my example</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
## define component logic
|
|
||||||
const MyExample = {
|
|
||||||
template: 'my-example-template'
|
|
||||||
}
|
|
||||||
|
|
||||||
## register the component both ways here..
|
|
||||||
|
|
||||||
## this is for Vue 2 - note the lack of quotes for classname
|
|
||||||
Vue.component('my-example', MyExample)
|
|
||||||
|
|
||||||
## this is for Vue 3 - note the classname must be quoted
|
|
||||||
<% request.register_component('my-example', 'MyExample') %>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
"""
|
"""
|
||||||
request = event.request
|
request = event.request
|
||||||
config = request.registry.settings['wutta_config']
|
config = request.registry.settings['wutta_config']
|
||||||
|
@ -143,34 +104,10 @@ def new_request(event):
|
||||||
if spec:
|
if spec:
|
||||||
func = app.load_object(spec)
|
func = app.load_object(spec)
|
||||||
return func(request)
|
return func(request)
|
||||||
|
|
||||||
theme = request.registry.settings.get('wuttaweb.theme')
|
|
||||||
if theme == 'butterfly':
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
request.set_property(use_oruga, reify=True)
|
request.set_property(use_oruga, reify=True)
|
||||||
|
|
||||||
def register_component(tagname, classname):
|
|
||||||
"""
|
|
||||||
Register a Vue 3 component, so the base template knows to
|
|
||||||
declare it for use within the app (page).
|
|
||||||
"""
|
|
||||||
if not hasattr(request, '_wuttaweb_registered_components'):
|
|
||||||
request._wuttaweb_registered_components = OrderedDict()
|
|
||||||
|
|
||||||
if tagname in request._wuttaweb_registered_components:
|
|
||||||
log.warning("component with tagname '%s' already registered "
|
|
||||||
"with class '%s' but we are replacing that "
|
|
||||||
"with class '%s'",
|
|
||||||
tagname,
|
|
||||||
request._wuttaweb_registered_components[tagname],
|
|
||||||
classname)
|
|
||||||
|
|
||||||
request._wuttaweb_registered_components[tagname] = classname
|
|
||||||
|
|
||||||
request.register_component = register_component
|
|
||||||
|
|
||||||
|
|
||||||
def default_user_getter(request, db_session=None):
|
def default_user_getter(request, db_session=None):
|
||||||
"""
|
"""
|
||||||
|
@ -353,23 +290,6 @@ def before_render(event):
|
||||||
|
|
||||||
Reference to the request method,
|
Reference to the request method,
|
||||||
:meth:`~pyramid:pyramid.request.Request.route_url()`.
|
:meth:`~pyramid:pyramid.request.Request.route_url()`.
|
||||||
|
|
||||||
.. data:: 'theme'
|
|
||||||
|
|
||||||
String name of the current theme. This will be ``'default'``
|
|
||||||
unless a custom theme is in effect.
|
|
||||||
|
|
||||||
.. data:: 'expose_theme_picker'
|
|
||||||
|
|
||||||
Boolean indicating whether the theme picker should *ever* be
|
|
||||||
exposed. For a user to see it, this flag must be true *and*
|
|
||||||
the user must have permission to change theme.
|
|
||||||
|
|
||||||
.. data:: 'available_themes'
|
|
||||||
|
|
||||||
List of theme names from which user may choose, if they are
|
|
||||||
allowed to change theme. Only set/relevant if
|
|
||||||
``expose_theme_picker`` is true (see above).
|
|
||||||
"""
|
"""
|
||||||
request = event.get('request') or threadlocal.get_current_request()
|
request = event.get('request') or threadlocal.get_current_request()
|
||||||
config = request.wutta_config
|
config = request.wutta_config
|
||||||
|
@ -391,13 +311,6 @@ def before_render(event):
|
||||||
menus = web.get_menu_handler()
|
menus = web.get_menu_handler()
|
||||||
context['menus'] = menus.do_make_menus(request)
|
context['menus'] = menus.do_make_menus(request)
|
||||||
|
|
||||||
# theme
|
|
||||||
context['theme'] = request.registry.settings.get('wuttaweb.theme', 'default')
|
|
||||||
context['expose_theme_picker'] = config.get_bool('wuttaweb.themes.expose_picker',
|
|
||||||
default=False)
|
|
||||||
if context['expose_theme_picker']:
|
|
||||||
context['available_themes'] = get_available_themes(config)
|
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
config.add_subscriber(new_request, 'pyramid.events.NewRequest')
|
config.add_subscriber(new_request, 'pyramid.events.NewRequest')
|
||||||
|
|
|
@ -39,13 +39,6 @@
|
||||||
@input="settingsNeedSaved = true">
|
@input="settingsNeedSaved = true">
|
||||||
Production Mode
|
Production Mode
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
<span style="width: 1rem;" />
|
|
||||||
<b-checkbox name="wuttaweb.themes.expose_picker"
|
|
||||||
v-model="simpleSettings['wuttaweb.themes.expose_picker']"
|
|
||||||
native-value="true"
|
|
||||||
@input="settingsNeedSaved = true">
|
|
||||||
Expose Theme Picker
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Menu Handler">
|
<b-field label="Menu Handler">
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
% if not request.wutta_config.production():
|
% if not request.wutta_config.production():
|
||||||
html, body, .navbar, .footer {
|
html, .navbar, .footer {
|
||||||
background-image: url(${request.static_url('wuttaweb:static/img/testing.png')});
|
background-image: url(${request.static_url('wuttaweb:static/img/testing.png')});
|
||||||
}
|
}
|
||||||
% endif
|
% endif
|
||||||
|
@ -440,27 +440,7 @@
|
||||||
</div>
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="render_theme_picker()">
|
<%def name="render_theme_picker()"></%def>
|
||||||
% if expose_theme_picker and request.has_perm('common.change_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 name in available_themes:
|
|
||||||
<option value="${name}">${name}</option>
|
|
||||||
% endfor
|
|
||||||
</${b}-select>
|
|
||||||
</div>
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="render_feedback_button()">
|
<%def name="render_feedback_button()">
|
||||||
% if request.has_perm('common.feedback'):
|
% if request.has_perm('common.feedback'):
|
||||||
|
@ -479,8 +459,8 @@
|
||||||
Feedback
|
Feedback
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|
||||||
<${b}-modal has-modal-card
|
<b-modal has-modal-card
|
||||||
:active.sync="showDialog">
|
:active.sync="showDialog">
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
|
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
|
@ -527,7 +507,7 @@
|
||||||
</b-button>
|
</b-button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</${b}-modal>
|
</b-modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
@ -651,12 +631,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
% if expose_theme_picker and request.has_perm('common.change_theme'):
|
|
||||||
changeTheme() {
|
|
||||||
this.$refs.themePickerForm.submit()
|
|
||||||
},
|
|
||||||
% endif
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,10 +638,6 @@
|
||||||
contentTitleHTML: ${json.dumps(capture(self.content_title))|n},
|
contentTitleHTML: ${json.dumps(capture(self.content_title))|n},
|
||||||
referrer: location.href,
|
referrer: location.href,
|
||||||
mountedHooks: [],
|
mountedHooks: [],
|
||||||
|
|
||||||
% if expose_theme_picker and request.has_perm('common.change_theme'):
|
|
||||||
globalTheme: ${json.dumps(theme or None)|n},
|
|
||||||
% endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## declare nested menu visibility toggle flags
|
## declare nested menu visibility toggle flags
|
||||||
|
@ -804,13 +774,11 @@
|
||||||
<script>
|
<script>
|
||||||
WholePage.data = function() { return WholePageData }
|
WholePage.data = function() { return WholePageData }
|
||||||
Vue.component('whole-page', WholePage)
|
Vue.component('whole-page', WholePage)
|
||||||
<% request.register_component('whole-page', 'WholePage') %>
|
|
||||||
</script>
|
</script>
|
||||||
% if request.has_perm('common.feedback'):
|
% if request.has_perm('common.feedback'):
|
||||||
<script>
|
<script>
|
||||||
WuttaFeedbackForm.data = function() { return WuttaFeedbackFormData }
|
WuttaFeedbackForm.data = function() { return WuttaFeedbackFormData }
|
||||||
Vue.component('wutta-feedback-form', WuttaFeedbackForm)
|
Vue.component('wutta-feedback-form', WuttaFeedbackForm)
|
||||||
<% request.register_component('wutta-feedback-form', 'WuttaFeedbackForm') %>
|
|
||||||
</script>
|
</script>
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
|
@ -95,5 +95,3 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<% request.register_component(form.vue_tagname, form.vue_component) %>
|
|
||||||
|
|
|
@ -739,5 +739,3 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<% request.register_component(grid.vue_tagname, grid.vue_component) %>
|
|
||||||
|
|
|
@ -50,6 +50,6 @@
|
||||||
<script>
|
<script>
|
||||||
ThisPage.data = function() { return ThisPageData }
|
ThisPage.data = function() { return ThisPageData }
|
||||||
Vue.component('this-page', ThisPage)
|
Vue.component('this-page', ThisPage)
|
||||||
<% request.register_component('this-page', 'ThisPage') %>
|
## <% request.register_component('this-page', 'ThisPage') %>
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
## -*- coding: utf-8; -*-
|
|
||||||
<%inherit file="wuttaweb:templates/base.mako" />
|
|
||||||
<%namespace file="/http-plugin.mako" import="make_http_plugin" />
|
|
||||||
<%namespace file="/buefy-plugin.mako" import="make_buefy_plugin" />
|
|
||||||
<%namespace file="/buefy-components.mako" import="make_buefy_components" />
|
|
||||||
|
|
||||||
<%def name="core_javascript()">
|
|
||||||
<script type="importmap">
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"vue": "${h.get_liburl(request, 'bb_vue')}",
|
|
||||||
"@oruga-ui/oruga-next": "${h.get_liburl(request, 'bb_oruga')}",
|
|
||||||
"@oruga-ui/theme-bulma": "${h.get_liburl(request, 'bb_oruga_bulma')}",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "${h.get_liburl(request, 'bb_fontawesome_svg_core')}",
|
|
||||||
"@fortawesome/free-solid-svg-icons": "${h.get_liburl(request, 'bb_free_solid_svg_icons')}",
|
|
||||||
"@fortawesome/vue-fontawesome": "${h.get_liburl(request, 'bb_vue_fontawesome')}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
## nb. empty stub to avoid errors for older buefy templates
|
|
||||||
const Vue = {
|
|
||||||
component(tagname, classname) {},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="core_styles()">
|
|
||||||
${h.stylesheet_link(h.get_liburl(request, 'bb_oruga_bulma_css'))}
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="render_vue_templates()">
|
|
||||||
${parent.render_vue_templates()}
|
|
||||||
${make_buefy_components()}
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="extra_styles()">
|
|
||||||
${parent.extra_styles()}
|
|
||||||
<style>
|
|
||||||
html, body, .navbar, .footer {
|
|
||||||
background-color: LightYellow;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_vue_app()">
|
|
||||||
${make_http_plugin()}
|
|
||||||
${make_buefy_plugin()}
|
|
||||||
<script type="module">
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
import { Oruga } from '@oruga-ui/oruga-next'
|
|
||||||
import { bulmaConfig } from '@oruga-ui/theme-bulma'
|
|
||||||
import { library } from "@fortawesome/fontawesome-svg-core"
|
|
||||||
import { fas } from "@fortawesome/free-solid-svg-icons"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
|
|
||||||
library.add(fas)
|
|
||||||
|
|
||||||
const app = createApp()
|
|
||||||
app.component('vue-fontawesome', FontAwesomeIcon)
|
|
||||||
|
|
||||||
% if hasattr(request, '_wuttaweb_registered_components'):
|
|
||||||
% for tagname, classname in request._wuttaweb_registered_components.items():
|
|
||||||
app.component('${tagname}', ${classname})
|
|
||||||
% endfor
|
|
||||||
% endif
|
|
||||||
|
|
||||||
app.use(Oruga, {
|
|
||||||
...bulmaConfig,
|
|
||||||
iconComponent: 'vue-fontawesome',
|
|
||||||
iconPack: 'fas',
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use(HttpPlugin)
|
|
||||||
app.use(BuefyPlugin)
|
|
||||||
|
|
||||||
app.mount('#app')
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
|
@ -1,760 +0,0 @@
|
||||||
|
|
||||||
<%def name="make_buefy_components()">
|
|
||||||
${self.make_b_autocomplete_component()}
|
|
||||||
${self.make_b_button_component()}
|
|
||||||
${self.make_b_checkbox_component()}
|
|
||||||
${self.make_b_collapse_component()}
|
|
||||||
${self.make_b_datepicker_component()}
|
|
||||||
${self.make_b_dropdown_component()}
|
|
||||||
${self.make_b_dropdown_item_component()}
|
|
||||||
${self.make_b_field_component()}
|
|
||||||
${self.make_b_icon_component()}
|
|
||||||
${self.make_b_input_component()}
|
|
||||||
${self.make_b_loading_component()}
|
|
||||||
${self.make_b_modal_component()}
|
|
||||||
${self.make_b_notification_component()}
|
|
||||||
${self.make_b_radio_component()}
|
|
||||||
${self.make_b_select_component()}
|
|
||||||
${self.make_b_steps_component()}
|
|
||||||
${self.make_b_step_item_component()}
|
|
||||||
${self.make_b_table_component()}
|
|
||||||
${self.make_b_table_column_component()}
|
|
||||||
${self.make_b_tooltip_component()}
|
|
||||||
${self.make_once_button_component()}
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_autocomplete_component()">
|
|
||||||
<script type="text/x-template" id="b-autocomplete-template">
|
|
||||||
<o-autocomplete v-model="orugaValue"
|
|
||||||
:data="data"
|
|
||||||
:field="field"
|
|
||||||
:open-on-focus="openOnFocus"
|
|
||||||
:keep-first="keepFirst"
|
|
||||||
:clearable="clearable"
|
|
||||||
:clear-on-select="clearOnSelect"
|
|
||||||
:formatter="customFormatter"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
@update:model-value="orugaValueUpdated"
|
|
||||||
ref="autocomplete">
|
|
||||||
</o-autocomplete>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BAutocomplete = {
|
|
||||||
template: '#b-autocomplete-template',
|
|
||||||
props: {
|
|
||||||
modelValue: String,
|
|
||||||
data: Array,
|
|
||||||
field: String,
|
|
||||||
openOnFocus: Boolean,
|
|
||||||
keepFirst: Boolean,
|
|
||||||
clearable: Boolean,
|
|
||||||
clearOnSelect: Boolean,
|
|
||||||
customFormatter: null,
|
|
||||||
placeholder: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
if (this.orugaValue != to) {
|
|
||||||
this.orugaValue = to
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
focus() {
|
|
||||||
const input = this.$refs.autocomplete.$el.querySelector('input')
|
|
||||||
input.focus()
|
|
||||||
},
|
|
||||||
orugaValueUpdated(value) {
|
|
||||||
this.$emit('update:modelValue', value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-autocomplete', 'BAutocomplete') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_button_component()">
|
|
||||||
<script type="text/x-template" id="b-button-template">
|
|
||||||
<o-button :variant="variant"
|
|
||||||
:size="orugaSize"
|
|
||||||
:native-type="nativeType"
|
|
||||||
:tag="tag"
|
|
||||||
:href="href"
|
|
||||||
:icon-left="iconLeft">
|
|
||||||
<slot />
|
|
||||||
</o-button>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BButton = {
|
|
||||||
template: '#b-button-template',
|
|
||||||
props: {
|
|
||||||
type: String,
|
|
||||||
nativeType: String,
|
|
||||||
tag: String,
|
|
||||||
href: String,
|
|
||||||
size: String,
|
|
||||||
iconPack: String, // ignored
|
|
||||||
iconLeft: String,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
orugaSize() {
|
|
||||||
if (this.size) {
|
|
||||||
return this.size.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
variant() {
|
|
||||||
if (this.type) {
|
|
||||||
return this.type.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-button', 'BButton') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_checkbox_component()">
|
|
||||||
<script type="text/x-template" id="b-checkbox-template">
|
|
||||||
<o-checkbox v-model="orugaValue"
|
|
||||||
@update:model-value="orugaValueUpdated"
|
|
||||||
:name="name"
|
|
||||||
:native-value="nativeValue">
|
|
||||||
<slot />
|
|
||||||
</o-checkbox>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BCheckbox = {
|
|
||||||
template: '#b-checkbox-template',
|
|
||||||
props: {
|
|
||||||
modelValue: null,
|
|
||||||
name: String,
|
|
||||||
nativeValue: null,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue || this.value,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
this.orugaValue = to
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
orugaValueUpdated(value) {
|
|
||||||
this.$emit('update:modelValue', value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-checkbox', 'BCheckbox') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_collapse_component()">
|
|
||||||
<script type="text/x-template" id="b-collapse-template">
|
|
||||||
<o-collapse :open="open">
|
|
||||||
<slot name="trigger" />
|
|
||||||
<slot />
|
|
||||||
</o-collapse>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BCollapse = {
|
|
||||||
template: '#b-collapse-template',
|
|
||||||
props: {
|
|
||||||
open: Boolean,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-collapse', 'BCollapse') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_datepicker_component()">
|
|
||||||
<script type="text/x-template" id="b-datepicker-template">
|
|
||||||
<o-datepicker :name="name"
|
|
||||||
v-model="orugaValue"
|
|
||||||
@update:model-value="orugaValueUpdated"
|
|
||||||
:value="value"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:date-formatter="dateFormatter"
|
|
||||||
:date-parser="dateParser"
|
|
||||||
:disabled="disabled"
|
|
||||||
:editable="editable"
|
|
||||||
:icon="icon"
|
|
||||||
:close-on-click="false">
|
|
||||||
</o-datepicker>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BDatepicker = {
|
|
||||||
template: '#b-datepicker-template',
|
|
||||||
props: {
|
|
||||||
dateFormatter: null,
|
|
||||||
dateParser: null,
|
|
||||||
disabled: Boolean,
|
|
||||||
editable: Boolean,
|
|
||||||
icon: String,
|
|
||||||
// iconPack: String, // ignored
|
|
||||||
modelValue: Date,
|
|
||||||
name: String,
|
|
||||||
placeholder: String,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
if (this.orugaValue != to) {
|
|
||||||
this.orugaValue = to
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
orugaValueUpdated(value) {
|
|
||||||
if (this.modelValue != value) {
|
|
||||||
this.$emit('update:modelValue', value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-datepicker', 'BDatepicker') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_dropdown_component()">
|
|
||||||
<script type="text/x-template" id="b-dropdown-template">
|
|
||||||
<o-dropdown :position="buefyPosition"
|
|
||||||
:triggers="triggers">
|
|
||||||
<slot name="trigger" />
|
|
||||||
<slot />
|
|
||||||
</o-dropdown>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BDropdown = {
|
|
||||||
template: '#b-dropdown-template',
|
|
||||||
props: {
|
|
||||||
position: String,
|
|
||||||
triggers: Array,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
buefyPosition() {
|
|
||||||
if (this.position) {
|
|
||||||
return this.position.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-dropdown', 'BDropdown') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_dropdown_item_component()">
|
|
||||||
<script type="text/x-template" id="b-dropdown-item-template">
|
|
||||||
<o-dropdown-item :label="label">
|
|
||||||
<slot />
|
|
||||||
</o-dropdown-item>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BDropdownItem = {
|
|
||||||
template: '#b-dropdown-item-template',
|
|
||||||
props: {
|
|
||||||
label: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-dropdown-item', 'BDropdownItem') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_field_component()">
|
|
||||||
<script type="text/x-template" id="b-field-template">
|
|
||||||
<o-field :grouped="grouped"
|
|
||||||
:label="label"
|
|
||||||
:horizontal="horizontal"
|
|
||||||
:expanded="expanded"
|
|
||||||
:variant="variant">
|
|
||||||
<slot />
|
|
||||||
</o-field>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BField = {
|
|
||||||
template: '#b-field-template',
|
|
||||||
props: {
|
|
||||||
expanded: Boolean,
|
|
||||||
grouped: Boolean,
|
|
||||||
horizontal: Boolean,
|
|
||||||
label: String,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
variant() {
|
|
||||||
if (this.type) {
|
|
||||||
return this.type.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-field', 'BField') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_icon_component()">
|
|
||||||
<script type="text/x-template" id="b-icon-template">
|
|
||||||
<o-icon :icon="icon"
|
|
||||||
:size="orugaSize" />
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BIcon = {
|
|
||||||
template: '#b-icon-template',
|
|
||||||
props: {
|
|
||||||
icon: String,
|
|
||||||
size: String,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
orugaSize() {
|
|
||||||
if (this.size) {
|
|
||||||
return this.size.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-icon', 'BIcon') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_input_component()">
|
|
||||||
<script type="text/x-template" id="b-input-template">
|
|
||||||
<o-input :type="type"
|
|
||||||
:disabled="disabled"
|
|
||||||
v-model="orugaValue"
|
|
||||||
@update:modelValue="val => $emit('update:modelValue', val)"
|
|
||||||
:autocomplete="autocomplete"
|
|
||||||
ref="input"
|
|
||||||
:expanded="expanded">
|
|
||||||
<slot />
|
|
||||||
</o-input>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BInput = {
|
|
||||||
template: '#b-input-template',
|
|
||||||
props: {
|
|
||||||
modelValue: null,
|
|
||||||
type: String,
|
|
||||||
autocomplete: String,
|
|
||||||
disabled: Boolean,
|
|
||||||
expanded: Boolean,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
if (this.orugaValue != to) {
|
|
||||||
this.orugaValue = to
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
focus() {
|
|
||||||
if (this.type == 'textarea') {
|
|
||||||
// TODO: this does not always work right?
|
|
||||||
this.$refs.input.$el.querySelector('textarea').focus()
|
|
||||||
} else {
|
|
||||||
// TODO: pretty sure we can rely on the <o-input> focus()
|
|
||||||
// here, but not sure why we weren't already doing that?
|
|
||||||
//this.$refs.input.$el.querySelector('input').focus()
|
|
||||||
this.$refs.input.focus()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-input', 'BInput') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_loading_component()">
|
|
||||||
<script type="text/x-template" id="b-loading-template">
|
|
||||||
<o-loading :full-page="isFullPage">
|
|
||||||
<slot />
|
|
||||||
</o-loading>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BLoading = {
|
|
||||||
template: '#b-loading-template',
|
|
||||||
props: {
|
|
||||||
isFullPage: Boolean,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-loading', 'BLoading') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_modal_component()">
|
|
||||||
<script type="text/x-template" id="b-modal-template">
|
|
||||||
<o-modal v-model:active="trueActive"
|
|
||||||
@update:active="activeChanged">
|
|
||||||
<slot />
|
|
||||||
</o-modal>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BModal = {
|
|
||||||
template: '#b-modal-template',
|
|
||||||
props: {
|
|
||||||
active: Boolean,
|
|
||||||
hasModalCard: Boolean, // nb. this is ignored
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
trueActive: this.active,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
active(to, from) {
|
|
||||||
this.trueActive = to
|
|
||||||
},
|
|
||||||
trueActive(to, from) {
|
|
||||||
if (this.active != to) {
|
|
||||||
this.tellParent(to)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
tellParent(active) {
|
|
||||||
// TODO: this does not work properly
|
|
||||||
this.$emit('update:active', active)
|
|
||||||
},
|
|
||||||
|
|
||||||
activeChanged(active) {
|
|
||||||
this.tellParent(active)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-modal', 'BModal') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_notification_component()">
|
|
||||||
<script type="text/x-template" id="b-notification-template">
|
|
||||||
<o-notification :variant="variant"
|
|
||||||
:closable="closable">
|
|
||||||
<slot />
|
|
||||||
</o-notification>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BNotification = {
|
|
||||||
template: '#b-notification-template',
|
|
||||||
props: {
|
|
||||||
type: String,
|
|
||||||
closable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
variant() {
|
|
||||||
if (this.type) {
|
|
||||||
return this.type.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-notification', 'BNotification') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_radio_component()">
|
|
||||||
<script type="text/x-template" id="b-radio-template">
|
|
||||||
<o-radio v-model="orugaValue"
|
|
||||||
@update:model-value="orugaValueUpdated"
|
|
||||||
:native-value="nativeValue">
|
|
||||||
<slot />
|
|
||||||
</o-radio>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BRadio = {
|
|
||||||
template: '#b-radio-template',
|
|
||||||
props: {
|
|
||||||
modelValue: null,
|
|
||||||
nativeValue: null,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
this.orugaValue = to
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
orugaValueUpdated(value) {
|
|
||||||
this.$emit('update:modelValue', value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-radio', 'BRadio') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_select_component()">
|
|
||||||
<script type="text/x-template" id="b-select-template">
|
|
||||||
<o-select :name="name"
|
|
||||||
ref="select"
|
|
||||||
v-model="orugaValue"
|
|
||||||
@update:model-value="orugaValueUpdated"
|
|
||||||
:expanded="expanded"
|
|
||||||
:multiple="multiple"
|
|
||||||
:size="orugaSize"
|
|
||||||
:native-size="nativeSize">
|
|
||||||
<slot />
|
|
||||||
</o-select>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BSelect = {
|
|
||||||
template: '#b-select-template',
|
|
||||||
props: {
|
|
||||||
expanded: Boolean,
|
|
||||||
modelValue: null,
|
|
||||||
multiple: Boolean,
|
|
||||||
name: String,
|
|
||||||
nativeSize: null,
|
|
||||||
size: null,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
this.orugaValue = to
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
orugaSize() {
|
|
||||||
if (this.size) {
|
|
||||||
return this.size.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
focus() {
|
|
||||||
this.$refs.select.focus()
|
|
||||||
},
|
|
||||||
orugaValueUpdated(value) {
|
|
||||||
this.$emit('update:modelValue', value)
|
|
||||||
this.$emit('input', value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-select', 'BSelect') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_steps_component()">
|
|
||||||
<script type="text/x-template" id="b-steps-template">
|
|
||||||
<o-steps v-model="orugaValue"
|
|
||||||
@update:model-value="orugaValueUpdated"
|
|
||||||
:animated="animated"
|
|
||||||
:rounded="rounded"
|
|
||||||
:has-navigation="hasNavigation"
|
|
||||||
:vertical="vertical">
|
|
||||||
<slot />
|
|
||||||
</o-steps>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BSteps = {
|
|
||||||
template: '#b-steps-template',
|
|
||||||
props: {
|
|
||||||
modelValue: null,
|
|
||||||
animated: Boolean,
|
|
||||||
rounded: Boolean,
|
|
||||||
hasNavigation: Boolean,
|
|
||||||
vertical: Boolean,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
orugaValue: this.modelValue,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue(to, from) {
|
|
||||||
this.orugaValue = to
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
orugaValueUpdated(value) {
|
|
||||||
this.$emit('update:modelValue', value)
|
|
||||||
this.$emit('input', value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-steps', 'BSteps') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_step_item_component()">
|
|
||||||
<script type="text/x-template" id="b-step-item-template">
|
|
||||||
<o-step-item :step="step"
|
|
||||||
:value="value"
|
|
||||||
:label="label"
|
|
||||||
:clickable="clickable">
|
|
||||||
<slot />
|
|
||||||
</o-step-item>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BStepItem = {
|
|
||||||
template: '#b-step-item-template',
|
|
||||||
props: {
|
|
||||||
step: null,
|
|
||||||
value: null,
|
|
||||||
label: String,
|
|
||||||
clickable: Boolean,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-step-item', 'BStepItem') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_table_component()">
|
|
||||||
<script type="text/x-template" id="b-table-template">
|
|
||||||
<o-table :data="data">
|
|
||||||
<slot />
|
|
||||||
</o-table>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BTable = {
|
|
||||||
template: '#b-table-template',
|
|
||||||
props: {
|
|
||||||
data: Array,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-table', 'BTable') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_table_column_component()">
|
|
||||||
<script type="text/x-template" id="b-table-column-template">
|
|
||||||
<o-table-column :field="field"
|
|
||||||
:label="label"
|
|
||||||
v-slot="props">
|
|
||||||
## TODO: this does not seem to really work for us...
|
|
||||||
<slot :props="props" />
|
|
||||||
</o-table-column>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BTableColumn = {
|
|
||||||
template: '#b-table-column-template',
|
|
||||||
props: {
|
|
||||||
field: String,
|
|
||||||
label: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-table-column', 'BTableColumn') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_b_tooltip_component()">
|
|
||||||
<script type="text/x-template" id="b-tooltip-template">
|
|
||||||
<o-tooltip :label="label"
|
|
||||||
:position="orugaPosition"
|
|
||||||
:multiline="multilined">
|
|
||||||
<slot />
|
|
||||||
</o-tooltip>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const BTooltip = {
|
|
||||||
template: '#b-tooltip-template',
|
|
||||||
props: {
|
|
||||||
label: String,
|
|
||||||
multilined: Boolean,
|
|
||||||
position: String,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
orugaPosition() {
|
|
||||||
if (this.position) {
|
|
||||||
return this.position.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('b-tooltip', 'BTooltip') %>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="make_once_button_component()">
|
|
||||||
<script type="text/x-template" id="once-button-template">
|
|
||||||
<b-button :type="type"
|
|
||||||
:native-type="nativeType"
|
|
||||||
:tag="tag"
|
|
||||||
:href="href"
|
|
||||||
:title="title"
|
|
||||||
:disabled="buttonDisabled"
|
|
||||||
@click="clicked"
|
|
||||||
icon-pack="fas"
|
|
||||||
:icon-left="iconLeft">
|
|
||||||
{{ buttonText }}
|
|
||||||
</b-button>
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const OnceButton = {
|
|
||||||
template: '#once-button-template',
|
|
||||||
props: {
|
|
||||||
type: String,
|
|
||||||
nativeType: String,
|
|
||||||
tag: String,
|
|
||||||
href: String,
|
|
||||||
text: String,
|
|
||||||
title: String,
|
|
||||||
iconLeft: String,
|
|
||||||
working: String,
|
|
||||||
workingText: String,
|
|
||||||
disabled: Boolean,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentText: null,
|
|
||||||
currentDisabled: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
buttonText: function() {
|
|
||||||
return this.currentText || this.text
|
|
||||||
},
|
|
||||||
buttonDisabled: function() {
|
|
||||||
if (this.currentDisabled !== null) {
|
|
||||||
return this.currentDisabled
|
|
||||||
}
|
|
||||||
return this.disabled
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
clicked(event) {
|
|
||||||
this.currentDisabled = true
|
|
||||||
if (this.workingText) {
|
|
||||||
this.currentText = this.workingText
|
|
||||||
} else if (this.working) {
|
|
||||||
this.currentText = this.working + ", please wait..."
|
|
||||||
} else {
|
|
||||||
this.currentText = "Working, please wait..."
|
|
||||||
}
|
|
||||||
// this.$nextTick(function() {
|
|
||||||
// this.$emit('click', event)
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% request.register_component('once-button', 'OnceButton') %>
|
|
||||||
</%def>
|
|
|
@ -1,32 +0,0 @@
|
||||||
|
|
||||||
<%def name="make_buefy_plugin()">
|
|
||||||
<script>
|
|
||||||
|
|
||||||
const BuefyPlugin = {
|
|
||||||
install(app, options) {
|
|
||||||
app.config.globalProperties.$buefy = {
|
|
||||||
|
|
||||||
toast: {
|
|
||||||
open(options) {
|
|
||||||
|
|
||||||
let variant = null
|
|
||||||
if (options.type) {
|
|
||||||
variant = options.type.replace(/^is-/, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
duration: options.duration,
|
|
||||||
message: options.message,
|
|
||||||
position: 'top',
|
|
||||||
variant,
|
|
||||||
}
|
|
||||||
|
|
||||||
const oruga = app.config.globalProperties.$oruga
|
|
||||||
oruga.notification.open(opts)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
|
@ -1,100 +0,0 @@
|
||||||
|
|
||||||
<%def name="make_http_plugin()">
|
|
||||||
<script>
|
|
||||||
|
|
||||||
const HttpPlugin = {
|
|
||||||
|
|
||||||
install(app, options) {
|
|
||||||
app.config.globalProperties.$http = {
|
|
||||||
|
|
||||||
get(url, options) {
|
|
||||||
if (options === undefined) {
|
|
||||||
options = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.params) {
|
|
||||||
// convert params to query string
|
|
||||||
const data = new URLSearchParams()
|
|
||||||
for (let [key, value] of Object.entries(options.params)) {
|
|
||||||
// nb. all values get converted to string here, so
|
|
||||||
// fallback to empty string to avoid null value
|
|
||||||
// from being interpreted as "null" string
|
|
||||||
if (value === null) {
|
|
||||||
value = ''
|
|
||||||
}
|
|
||||||
data.append(key, value)
|
|
||||||
}
|
|
||||||
// TODO: this should be smarter in case query string already exists
|
|
||||||
url += '?' + data.toString()
|
|
||||||
// params is not a valid arg for options to fetch()
|
|
||||||
delete options.params
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fetch(url, options).then(response => {
|
|
||||||
// original response does not contain 'data'
|
|
||||||
// attribute, so must use a "mock" response
|
|
||||||
// which does contain everything
|
|
||||||
response.json().then(json => {
|
|
||||||
resolve({
|
|
||||||
data: json,
|
|
||||||
headers: response.headers,
|
|
||||||
ok: response.ok,
|
|
||||||
redirected: response.redirected,
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
type: response.type,
|
|
||||||
url: response.url,
|
|
||||||
})
|
|
||||||
}, json => {
|
|
||||||
reject(response)
|
|
||||||
})
|
|
||||||
}, response => {
|
|
||||||
reject(response)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
post(url, params, options) {
|
|
||||||
|
|
||||||
if (params) {
|
|
||||||
|
|
||||||
// attach params as json
|
|
||||||
options.body = JSON.stringify(params)
|
|
||||||
|
|
||||||
// and declare content-type
|
|
||||||
options.headers = new Headers(options.headers)
|
|
||||||
options.headers.append('Content-Type', 'application/json')
|
|
||||||
}
|
|
||||||
|
|
||||||
options.method = 'POST'
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fetch(url, options).then(response => {
|
|
||||||
// original response does not contain 'data'
|
|
||||||
// attribute, so must use a "mock" response
|
|
||||||
// which does contain everything
|
|
||||||
response.json().then(json => {
|
|
||||||
resolve({
|
|
||||||
data: json,
|
|
||||||
headers: response.headers,
|
|
||||||
ok: response.ok,
|
|
||||||
redirected: response.redirected,
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
type: response.type,
|
|
||||||
url: response.url,
|
|
||||||
})
|
|
||||||
}, json => {
|
|
||||||
reject(response)
|
|
||||||
})
|
|
||||||
}, response => {
|
|
||||||
reject(response)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
|
@ -228,7 +228,6 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
Vue.component('wutta-autocomplete', WuttaAutocomplete)
|
Vue.component('wutta-autocomplete', WuttaAutocomplete)
|
||||||
<% request.register_component('wutta-autocomplete', 'WuttaAutocomplete') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -296,7 +295,6 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Vue.component('wutta-button', WuttaButton)
|
Vue.component('wutta-button', WuttaButton)
|
||||||
<% request.register_component('wutta-button', 'WuttaButton') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -369,7 +367,6 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Vue.component('wutta-datepicker', WuttaDatepicker)
|
Vue.component('wutta-datepicker', WuttaDatepicker)
|
||||||
<% request.register_component('wutta-datepicker', 'WuttaDatepicker') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -447,7 +444,6 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Vue.component('wutta-timepicker', WuttaTimepicker)
|
Vue.component('wutta-timepicker', WuttaTimepicker)
|
||||||
<% request.register_component('wutta-timepicker', 'WuttaTimepicker') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -620,7 +616,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.component('wutta-filter', WuttaFilter)
|
Vue.component('wutta-filter', WuttaFilter)
|
||||||
<% request.register_component('wutta-filter', 'WuttaFilter') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -659,7 +655,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.component('wutta-filter-value', WuttaFilterValue)
|
Vue.component('wutta-filter-value', WuttaFilterValue)
|
||||||
<% request.register_component('wutta-filter-value', 'WuttaFilterValue') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -706,7 +702,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.component('wutta-filter-date-value', WuttaFilterDateValue)
|
Vue.component('wutta-filter-date-value', WuttaFilterDateValue)
|
||||||
<% request.register_component('wutta-filter-date-value', 'WuttaFilterDateValue') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -731,6 +727,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.component('wutta-tool-panel', WuttaToolPanel)
|
Vue.component('wutta-tool-panel', WuttaToolPanel)
|
||||||
<% request.register_component('wutta-tool-panel', 'WuttaToolPanel') %>
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
@ -35,12 +35,8 @@ import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from pyramid.renderers import get_renderer
|
|
||||||
from webhelpers2.html import HTML, tags
|
from webhelpers2.html import HTML, tags
|
||||||
|
|
||||||
from wuttjamaican.util import resource_path
|
|
||||||
from wuttaweb.db import Session
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -593,156 +589,3 @@ def make_json_safe(value, key=None, warn=True):
|
||||||
log.warning("forced value to: %s", value)
|
log.warning("forced value to: %s", value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# theme functions
|
|
||||||
##############################
|
|
||||||
|
|
||||||
def get_available_themes(config):
|
|
||||||
"""
|
|
||||||
Returns the official list of theme names which are available for
|
|
||||||
use in the app. Privileged users may choose among these when
|
|
||||||
changing the global theme.
|
|
||||||
|
|
||||||
If config specifies a list, that will be honored. Otherwise the
|
|
||||||
default list is: ``['default', 'butterfly']``
|
|
||||||
|
|
||||||
Note that the 'default' theme is Vue 2 + Buefy, while 'butterfly'
|
|
||||||
is Vue 3 + Oruga.
|
|
||||||
|
|
||||||
You can specify via config by setting e.g.:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[wuttaweb]
|
|
||||||
themes.keys = default, butterfly, my-other-one
|
|
||||||
|
|
||||||
:param config: App :term:`config object`.
|
|
||||||
"""
|
|
||||||
# get available list from config, if it has one
|
|
||||||
available = config.get_list('wuttaweb.themes.keys',
|
|
||||||
default=['default', 'butterfly'])
|
|
||||||
|
|
||||||
# sort the list by name
|
|
||||||
available.sort()
|
|
||||||
|
|
||||||
# make default theme the first option
|
|
||||||
if 'default' in available:
|
|
||||||
available.remove('default')
|
|
||||||
available.insert(0, 'default')
|
|
||||||
|
|
||||||
return available
|
|
||||||
|
|
||||||
|
|
||||||
def get_effective_theme(config, theme=None, session=None):
|
|
||||||
"""
|
|
||||||
Validate and return the "effective" theme.
|
|
||||||
|
|
||||||
If caller specifies a ``theme`` then it will be returned (if
|
|
||||||
"available" - see below).
|
|
||||||
|
|
||||||
Otherwise the current theme will be read from db setting. (Note
|
|
||||||
we do not read simply from config object, we always read from db
|
|
||||||
setting - this allows for the theme setting to change dynamically
|
|
||||||
while app is running.)
|
|
||||||
|
|
||||||
In either case if the theme is not listed in
|
|
||||||
:func:`get_available_themes()` then a ``ValueError`` is raised.
|
|
||||||
|
|
||||||
:param config: App :term:`config object`.
|
|
||||||
|
|
||||||
:param theme: Optional name of desired theme, instead of getting
|
|
||||||
current theme per db setting.
|
|
||||||
|
|
||||||
:param session: Optional :term:`db session`.
|
|
||||||
|
|
||||||
:returns: Name of theme.
|
|
||||||
"""
|
|
||||||
app = config.get_app()
|
|
||||||
|
|
||||||
if not theme:
|
|
||||||
with app.short_session(session=session) as s:
|
|
||||||
theme = app.get_setting(s, 'wuttaweb.theme') or 'default'
|
|
||||||
|
|
||||||
# confirm requested theme is available
|
|
||||||
available = get_available_themes(config)
|
|
||||||
if theme not in available:
|
|
||||||
raise ValueError(f"theme not available: {theme}")
|
|
||||||
|
|
||||||
return theme
|
|
||||||
|
|
||||||
|
|
||||||
def get_theme_template_path(config, theme=None, session=None):
|
|
||||||
"""
|
|
||||||
Return the template path for effective theme.
|
|
||||||
|
|
||||||
If caller specifies a ``theme`` then it will be used; otherwise
|
|
||||||
the current theme will be read from db setting. The logic for
|
|
||||||
that happens in :func:`get_effective_theme()`, which this function
|
|
||||||
will call first.
|
|
||||||
|
|
||||||
Once we have the valid theme name, we check config in case it
|
|
||||||
specifies a template path override for it. But if not, a default
|
|
||||||
template path is assumed.
|
|
||||||
|
|
||||||
The default path would be expected to live under
|
|
||||||
``wuttaweb:templates/themes``; for instance the ``butterfly``
|
|
||||||
theme has a default template path of
|
|
||||||
``wuttaweb:templates/themes/butterfly``.
|
|
||||||
|
|
||||||
:param config: App :term:`config object`.
|
|
||||||
|
|
||||||
:param theme: Optional name of desired theme, instead of getting
|
|
||||||
current theme per db setting.
|
|
||||||
|
|
||||||
:param session: Optional :term:`db session`.
|
|
||||||
|
|
||||||
:returns: Path on disk to theme template folder.
|
|
||||||
"""
|
|
||||||
theme = get_effective_theme(config, theme=theme, session=session)
|
|
||||||
theme_path = config.get(f'wuttaweb.theme.{theme}',
|
|
||||||
default=f'wuttaweb:templates/themes/{theme}')
|
|
||||||
return resource_path(theme_path)
|
|
||||||
|
|
||||||
|
|
||||||
def set_app_theme(request, theme, session=None):
|
|
||||||
"""
|
|
||||||
Set the effective theme for the running app.
|
|
||||||
|
|
||||||
This will modify the *global* Mako template lookup directories,
|
|
||||||
i.e. app templates will change for all users immediately.
|
|
||||||
|
|
||||||
This will first validate the theme by calling
|
|
||||||
:func:`get_effective_theme()`. It then retrieves the template
|
|
||||||
path via :func:`get_theme_template_path()`.
|
|
||||||
|
|
||||||
The theme template path is then injected into the app settings
|
|
||||||
registry such that it overrides the Mako lookup directories.
|
|
||||||
|
|
||||||
It also will persist the theme name within db settings, so as to
|
|
||||||
ensure it survives app restart.
|
|
||||||
"""
|
|
||||||
config = request.wutta_config
|
|
||||||
app = config.get_app()
|
|
||||||
|
|
||||||
theme = get_effective_theme(config, theme=theme, session=session)
|
|
||||||
theme_path = get_theme_template_path(config, theme=theme, session=session)
|
|
||||||
|
|
||||||
# there's only one global template lookup; can get to it via any renderer
|
|
||||||
# but should *not* use /base.mako since that one is about to get volatile
|
|
||||||
renderer = get_renderer('/menu.mako')
|
|
||||||
lookup = renderer.lookup
|
|
||||||
|
|
||||||
# overwrite first entry in lookup's directory list
|
|
||||||
lookup.directories[0] = theme_path
|
|
||||||
|
|
||||||
# clear template cache for lookup object, so it will reload each (as needed)
|
|
||||||
lookup._collection.clear()
|
|
||||||
|
|
||||||
# persist current theme in db settings
|
|
||||||
with app.short_session(session=session) as s:
|
|
||||||
app.save_setting(s, 'wuttaweb.theme', theme)
|
|
||||||
|
|
||||||
# and cache in live app settings
|
|
||||||
request.registry.settings['wuttaweb.theme'] = theme
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
@ -32,7 +32,6 @@ from pyramid.renderers import render
|
||||||
from wuttaweb.views import View
|
from wuttaweb.views import View
|
||||||
from wuttaweb.forms import widgets
|
from wuttaweb.forms import widgets
|
||||||
from wuttaweb.db import Session
|
from wuttaweb.db import Session
|
||||||
from wuttaweb.util import set_app_theme
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -268,21 +267,6 @@ class CommonView(View):
|
||||||
which was just created as part of initial setup.
|
which was just created as part of initial setup.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def change_theme(self):
|
|
||||||
"""
|
|
||||||
This view will set the global app theme, then redirect back to
|
|
||||||
the referring page.
|
|
||||||
"""
|
|
||||||
theme = self.request.params.get('theme')
|
|
||||||
if theme:
|
|
||||||
try:
|
|
||||||
set_app_theme(self.request, theme, session=Session())
|
|
||||||
except Exception as error:
|
|
||||||
error = self.app.render_error(error)
|
|
||||||
self.request.session.flash(f"Failed to set theme: {error}", 'error')
|
|
||||||
referrer = self.request.params.get('referrer') or self.request.get_referrer()
|
|
||||||
return self.redirect(referrer)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def defaults(cls, config):
|
def defaults(cls, config):
|
||||||
cls._defaults(config)
|
cls._defaults(config)
|
||||||
|
@ -324,12 +308,6 @@ class CommonView(View):
|
||||||
route_name='setup',
|
route_name='setup',
|
||||||
renderer='/setup.mako')
|
renderer='/setup.mako')
|
||||||
|
|
||||||
# change theme
|
|
||||||
config.add_route('change_theme', '/change-theme', request_method='POST')
|
|
||||||
config.add_view(cls, attr='change_theme', route_name='change_theme')
|
|
||||||
config.add_wutta_permission('common', 'common.change_theme',
|
|
||||||
"Change global theme")
|
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
@ -129,8 +129,6 @@ class AppInfoView(MasterView):
|
||||||
{'name': f'{self.config.appname}.node_title'},
|
{'name': f'{self.config.appname}.node_title'},
|
||||||
{'name': f'{self.config.appname}.production',
|
{'name': f'{self.config.appname}.production',
|
||||||
'type': bool},
|
'type': bool},
|
||||||
{'name': 'wuttaweb.themes.expose_picker',
|
|
||||||
'type': bool},
|
|
||||||
{'name': f'{self.config.appname}.web.menus.handler.spec'},
|
{'name': f'{self.config.appname}.web.menus.handler.spec'},
|
||||||
# nb. this is deprecated; we define so it is auto-deleted
|
# nb. this is deprecated; we define so it is auto-deleted
|
||||||
# when we replace with newer setting
|
# when we replace with newer setting
|
||||||
|
|
|
@ -11,7 +11,7 @@ from pyramid import testing
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttjamaican.conf import WuttaConfig
|
||||||
from wuttaweb.forms import base, widgets
|
from wuttaweb.forms import base, widgets
|
||||||
from wuttaweb import helpers, subscribers
|
from wuttaweb import helpers
|
||||||
from wuttaweb.grids import Grid
|
from wuttaweb.grids import Grid
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,14 +25,10 @@ class TestForm(TestCase):
|
||||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||||
|
|
||||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||||
'wutta_config': self.config,
|
|
||||||
'mako.directories': ['wuttaweb:templates'],
|
'mako.directories': ['wuttaweb:templates'],
|
||||||
'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
||||||
})
|
})
|
||||||
|
|
||||||
event = MagicMock(request=self.request)
|
|
||||||
subscribers.new_request(event)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
testing.tearDown()
|
testing.tearDown()
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from wuttjamaican.testing import FileTestCase, ConfigTestCase, DataTestCase
|
from wuttjamaican.testing import FileTestCase, ConfigTestCase
|
||||||
|
|
||||||
from asgiref.wsgi import WsgiToAsgi
|
from asgiref.wsgi import WsgiToAsgi
|
||||||
from pyramid.config import Configurator
|
from pyramid.config import Configurator
|
||||||
|
@ -11,8 +11,6 @@ from pyramid.router import Router
|
||||||
|
|
||||||
from wuttaweb import app as mod
|
from wuttaweb import app as mod
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttjamaican.conf import WuttaConfig
|
||||||
from wuttjamaican.app import AppHandler
|
|
||||||
from wuttjamaican.util import resource_path
|
|
||||||
|
|
||||||
|
|
||||||
class TestWebAppProvider(TestCase):
|
class TestWebAppProvider(TestCase):
|
||||||
|
@ -47,25 +45,22 @@ class TestMakeWuttaConfig(FileTestCase):
|
||||||
self.assertIs(settings['wutta_config'], config)
|
self.assertIs(settings['wutta_config'], config)
|
||||||
|
|
||||||
|
|
||||||
class TestMakePyramidConfig(DataTestCase):
|
class TestMakePyramidConfig(TestCase):
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
with patch.object(AppHandler, 'make_session', return_value=self.session):
|
settings = {}
|
||||||
settings = {'wutta_config': self.config}
|
config = mod.make_pyramid_config(settings)
|
||||||
config = mod.make_pyramid_config(settings)
|
self.assertIsInstance(config, Configurator)
|
||||||
self.assertIsInstance(config, Configurator)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
|
|
||||||
|
|
||||||
class TestMain(DataTestCase):
|
class TestMain(FileTestCase):
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
with patch.object(AppHandler, 'make_session', return_value=self.session):
|
global_config = None
|
||||||
global_config = None
|
myconf = self.write_file('my.conf', '')
|
||||||
myconf = self.write_file('my.conf', '')
|
settings = {'wutta.config': myconf}
|
||||||
settings = {'wutta.config': myconf}
|
app = mod.main(global_config, **settings)
|
||||||
app = mod.main(global_config, **settings)
|
self.assertIsInstance(app, Router)
|
||||||
self.assertIsInstance(app, Router)
|
|
||||||
|
|
||||||
|
|
||||||
def mock_main(global_config, **settings):
|
def mock_main(global_config, **settings):
|
||||||
|
@ -80,20 +75,19 @@ def mock_main(global_config, **settings):
|
||||||
return pyramid_config.make_wsgi_app()
|
return pyramid_config.make_wsgi_app()
|
||||||
|
|
||||||
|
|
||||||
class TestMakeWsgiApp(DataTestCase):
|
class TestMakeWsgiApp(ConfigTestCase):
|
||||||
|
|
||||||
def test_with_callable(self):
|
def test_with_callable(self):
|
||||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
||||||
|
|
||||||
# specify config
|
# specify config
|
||||||
wsgi = mod.make_wsgi_app(mock_main, config=self.config)
|
wsgi = mod.make_wsgi_app(mock_main, config=self.config)
|
||||||
|
self.assertIsInstance(wsgi, Router)
|
||||||
|
|
||||||
|
# auto config
|
||||||
|
with patch.object(mod, 'make_config', return_value=self.config):
|
||||||
|
wsgi = mod.make_wsgi_app(mock_main)
|
||||||
self.assertIsInstance(wsgi, Router)
|
self.assertIsInstance(wsgi, Router)
|
||||||
|
|
||||||
# auto config
|
|
||||||
with patch.object(mod, 'make_config', return_value=self.config):
|
|
||||||
wsgi = mod.make_wsgi_app(mock_main)
|
|
||||||
self.assertIsInstance(wsgi, Router)
|
|
||||||
|
|
||||||
def test_with_spec(self):
|
def test_with_spec(self):
|
||||||
|
|
||||||
# specify config
|
# specify config
|
||||||
|
@ -109,87 +103,29 @@ class TestMakeWsgiApp(DataTestCase):
|
||||||
self.assertRaises(ValueError, mod.make_wsgi_app, 42, config=self.config)
|
self.assertRaises(ValueError, mod.make_wsgi_app, 42, config=self.config)
|
||||||
|
|
||||||
|
|
||||||
class TestMakeAsgiApp(DataTestCase):
|
class TestMakeAsgiApp(ConfigTestCase):
|
||||||
|
|
||||||
def test_with_callable(self):
|
def test_with_callable(self):
|
||||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
||||||
|
|
||||||
# specify config
|
# specify config
|
||||||
asgi = mod.make_asgi_app(mock_main, config=self.config)
|
asgi = mod.make_asgi_app(mock_main, config=self.config)
|
||||||
|
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||||
|
|
||||||
|
# auto config
|
||||||
|
with patch.object(mod, 'make_config', return_value=self.config):
|
||||||
|
asgi = mod.make_asgi_app(mock_main)
|
||||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||||
|
|
||||||
# auto config
|
|
||||||
with patch.object(mod, 'make_config', return_value=self.config):
|
|
||||||
asgi = mod.make_asgi_app(mock_main)
|
|
||||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
|
||||||
|
|
||||||
def test_with_spec(self):
|
def test_with_spec(self):
|
||||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
||||||
|
|
||||||
# specify config
|
# specify config
|
||||||
asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config)
|
asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config)
|
||||||
|
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||||
|
|
||||||
|
# auto config
|
||||||
|
with patch.object(mod, 'make_config', return_value=self.config):
|
||||||
|
asgi = mod.make_asgi_app('tests.test_app:mock_main')
|
||||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||||
|
|
||||||
# auto config
|
|
||||||
with patch.object(mod, 'make_config', return_value=self.config):
|
|
||||||
asgi = mod.make_asgi_app('tests.test_app:mock_main')
|
|
||||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
|
||||||
|
|
||||||
def test_invalid(self):
|
def test_invalid(self):
|
||||||
self.assertRaises(ValueError, mod.make_asgi_app, 42, config=self.config)
|
self.assertRaises(ValueError, mod.make_asgi_app, 42, config=self.config)
|
||||||
|
|
||||||
|
|
||||||
class TestEstablishTheme(DataTestCase):
|
|
||||||
|
|
||||||
def test_default(self):
|
|
||||||
settings = {
|
|
||||||
'wutta_config': self.config,
|
|
||||||
'mako.directories': ['wuttaweb:templates'],
|
|
||||||
}
|
|
||||||
mod.establish_theme(settings)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
self.assertEqual(settings['mako.directories'], [
|
|
||||||
resource_path('wuttaweb:templates/themes/default'),
|
|
||||||
'wuttaweb:templates',
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_mako_dirs_as_string(self):
|
|
||||||
settings = {
|
|
||||||
'wutta_config': self.config,
|
|
||||||
'mako.directories': 'wuttaweb:templates',
|
|
||||||
}
|
|
||||||
mod.establish_theme(settings)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
self.assertEqual(settings['mako.directories'], [
|
|
||||||
resource_path('wuttaweb:templates/themes/default'),
|
|
||||||
'wuttaweb:templates',
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_butterfly(self):
|
|
||||||
settings = {
|
|
||||||
'wutta_config': self.config,
|
|
||||||
'mako.directories': 'wuttaweb:templates',
|
|
||||||
}
|
|
||||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly')
|
|
||||||
self.session.commit()
|
|
||||||
mod.establish_theme(settings)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
|
||||||
self.assertEqual(settings['mako.directories'], [
|
|
||||||
resource_path('wuttaweb:templates/themes/butterfly'),
|
|
||||||
'wuttaweb:templates',
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_custom(self):
|
|
||||||
settings = {
|
|
||||||
'wutta_config': self.config,
|
|
||||||
'mako.directories': 'wuttaweb:templates',
|
|
||||||
}
|
|
||||||
self.config.setdefault('wuttaweb.themes.keys', 'anotherone')
|
|
||||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'anotherone')
|
|
||||||
self.session.commit()
|
|
||||||
mod.establish_theme(settings)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'anotherone')
|
|
||||||
self.assertEqual(settings['mako.directories'], [
|
|
||||||
resource_path('wuttaweb:templates/themes/anotherone'),
|
|
||||||
'wuttaweb:templates',
|
|
||||||
])
|
|
||||||
|
|
|
@ -41,21 +41,13 @@ class TestNewRequest(TestCase):
|
||||||
self.assertIs(self.request.wutta_config, self.config)
|
self.assertIs(self.request.wutta_config, self.config)
|
||||||
|
|
||||||
def test_use_oruga_default(self):
|
def test_use_oruga_default(self):
|
||||||
|
event = MagicMock(request=self.request)
|
||||||
|
|
||||||
# request gets a new attr, false by default
|
# request gets a new attr, false by default
|
||||||
self.assertFalse(hasattr(self.request, 'use_oruga'))
|
self.assertFalse(hasattr(self.request, 'use_oruga'))
|
||||||
event = MagicMock(request=self.request)
|
|
||||||
subscribers.new_request(event)
|
subscribers.new_request(event)
|
||||||
self.assertFalse(self.request.use_oruga)
|
self.assertFalse(self.request.use_oruga)
|
||||||
|
|
||||||
# nb. using 'butterfly' theme should cause the 'use_oruga'
|
|
||||||
# flag to be turned on by default
|
|
||||||
self.request = self.make_request()
|
|
||||||
self.request.registry.settings['wuttaweb.theme'] = 'butterfly'
|
|
||||||
event = MagicMock(request=self.request)
|
|
||||||
subscribers.new_request(event)
|
|
||||||
self.assertTrue(self.request.use_oruga)
|
|
||||||
|
|
||||||
def test_use_oruga_custom(self):
|
def test_use_oruga_custom(self):
|
||||||
self.config.setdefault('wuttaweb.oruga_detector.spec', 'tests.test_subscribers:custom_oruga_detector')
|
self.config.setdefault('wuttaweb.oruga_detector.spec', 'tests.test_subscribers:custom_oruga_detector')
|
||||||
event = MagicMock(request=self.request)
|
event = MagicMock(request=self.request)
|
||||||
|
@ -65,26 +57,6 @@ class TestNewRequest(TestCase):
|
||||||
subscribers.new_request(event)
|
subscribers.new_request(event)
|
||||||
self.assertTrue(self.request.use_oruga)
|
self.assertTrue(self.request.use_oruga)
|
||||||
|
|
||||||
def test_register_component(self):
|
|
||||||
event = MagicMock(request=self.request)
|
|
||||||
subscribers.new_request(event)
|
|
||||||
|
|
||||||
# component tracking dict is missing at first
|
|
||||||
self.assertFalse(hasattr(self.request, '_wuttaweb_registered_components'))
|
|
||||||
|
|
||||||
# registering a component
|
|
||||||
self.request.register_component('foo-example', 'FooExample')
|
|
||||||
self.assertTrue(hasattr(self.request, '_wuttaweb_registered_components'))
|
|
||||||
self.assertEqual(len(self.request._wuttaweb_registered_components), 1)
|
|
||||||
self.assertIn('foo-example', self.request._wuttaweb_registered_components)
|
|
||||||
self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample')
|
|
||||||
|
|
||||||
# re-registering same name
|
|
||||||
self.request.register_component('foo-example', 'FooExample')
|
|
||||||
self.assertEqual(len(self.request._wuttaweb_registered_components), 1)
|
|
||||||
self.assertIn('foo-example', self.request._wuttaweb_registered_components)
|
|
||||||
self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample')
|
|
||||||
|
|
||||||
def test_get_referrer(self):
|
def test_get_referrer(self):
|
||||||
event = MagicMock(request=self.request)
|
event = MagicMock(request=self.request)
|
||||||
|
|
||||||
|
@ -374,7 +346,7 @@ class TestBeforeRender(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.config = WuttaConfig(defaults={
|
self.config = WuttaConfig(defaults={
|
||||||
'wutta.web.menus.handler.spec': 'tests.util:NullMenuHandler',
|
'wutta.web.menus.handler_spec': 'tests.util:NullMenuHandler',
|
||||||
})
|
})
|
||||||
|
|
||||||
def make_request(self):
|
def make_request(self):
|
||||||
|
@ -406,24 +378,6 @@ class TestBeforeRender(TestCase):
|
||||||
self.assertIn('json', event)
|
self.assertIn('json', event)
|
||||||
self.assertIs(event['json'], json)
|
self.assertIs(event['json'], json)
|
||||||
|
|
||||||
# current theme should be 'default' and picker not exposed
|
|
||||||
self.assertEqual(event['theme'], 'default')
|
|
||||||
self.assertFalse(event['expose_theme_picker'])
|
|
||||||
self.assertNotIn('available_themes', event)
|
|
||||||
|
|
||||||
def test_custom_theme(self):
|
|
||||||
self.config.setdefault('wuttaweb.themes.expose_picker', 'true')
|
|
||||||
request = self.make_request()
|
|
||||||
request.registry.settings['wuttaweb.theme'] = 'butterfly'
|
|
||||||
event = {'request': request}
|
|
||||||
|
|
||||||
# event dict will get populated with more context
|
|
||||||
subscribers.before_render(event)
|
|
||||||
self.assertEqual(event['theme'], 'butterfly')
|
|
||||||
self.assertTrue(event['expose_theme_picker'])
|
|
||||||
self.assertIn('available_themes', event)
|
|
||||||
self.assertEqual(event['available_themes'], ['default', 'butterfly'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestIncludeMe(TestCase):
|
class TestIncludeMe(TestCase):
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,9 @@ from fanstatic import Library, Resource
|
||||||
from pyramid import testing
|
from pyramid import testing
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttjamaican.conf import WuttaConfig
|
||||||
from wuttjamaican.testing import ConfigTestCase, DataTestCase
|
from wuttjamaican.testing import ConfigTestCase
|
||||||
from wuttjamaican.util import resource_path
|
|
||||||
|
|
||||||
from wuttaweb import util as mod
|
from wuttaweb import util as mod
|
||||||
from wuttaweb.app import establish_theme
|
|
||||||
from wuttaweb.testing import WebTestCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestFieldList(TestCase):
|
class TestFieldList(TestCase):
|
||||||
|
@ -624,89 +621,3 @@ class TestMakeJsonSafe(TestCase):
|
||||||
'bar',
|
'bar',
|
||||||
"Betty Boop",
|
"Betty Boop",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class TestGetAvailableThemes(TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.config = WuttaConfig()
|
|
||||||
self.app = self.config.get_app()
|
|
||||||
|
|
||||||
def test_defaults(self):
|
|
||||||
themes = mod.get_available_themes(self.config)
|
|
||||||
self.assertEqual(themes, ['default', 'butterfly'])
|
|
||||||
|
|
||||||
def test_sorting(self):
|
|
||||||
self.config.setdefault('wuttaweb.themes.keys', 'default, foo2, foo4, foo1')
|
|
||||||
themes = mod.get_available_themes(self.config)
|
|
||||||
self.assertEqual(themes, ['default', 'foo1', 'foo2', 'foo4'])
|
|
||||||
|
|
||||||
def test_default_omitted(self):
|
|
||||||
self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo')
|
|
||||||
themes = mod.get_available_themes(self.config)
|
|
||||||
self.assertEqual(themes, ['default', 'butterfly', 'foo'])
|
|
||||||
|
|
||||||
def test_default_notfirst(self):
|
|
||||||
self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo, default')
|
|
||||||
themes = mod.get_available_themes(self.config)
|
|
||||||
self.assertEqual(themes, ['default', 'butterfly', 'foo'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetEffectiveTheme(DataTestCase):
|
|
||||||
|
|
||||||
def test_default(self):
|
|
||||||
theme = mod.get_effective_theme(self.config)
|
|
||||||
self.assertEqual(theme, 'default')
|
|
||||||
|
|
||||||
def test_override_config(self):
|
|
||||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly')
|
|
||||||
self.session.commit()
|
|
||||||
theme = mod.get_effective_theme(self.config)
|
|
||||||
self.assertEqual(theme, 'butterfly')
|
|
||||||
|
|
||||||
def test_override_param(self):
|
|
||||||
theme = mod.get_effective_theme(self.config, theme='butterfly')
|
|
||||||
self.assertEqual(theme, 'butterfly')
|
|
||||||
|
|
||||||
def test_invalid(self):
|
|
||||||
self.assertRaises(ValueError, mod.get_effective_theme, self.config, theme='invalid')
|
|
||||||
|
|
||||||
|
|
||||||
class TestThemeTemplatePath(DataTestCase):
|
|
||||||
|
|
||||||
def test_default(self):
|
|
||||||
path = mod.get_theme_template_path(self.config, theme='default')
|
|
||||||
# nb. even though the path does not exist, we still want to
|
|
||||||
# pretend like it does, hence prev call should return this:
|
|
||||||
expected = resource_path('wuttaweb:templates/themes/default')
|
|
||||||
self.assertEqual(path, expected)
|
|
||||||
|
|
||||||
def test_default(self):
|
|
||||||
path = mod.get_theme_template_path(self.config, theme='butterfly')
|
|
||||||
expected = resource_path('wuttaweb:templates/themes/butterfly')
|
|
||||||
self.assertEqual(path, expected)
|
|
||||||
|
|
||||||
def test_custom(self):
|
|
||||||
self.config.setdefault('wuttaweb.themes.keys', 'default, butterfly, poser')
|
|
||||||
self.config.setdefault('wuttaweb.theme.poser', '/tmp/poser-theme')
|
|
||||||
path = mod.get_theme_template_path(self.config, theme='poser')
|
|
||||||
self.assertEqual(path, '/tmp/poser-theme')
|
|
||||||
|
|
||||||
|
|
||||||
class TestSetAppTheme(WebTestCase):
|
|
||||||
|
|
||||||
def test_basic(self):
|
|
||||||
|
|
||||||
# establish default
|
|
||||||
settings = self.request.registry.settings
|
|
||||||
self.assertNotIn('wuttaweb.theme', settings)
|
|
||||||
establish_theme(settings)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
|
|
||||||
# set to butterfly
|
|
||||||
mod.set_app_theme(self.request, 'butterfly', session=self.session)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
|
||||||
|
|
||||||
# set back to default
|
|
||||||
mod.set_app_theme(self.request, 'default', session=self.session)
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import colander
|
||||||
|
|
||||||
from wuttaweb.views import common as mod
|
from wuttaweb.views import common as mod
|
||||||
from wuttaweb.testing import WebTestCase
|
from wuttaweb.testing import WebTestCase
|
||||||
from wuttaweb.app import establish_theme
|
|
||||||
|
|
||||||
|
|
||||||
class TestCommonView(WebTestCase):
|
class TestCommonView(WebTestCase):
|
||||||
|
@ -181,32 +180,3 @@ class TestCommonView(WebTestCase):
|
||||||
self.assertEqual(person.first_name, "Barney")
|
self.assertEqual(person.first_name, "Barney")
|
||||||
self.assertEqual(person.last_name, "Rubble")
|
self.assertEqual(person.last_name, "Rubble")
|
||||||
self.assertEqual(person.full_name, "Barney Rubble")
|
self.assertEqual(person.full_name, "Barney Rubble")
|
||||||
|
|
||||||
def test_change_theme(self):
|
|
||||||
self.pyramid_config.add_route('home', '/')
|
|
||||||
settings = self.request.registry.settings
|
|
||||||
establish_theme(settings)
|
|
||||||
view = self.make_view()
|
|
||||||
|
|
||||||
# theme is not changed if not provided by caller
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
with patch.object(mod, 'set_app_theme') as set_app_theme:
|
|
||||||
view.change_theme()
|
|
||||||
set_app_theme.assert_not_called()
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
|
||||||
|
|
||||||
# but theme will change if provided
|
|
||||||
with patch.object(self.request, 'params', new={'theme': 'butterfly'}):
|
|
||||||
with patch.object(mod, 'Session', return_value=self.session):
|
|
||||||
view.change_theme()
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
|
||||||
|
|
||||||
# flash error if invalid theme is provided
|
|
||||||
self.assertFalse(self.request.session.peek_flash('error'))
|
|
||||||
with patch.object(self.request, 'params', new={'theme': 'anotherone'}):
|
|
||||||
with patch.object(mod, 'Session', return_value=self.session):
|
|
||||||
view.change_theme()
|
|
||||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
|
||||||
self.assertTrue(self.request.session.peek_flash('error'))
|
|
||||||
messages = self.request.session.pop_flash('error')
|
|
||||||
self.assertIn('Failed to set theme', messages[0])
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue