Compare commits
3 commits
dd207a4a05
...
23d227b2c6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
23d227b2c6 | ||
![]() |
d35e6e71c9 | ||
![]() |
ed67cdb2d8 |
|
@ -33,6 +33,7 @@ dependencies = [
|
|||
"pyramid>=2",
|
||||
"pyramid_beaker",
|
||||
"pyramid_deform",
|
||||
"pyramid_fanstatic",
|
||||
"pyramid_mako",
|
||||
"pyramid_tm",
|
||||
"waitress",
|
||||
|
|
|
@ -110,7 +110,12 @@ def make_pyramid_config(settings):
|
|||
|
||||
The config is initialized with certain features deemed useful for
|
||||
all apps.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`pyramid:pyramid.config.Configurator`.
|
||||
"""
|
||||
settings.setdefault('fanstatic.versioning', 'true')
|
||||
settings.setdefault('mako.directories', ['wuttaweb:templates'])
|
||||
settings.setdefault('pyramid_deform.template_search_path',
|
||||
'wuttaweb:templates/deform')
|
||||
|
||||
|
@ -119,8 +124,14 @@ def make_pyramid_config(settings):
|
|||
# configure user authorization / authentication
|
||||
pyramid_config.set_security_policy(WuttaSecurityPolicy())
|
||||
|
||||
# require CSRF token for POST
|
||||
pyramid_config.set_default_csrf_options(require_csrf=True,
|
||||
token='_csrf',
|
||||
header='X-CSRF-TOKEN')
|
||||
|
||||
pyramid_config.include('pyramid_beaker')
|
||||
pyramid_config.include('pyramid_deform')
|
||||
pyramid_config.include('pyramid_fanstatic')
|
||||
pyramid_config.include('pyramid_mako')
|
||||
pyramid_config.include('pyramid_tm')
|
||||
|
||||
|
@ -143,8 +154,6 @@ def main(global_config, **settings):
|
|||
will need to define their own ``main()`` function, and use that
|
||||
instead.
|
||||
"""
|
||||
settings.setdefault('mako.directories', ['wuttaweb:templates'])
|
||||
|
||||
wutta_config = make_wutta_config(settings)
|
||||
pyramid_config = make_pyramid_config(settings)
|
||||
|
||||
|
|
|
@ -323,6 +323,7 @@ class Form:
|
|||
"""
|
||||
context['form'] = self
|
||||
context.setdefault('form_attrs', {})
|
||||
context.setdefault('request', self.request)
|
||||
|
||||
# auto disable button on submit
|
||||
if self.auto_disable_submit:
|
||||
|
|
|
@ -38,12 +38,20 @@ instance:
|
|||
|
||||
This module contains the following references:
|
||||
|
||||
* :func:`~wuttaweb.util.get_liburl()`
|
||||
* all names from :mod:`webhelpers2:webhelpers2.html`
|
||||
* all names from :mod:`webhelpers2:webhelpers2.html.tags`
|
||||
* :func:`~wuttaweb.util.get_liburl()`
|
||||
* :func:`~wuttaweb.util.get_csrf_token()`
|
||||
* :func:`~wuttaweb.util.render_csrf_token()` (as :func:`csrf_token()`)
|
||||
|
||||
.. function:: csrf_token
|
||||
|
||||
This is a shorthand reference to
|
||||
:func:`wuttaweb.util.render_csrf_token()`.
|
||||
|
||||
"""
|
||||
|
||||
from webhelpers2.html import *
|
||||
from webhelpers2.html.tags import *
|
||||
|
||||
from wuttaweb.util import get_liburl
|
||||
from wuttaweb.util import get_liburl, get_csrf_token, render_csrf_token as csrf_token
|
||||
|
|
177
src/wuttaweb/templates/appinfo/configure.mako
Normal file
177
src/wuttaweb/templates/appinfo/configure.mako
Normal file
|
@ -0,0 +1,177 @@
|
|||
## -*- 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; width: 50%;">
|
||||
|
||||
<b-field label="App Title">
|
||||
<b-input name="${app.appname}.app_title"
|
||||
v-model="simpleSettings['${app.appname}.app_title']"
|
||||
@input="settingsNeedSaved = true">
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field>
|
||||
<b-checkbox name="${app.appname}.production"
|
||||
v-model="simpleSettings['${app.appname}.production']"
|
||||
native-value="true"
|
||||
@input="settingsNeedSaved = true">
|
||||
Production Mode
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
</div>
|
||||
|
||||
<h3 class="block is-size-3">Web Libraries</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
|
||||
<${b}-table :data="weblibs">
|
||||
|
||||
<${b}-table-column field="title"
|
||||
label="Name"
|
||||
v-slot="props">
|
||||
{{ props.row.title }}
|
||||
</${b}-table-column>
|
||||
|
||||
<${b}-table-column field="configured_version"
|
||||
label="Version"
|
||||
v-slot="props">
|
||||
{{ props.row.configured_version || props.row.default_version }}
|
||||
</${b}-table-column>
|
||||
|
||||
<${b}-table-column field="configured_url"
|
||||
label="URL Override"
|
||||
v-slot="props">
|
||||
{{ props.row.configured_url }}
|
||||
</${b}-table-column>
|
||||
|
||||
<${b}-table-column field="live_url"
|
||||
label="Effective (Live) URL"
|
||||
v-slot="props">
|
||||
<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"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
@click.prevent="editWebLibraryInit(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
</${b}-table-column>
|
||||
|
||||
</${b}-table>
|
||||
|
||||
% for weblib in weblibs or []:
|
||||
${h.hidden('wuttaweb.libver.{}'.format(weblib['key']), **{':value': "simpleSettings['wuttaweb.libver.{}']".format(weblib['key'])})}
|
||||
${h.hidden('wuttaweb.liburl.{}'.format(weblib['key']), **{':value': "simpleSettings['wuttaweb.liburl.{}']".format(weblib['key'])})}
|
||||
% endfor
|
||||
|
||||
<${b}-modal has-modal-card
|
||||
% if request.use_oruga:
|
||||
v-model:active="editWebLibraryShowDialog"
|
||||
% else:
|
||||
:active.sync="editWebLibraryShowDialog"
|
||||
% endif
|
||||
>
|
||||
<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"
|
||||
expanded />
|
||||
</b-field>
|
||||
|
||||
<b-field label="Effective URL (as of last page load)">
|
||||
<b-input v-model="editWebLibraryRecord.live_url"
|
||||
disabled
|
||||
expanded />
|
||||
</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>
|
||||
|
||||
ThisPageData.weblibs = ${json.dumps(weblibs or [])|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[`wuttaweb.libver.${'$'}{this.editWebLibraryRecord.key}`] = this.editWebLibraryVersion
|
||||
this.simpleSettings[`wuttaweb.liburl.${'$'}{this.editWebLibraryRecord.key}`] = this.editWebLibraryURL
|
||||
|
||||
this.settingsNeedSaved = true
|
||||
this.editWebLibraryShowDialog = false
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
||||
${parent.body()}
|
|
@ -209,16 +209,14 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="level" style="margin: 0.5rem auto;">
|
||||
<nav class="level" style="margin: 0.5rem 0.5rem 0.5rem auto;">
|
||||
<div class="level-left">
|
||||
|
||||
## Current Context
|
||||
<div id="current-context" class="level-item">
|
||||
% if index_title:
|
||||
% if index_url:
|
||||
<span class="header-text">
|
||||
${h.link_to(index_title, index_url)}
|
||||
</span>
|
||||
<h1 class="title">${h.link_to(index_title, index_url)}</h1>
|
||||
% else:
|
||||
<h1 class="title">${index_title}</h1>
|
||||
% endif
|
||||
|
@ -226,6 +224,23 @@
|
|||
</div>
|
||||
|
||||
</div><!-- level-left -->
|
||||
|
||||
<div class="level-right">
|
||||
|
||||
## TODO
|
||||
% if master and master.configurable and not master.configuring:
|
||||
<div class="level-item">
|
||||
<b-button type="is-primary"
|
||||
tag="a"
|
||||
href="${url(f'{route_prefix}.configure')}"
|
||||
icon-pack="fas"
|
||||
icon-left="cog">
|
||||
Configure
|
||||
</b-button>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
</div> <!-- level-right -->
|
||||
</nav><!-- level -->
|
||||
</header>
|
||||
|
||||
|
@ -318,8 +333,7 @@
|
|||
<div class="navbar-dropdown">
|
||||
% if request.is_root:
|
||||
${h.form(url('stop_root'), ref='stopBeingRootForm')}
|
||||
## TODO
|
||||
## ${h.csrf_token(request)}
|
||||
${h.csrf_token(request)}
|
||||
<input type="hidden" name="referrer" value="${request.current_route_url()}" />
|
||||
<a @click="stopBeingRoot()"
|
||||
class="navbar-item has-background-danger has-text-white">
|
||||
|
@ -328,8 +342,7 @@
|
|||
${h.end_form()}
|
||||
% elif request.is_admin:
|
||||
${h.form(url('become_root'), ref='startBeingRootForm')}
|
||||
## TODO
|
||||
## ${h.csrf_token(request)}
|
||||
${h.csrf_token(request)}
|
||||
<input type="hidden" name="referrer" value="${request.current_route_url()}" />
|
||||
<a @click="startBeingRoot()"
|
||||
class="navbar-item has-background-danger has-text-white">
|
||||
|
|
181
src/wuttaweb/templates/configure.mako
Normal file
181
src/wuttaweb/templates/configure.mako
Normal file
|
@ -0,0 +1,181 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/page.mako" />
|
||||
|
||||
<%def name="title()">Configure ${config_title}</%def>
|
||||
|
||||
<%def name="page_content()">
|
||||
<br />
|
||||
${self.buttons_content()}
|
||||
|
||||
${h.form(request.current_route_url(), enctype='multipart/form-data', ref='saveSettingsForm', **{'@submit': 'saveSettingsFormSubmit'})}
|
||||
${h.csrf_token(request)}
|
||||
${self.form_content()}
|
||||
${h.end_form()}
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="purgeSettingsShowDialog">
|
||||
<div class="modal-card">
|
||||
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Remove All Settings</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body">
|
||||
<p class="block">
|
||||
Really remove all settings for ${config_title} from the DB?
|
||||
</p>
|
||||
<p class="block">
|
||||
Note that when you <span class="is-italic">save</span>
|
||||
settings, any existing settings are first removed and then
|
||||
new ones are saved.
|
||||
</p>
|
||||
<p class="block">
|
||||
But here you can remove existing without saving new
|
||||
ones. It is basically "factory reset" for
|
||||
${config_title}.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<b-button @click="purgeSettingsShowDialog = false">
|
||||
Cancel
|
||||
</b-button>
|
||||
${h.form(request.current_route_url())}
|
||||
${h.csrf_token(request)}
|
||||
${h.hidden('remove_settings', 'true')}
|
||||
<b-button type="is-danger"
|
||||
native-type="submit"
|
||||
:disabled="purgingSettings"
|
||||
icon-pack="fas"
|
||||
icon-left="trash"
|
||||
@click="purgingSettings = true">
|
||||
{{ purgingSettings ? "Working, please wait..." : "Remove All Settings for ${config_title}" }}
|
||||
</b-button>
|
||||
${h.end_form()}
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
|
||||
</%def>
|
||||
|
||||
<%def name="buttons_content()">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
|
||||
<div class="level-item">
|
||||
${self.intro_message()}
|
||||
</div>
|
||||
|
||||
<div class="level-item">
|
||||
${self.save_undo_buttons()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
${self.purge_button()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="intro_message()">
|
||||
<p class="block">
|
||||
This page lets you modify the settings for ${config_title}.
|
||||
</p>
|
||||
</%def>
|
||||
|
||||
<%def name="save_undo_buttons()">
|
||||
<div class="buttons"
|
||||
v-if="settingsNeedSaved">
|
||||
<b-button type="is-primary"
|
||||
@click="saveSettings"
|
||||
:disabled="savingSettings"
|
||||
icon-pack="fas"
|
||||
icon-left="save">
|
||||
{{ savingSettings ? "Working, please wait..." : "Save All Settings" }}
|
||||
</b-button>
|
||||
<b-button tag="a" href="${request.current_route_url()}"
|
||||
icon-pack="fas"
|
||||
icon-left="undo"
|
||||
@click="undoChanges = true"
|
||||
:disabled="undoChanges">
|
||||
{{ undoChanges ? "Working, please wait..." : "Undo All Changes" }}
|
||||
</b-button>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="purge_button()">
|
||||
<b-button type="is-danger"
|
||||
@click="purgeSettingsShowDialog = true"
|
||||
icon-pack="fas"
|
||||
icon-left="trash">
|
||||
Remove All Settings
|
||||
</b-button>
|
||||
</%def>
|
||||
|
||||
<%def name="form_content()">
|
||||
<b-notification type="is-warning"
|
||||
:closable="false">
|
||||
<h4 class="block is-size-4">
|
||||
TODO: you must define the
|
||||
<span class="is-family-monospace"><%def name="form_content()"></span>
|
||||
template block
|
||||
</h4>
|
||||
<p class="block">
|
||||
or if you need more control, define the
|
||||
<span class="is-family-monospace"><%def name="page_content()"></span>
|
||||
template block
|
||||
</p>
|
||||
<p class="block">
|
||||
for a real-world example see template at
|
||||
<span class="is-family-monospace">wuttaweb:templates/appinfo/configure.mako</span>
|
||||
</p>
|
||||
</b-notification>
|
||||
</%def>
|
||||
|
||||
<%def name="modify_this_page_vars()">
|
||||
${parent.modify_this_page_vars()}
|
||||
<script>
|
||||
|
||||
% if simple_settings is not Undefined:
|
||||
ThisPageData.simpleSettings = ${json.dumps(simple_settings)|n}
|
||||
% endif
|
||||
|
||||
ThisPageData.purgeSettingsShowDialog = false
|
||||
ThisPageData.purgingSettings = false
|
||||
|
||||
ThisPageData.settingsNeedSaved = false
|
||||
ThisPageData.undoChanges = false
|
||||
ThisPageData.savingSettings = false
|
||||
|
||||
ThisPage.methods.saveSettings = function() {
|
||||
this.savingSettings = true
|
||||
this.$refs.saveSettingsForm.submit()
|
||||
}
|
||||
|
||||
// nb. this is here to avoid auto-submitting form when user
|
||||
// presses ENTER while some random input field has focus
|
||||
ThisPage.methods.saveSettingsFormSubmit = function(event) {
|
||||
if (!this.savingSettings) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
// cf. https://stackoverflow.com/a/56551646
|
||||
ThisPage.methods.beforeWindowUnload = function(e) {
|
||||
if (this.settingsNeedSaved && !this.savingSettings && !this.undoChanges) {
|
||||
e.preventDefault()
|
||||
e.returnValue = ''
|
||||
}
|
||||
}
|
||||
|
||||
ThisPage.created = function() {
|
||||
window.addEventListener('beforeunload', this.beforeWindowUnload)
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
||||
${parent.body()}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
<script type="text/x-template" id="${form.vue_tagname}-template">
|
||||
${h.form(form.action_url, method='post', enctype='multipart/form-data', **form_attrs)}
|
||||
${h.csrf_token(request)}
|
||||
|
||||
<section>
|
||||
% for fieldname in form:
|
||||
|
|
9
src/wuttaweb/templates/master/configure.mako
Normal file
9
src/wuttaweb/templates/master/configure.mako
Normal file
|
@ -0,0 +1,9 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/configure.mako" />
|
||||
|
||||
## NB. /master/configure.mako is only a placeholder.
|
||||
## there is no reason to *inherit* from this template;
|
||||
## you can always just inherit from /configure.mako
|
||||
|
||||
|
||||
${parent.body()}
|
|
@ -26,6 +26,8 @@ Web Utilities
|
|||
|
||||
import importlib
|
||||
|
||||
from webhelpers2.html import HTML, tags
|
||||
|
||||
|
||||
def get_form_data(request):
|
||||
"""
|
||||
|
@ -51,6 +53,7 @@ def get_form_data(request):
|
|||
def get_libver(
|
||||
request,
|
||||
key,
|
||||
configured_only=False,
|
||||
default_only=False,
|
||||
prefix='wuttaweb',
|
||||
):
|
||||
|
@ -76,13 +79,11 @@ def get_libver(
|
|||
:param key: Unique key for the library, as string. Possibilities
|
||||
are the same as for :func:`get_liburl()`.
|
||||
|
||||
:param default_only: If this flag is ``True``, the logic will
|
||||
*not* look for a "configured" version but rather will *only*
|
||||
return the "default" version regardless of config.
|
||||
:param configured_only: Pass ``True`` here if you only want the
|
||||
configured version and ignore the default version.
|
||||
|
||||
If the flag is ``False`` (which it is by default) then the
|
||||
config value will be used if present, and a default version is
|
||||
used only if the config does not have a value.
|
||||
:param default_only: Pass ``True`` here if you only want the
|
||||
default version and ignore the configured version.
|
||||
|
||||
:param prefix: If specified, will override the prefix used for
|
||||
config lookups.
|
||||
|
@ -93,7 +94,7 @@ def get_libver(
|
|||
be removed in the future.
|
||||
|
||||
:returns: The appropriate version string, e.g. ``'1.2.3'`` or
|
||||
``'latest'`` etc.
|
||||
``'latest'`` etc. Can also return ``None`` in some cases.
|
||||
"""
|
||||
config = request.wutta_config
|
||||
|
||||
|
@ -113,11 +114,14 @@ def get_libver(
|
|||
version = config.get(f'{prefix}.buefy_version')
|
||||
if version:
|
||||
return version
|
||||
return 'latest'
|
||||
if not configured_only:
|
||||
return 'latest'
|
||||
|
||||
elif key == 'buefy.css':
|
||||
# nb. this always returns something
|
||||
return get_libver(request, 'buefy', default_only=default_only)
|
||||
return get_libver(request, 'buefy',
|
||||
default_only=default_only,
|
||||
configured_only=configured_only)
|
||||
|
||||
elif key == 'vue':
|
||||
if not default_only:
|
||||
|
@ -125,36 +129,47 @@ def get_libver(
|
|||
version = config.get(f'{prefix}.vue_version')
|
||||
if version:
|
||||
return version
|
||||
return '2.6.14'
|
||||
if not configured_only:
|
||||
return '2.6.14'
|
||||
|
||||
elif key == 'vue_resource':
|
||||
return 'latest'
|
||||
if not configured_only:
|
||||
return 'latest'
|
||||
|
||||
elif key == 'fontawesome':
|
||||
return '5.3.1'
|
||||
if not configured_only:
|
||||
return '5.3.1'
|
||||
|
||||
elif key == 'bb_vue':
|
||||
return '3.4.31'
|
||||
if not configured_only:
|
||||
return '3.4.31'
|
||||
|
||||
elif key == 'bb_oruga':
|
||||
return '0.8.12'
|
||||
if not configured_only:
|
||||
return '0.8.12'
|
||||
|
||||
elif key in ('bb_oruga_bulma', 'bb_oruga_bulma_css'):
|
||||
return '0.3.0'
|
||||
if not configured_only:
|
||||
return '0.3.0'
|
||||
|
||||
elif key == 'bb_fontawesome_svg_core':
|
||||
return '6.5.2'
|
||||
if not configured_only:
|
||||
return '6.5.2'
|
||||
|
||||
elif key == 'bb_free_solid_svg_icons':
|
||||
return '6.5.2'
|
||||
if not configured_only:
|
||||
return '6.5.2'
|
||||
|
||||
elif key == 'bb_vue_fontawesome':
|
||||
return '3.0.6'
|
||||
if not configured_only:
|
||||
return '3.0.6'
|
||||
|
||||
|
||||
def get_liburl(
|
||||
request,
|
||||
key,
|
||||
configured_only=False,
|
||||
default_only=False,
|
||||
prefix='wuttaweb',
|
||||
):
|
||||
"""
|
||||
|
@ -204,6 +219,12 @@ def get_liburl(
|
|||
* ``bb_free_solid_svg_icons``
|
||||
* ``bb_vue_fontawesome``
|
||||
|
||||
:param configured_only: Pass ``True`` here if you only want the
|
||||
configured URL and ignore the default URL.
|
||||
|
||||
:param default_only: Pass ``True`` here if you only want the
|
||||
default URL and ignore the configured URL.
|
||||
|
||||
:param prefix: If specified, will override the prefix used for
|
||||
config lookups.
|
||||
|
||||
|
@ -212,48 +233,127 @@ def get_liburl(
|
|||
This ``prefix`` param is for backward compatibility and may
|
||||
be removed in the future.
|
||||
|
||||
:returns: The appropriate URL as string.
|
||||
:returns: The appropriate URL as string. Can also return ``None``
|
||||
in some cases.
|
||||
"""
|
||||
config = request.wutta_config
|
||||
|
||||
url = config.get(f'{prefix}.liburl.{key}')
|
||||
if url:
|
||||
return url
|
||||
if not default_only:
|
||||
url = config.get(f'{prefix}.liburl.{key}')
|
||||
if url:
|
||||
return url
|
||||
|
||||
if configured_only:
|
||||
return
|
||||
|
||||
version = get_libver(request, key, prefix=prefix)
|
||||
|
||||
static = config.get('wuttaweb.static_libcache.module')
|
||||
if static:
|
||||
static = importlib.import_module(static)
|
||||
needed = request.environ['fanstatic.needed']
|
||||
liburl = needed.library_url(static.libcache) + '/'
|
||||
# nb. add custom url prefix if needed, e.g. /wutta
|
||||
if request.script_name:
|
||||
liburl = request.script_name + liburl
|
||||
|
||||
if key == 'buefy':
|
||||
if static and hasattr(static, 'buefy_js'):
|
||||
return liburl + static.buefy_js.relpath
|
||||
return f'https://unpkg.com/buefy@{version}/dist/buefy.min.js'
|
||||
|
||||
elif key == 'buefy.css':
|
||||
if static and hasattr(static, 'buefy_css'):
|
||||
return liburl + static.buefy_css.relpath
|
||||
return f'https://unpkg.com/buefy@{version}/dist/buefy.min.css'
|
||||
|
||||
elif key == 'vue':
|
||||
if static and hasattr(static, 'vue_js'):
|
||||
return liburl + static.vue_js.relpath
|
||||
return f'https://unpkg.com/vue@{version}/dist/vue.min.js'
|
||||
|
||||
elif key == 'vue_resource':
|
||||
if static and hasattr(static, 'vue_resource_js'):
|
||||
return liburl + static.vue_resource_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/vue-resource@{version}'
|
||||
|
||||
elif key == 'fontawesome':
|
||||
if static and hasattr(static, 'fontawesome_js'):
|
||||
return liburl + static.fontawesome_js.relpath
|
||||
return f'https://use.fontawesome.com/releases/v{version}/js/all.js'
|
||||
|
||||
elif key == 'bb_vue':
|
||||
if static and hasattr(static, 'bb_vue_js'):
|
||||
return liburl + static.bb_vue_js.relpath
|
||||
return f'https://unpkg.com/vue@{version}/dist/vue.esm-browser.prod.js'
|
||||
|
||||
elif key == 'bb_oruga':
|
||||
if static and hasattr(static, 'bb_oruga_js'):
|
||||
return liburl + static.bb_oruga_js.relpath
|
||||
return f'https://unpkg.com/@oruga-ui/oruga-next@{version}/dist/oruga.mjs'
|
||||
|
||||
elif key == 'bb_oruga_bulma':
|
||||
if static and hasattr(static, 'bb_oruga_bulma_js'):
|
||||
return liburl + static.bb_oruga_bulma_js.relpath
|
||||
return f'https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.mjs'
|
||||
|
||||
elif key == 'bb_oruga_bulma_css':
|
||||
if static and hasattr(static, 'bb_oruga_bulma_css'):
|
||||
return liburl + static.bb_oruga_bulma_css.relpath
|
||||
return f'https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.css'
|
||||
|
||||
elif key == 'bb_fontawesome_svg_core':
|
||||
if static and hasattr(static, 'bb_fontawesome_svg_core_js'):
|
||||
return liburl + static.bb_fontawesome_svg_core_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@{version}/+esm'
|
||||
|
||||
elif key == 'bb_free_solid_svg_icons':
|
||||
if static and hasattr(static, 'bb_free_solid_svg_icons_js'):
|
||||
return liburl + static.bb_free_solid_svg_icons_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@{version}/+esm'
|
||||
|
||||
elif key == 'bb_vue_fontawesome':
|
||||
if static and hasattr(static, 'bb_vue_fontawesome_js'):
|
||||
return liburl + static.bb_vue_fontawesome_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@{version}/+esm'
|
||||
|
||||
|
||||
def get_csrf_token(request):
|
||||
"""
|
||||
Convenience function, returns the effective CSRF token (raw
|
||||
string) for the given request.
|
||||
|
||||
See also :func:`render_csrf_token()`.
|
||||
"""
|
||||
token = request.session.get_csrf_token()
|
||||
if token is None:
|
||||
token = request.session.new_csrf_token()
|
||||
return token
|
||||
|
||||
|
||||
def render_csrf_token(request, name='_csrf'):
|
||||
"""
|
||||
Convenience function, returns CSRF hidden input inside hidden div,
|
||||
e.g.:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<div style="display: none;">
|
||||
<input type="hidden" name="_csrf" value="TOKEN" />
|
||||
</div>
|
||||
|
||||
This function is part of :mod:`wuttaweb.helpers` (as
|
||||
:func:`~wuttaweb.helpers.csrf_token()`) which means you can do
|
||||
this in page templates:
|
||||
|
||||
.. code-block:: mako
|
||||
|
||||
${h.form(request.current_route_url())}
|
||||
${h.csrf_token(request)}
|
||||
<!-- other fields etc. -->
|
||||
${h.end_form()}
|
||||
|
||||
See also :func:`get_csrf_token()`.
|
||||
"""
|
||||
token = get_csrf_token(request)
|
||||
return HTML.tag('div', tags.hidden(name, value=token), style='display:none;')
|
||||
|
|
|
@ -27,6 +27,8 @@ Base Logic for Master Views
|
|||
from pyramid.renderers import render_to_response
|
||||
|
||||
from wuttaweb.views import View
|
||||
from wuttaweb.util import get_form_data
|
||||
from wuttaweb.db import Session
|
||||
|
||||
|
||||
class MasterView(View):
|
||||
|
@ -98,6 +100,14 @@ class MasterView(View):
|
|||
Code should not access this directly but instead call
|
||||
:meth:`get_model_title_plural()`.
|
||||
|
||||
.. attribute:: config_title
|
||||
|
||||
Optional override for the view's "config" title, e.g. ``"Wutta
|
||||
Widgets"`` (to be displayed as **Configure Wutta Widgets**).
|
||||
|
||||
Code should not access this directly but instead call
|
||||
:meth:`get_config_title()`.
|
||||
|
||||
.. attribute:: route_prefix
|
||||
|
||||
Optional override for the view's route prefix,
|
||||
|
@ -125,17 +135,29 @@ class MasterView(View):
|
|||
.. attribute:: listable
|
||||
|
||||
Boolean indicating whether the view model supports "listing" -
|
||||
i.e. it should have an :meth:`index()` view.
|
||||
i.e. it should have an :meth:`index()` view. Default value is
|
||||
``True``.
|
||||
|
||||
.. attribute:: configurable
|
||||
|
||||
Boolean indicating whether the master view supports
|
||||
"configuring" - i.e. it should have a :meth:`configure()` view.
|
||||
Default value is ``False``.
|
||||
"""
|
||||
|
||||
##############################
|
||||
# attributes
|
||||
##############################
|
||||
|
||||
# features
|
||||
listable = True
|
||||
configurable = False
|
||||
|
||||
# current action
|
||||
configuring = False
|
||||
|
||||
##############################
|
||||
# view methods
|
||||
# index methods
|
||||
##############################
|
||||
|
||||
def index(self):
|
||||
|
@ -145,8 +167,304 @@ class MasterView(View):
|
|||
This is the "default" view for the model and is what user sees
|
||||
when visiting the "root" path under the :attr:`url_prefix`,
|
||||
e.g. ``/widgets/``.
|
||||
|
||||
By default, this view is included only if :attr:`listable` is
|
||||
true.
|
||||
"""
|
||||
return self.render_to_response('index', {})
|
||||
context = {
|
||||
'index_url': None, # avoid title link since this *is* the index
|
||||
}
|
||||
return self.render_to_response('index', context)
|
||||
|
||||
##############################
|
||||
# configure methods
|
||||
##############################
|
||||
|
||||
def configure(self):
|
||||
"""
|
||||
View for configuring aspects of the app which are pertinent to
|
||||
this master view and/or model.
|
||||
|
||||
By default, this view is included only if :attr:`configurable`
|
||||
is true. It usually maps to a URL like ``/widgets/configure``.
|
||||
|
||||
The expected workflow is as follows:
|
||||
|
||||
* user navigates to Configure page
|
||||
* user modifies settings and clicks Save
|
||||
* this view then *deletes* all "known" settings
|
||||
* then it saves user-submitted settings
|
||||
|
||||
That is unless ``remove_settings`` is requested, in which case
|
||||
settings are deleted but then none are saved. The "known"
|
||||
settings by default include only the "simple" settings.
|
||||
|
||||
As a general rule, a particular setting should be configurable
|
||||
by (at most) one master view. Some settings may never be
|
||||
exposed at all. But when exposing a setting, careful thought
|
||||
should be given to where it logically/best belongs.
|
||||
|
||||
Some settings are "simple" and a master view subclass need
|
||||
only provide their basic definitions via
|
||||
:meth:`configure_get_simple_settings()`. If complex settings
|
||||
are needed, subclass must override one or more other methods
|
||||
to achieve the aim(s).
|
||||
|
||||
See also related methods, used by this one:
|
||||
|
||||
* :meth:`configure_get_simple_settings()`
|
||||
* :meth:`configure_get_context()`
|
||||
* :meth:`configure_gather_settings()`
|
||||
* :meth:`configure_remove_settings()`
|
||||
* :meth:`configure_save_settings()`
|
||||
"""
|
||||
self.configuring = True
|
||||
config_title = self.get_config_title()
|
||||
|
||||
# was form submitted?
|
||||
if self.request.method == 'POST':
|
||||
|
||||
# maybe just remove settings
|
||||
if self.request.POST.get('remove_settings'):
|
||||
self.configure_remove_settings()
|
||||
self.request.session.flash(f"All settings for {config_title} have been removed.",
|
||||
'warning')
|
||||
|
||||
# reload configure page
|
||||
return self.redirect(self.request.current_route_url())
|
||||
|
||||
# gather/save settings
|
||||
data = get_form_data(self.request)
|
||||
settings = self.configure_gather_settings(data)
|
||||
self.configure_remove_settings()
|
||||
self.configure_save_settings(settings)
|
||||
self.request.session.flash("Settings have been saved.")
|
||||
|
||||
# reload configure page
|
||||
return self.redirect(self.request.current_route_url())
|
||||
|
||||
# render configure page
|
||||
context = self.configure_get_context()
|
||||
return self.render_to_response('configure', context)
|
||||
|
||||
def configure_get_context(
|
||||
self,
|
||||
simple_settings=None,
|
||||
):
|
||||
"""
|
||||
Returns the full context dict, for rendering the
|
||||
:meth:`configure()` page template.
|
||||
|
||||
Default context will include ``simple_settings`` (normalized
|
||||
to just name/value).
|
||||
|
||||
You may need to override this method, to add additional
|
||||
"complex" settings etc.
|
||||
|
||||
:param simple_settings: Optional list of simple settings, if
|
||||
already initialized. Otherwise it is retrieved via
|
||||
:meth:`configure_get_simple_settings()`.
|
||||
|
||||
:returns: Context dict for the page template.
|
||||
"""
|
||||
context = {}
|
||||
|
||||
# simple settings
|
||||
if simple_settings is None:
|
||||
simple_settings = self.configure_get_simple_settings()
|
||||
if simple_settings:
|
||||
|
||||
# we got some, so "normalize" each definition to name/value
|
||||
normalized = {}
|
||||
for simple in simple_settings:
|
||||
|
||||
# name
|
||||
name = simple['name']
|
||||
|
||||
# value
|
||||
if 'value' in simple:
|
||||
value = simple['value']
|
||||
elif simple.get('type') is bool:
|
||||
value = self.config.get_bool(name, default=simple.get('default', False))
|
||||
else:
|
||||
value = self.config.get(name)
|
||||
|
||||
normalized[name] = value
|
||||
|
||||
# add to template context
|
||||
context['simple_settings'] = normalized
|
||||
|
||||
return context
|
||||
|
||||
def configure_get_simple_settings(self):
|
||||
"""
|
||||
This should return a list of "simple" setting definitions for
|
||||
the :meth:`configure()` view, which can be handled in a more
|
||||
automatic way. (This is as opposed to some settings which are
|
||||
more complex and must be handled manually; those should not be
|
||||
part of this method's return value.)
|
||||
|
||||
Basically a "simple" setting is one which can be represented
|
||||
by a single field/widget on the Configure page.
|
||||
|
||||
The setting definitions returned must each be a dict of
|
||||
"attributes" for the setting. For instance a *very* simple
|
||||
setting might be::
|
||||
|
||||
{'name': 'wutta.app_title'}
|
||||
|
||||
The ``name`` is required, everything else is optional. Here
|
||||
is a more complete example::
|
||||
|
||||
{
|
||||
'name': 'wutta.production',
|
||||
'type': bool,
|
||||
'default': False,
|
||||
'save_if_empty': False,
|
||||
}
|
||||
|
||||
Note that if specified, the ``default`` should be of the same
|
||||
data type as defined for the setting (``bool`` in the above
|
||||
example). The default ``type`` is ``str``.
|
||||
|
||||
Normally if a setting's value is effectively null, the setting
|
||||
is removed instead of keeping it in the DB. This behavior can
|
||||
be changed per-setting via the ``save_if_empty`` flag.
|
||||
|
||||
:returns: List of setting definition dicts as described above.
|
||||
Note that their order does not matter since the template
|
||||
must explicitly define field layout etc.
|
||||
"""
|
||||
|
||||
def configure_gather_settings(
|
||||
self,
|
||||
data,
|
||||
simple_settings=None,
|
||||
):
|
||||
"""
|
||||
Collect the full set of "normalized" settings from user
|
||||
request, so that :meth:`configure()` can save them.
|
||||
|
||||
Settings are gathered from the given request (e.g. POST)
|
||||
``data``, but also taking into account what we know based on
|
||||
the simple setting definitions.
|
||||
|
||||
Subclass may need to override this method if complex settings
|
||||
are required.
|
||||
|
||||
:param data: Form data submitted via POST request.
|
||||
|
||||
:param simple_settings: Optional list of simple settings, if
|
||||
already initialized. Otherwise it is retrieved via
|
||||
:meth:`configure_get_simple_settings()`.
|
||||
|
||||
This method must return a list of normalized settings, similar
|
||||
in spirit to the definition syntax used in
|
||||
:meth:`configure_get_simple_settings()`. However the format
|
||||
returned here is minimal and contains just name/value::
|
||||
|
||||
{
|
||||
'name': 'wutta.app_title',
|
||||
'value': 'Wutta Wutta',
|
||||
}
|
||||
|
||||
Note that the ``value`` will always be a string.
|
||||
|
||||
Also note, whereas it's possible ``data`` will not contain all
|
||||
known settings, the return value *should* (potentially)
|
||||
contain all of them.
|
||||
|
||||
The one exception is when a simple setting has null value, by
|
||||
default it will not be included in the result (hence, not
|
||||
saved to DB) unless the setting definition has the
|
||||
``save_if_empty`` flag set.
|
||||
"""
|
||||
settings = []
|
||||
|
||||
# simple settings
|
||||
if simple_settings is None:
|
||||
simple_settings = self.configure_get_simple_settings()
|
||||
if simple_settings:
|
||||
|
||||
# we got some, so "normalize" each definition to name/value
|
||||
for simple in simple_settings:
|
||||
name = simple['name']
|
||||
|
||||
if name in data:
|
||||
value = data[name]
|
||||
else:
|
||||
value = simple.get('default')
|
||||
|
||||
if simple.get('type') is bool:
|
||||
value = str(bool(value)).lower()
|
||||
elif simple.get('type') is int:
|
||||
value = str(int(value or '0'))
|
||||
elif value is None:
|
||||
value = ''
|
||||
else:
|
||||
value = str(value)
|
||||
|
||||
# only want to save this setting if we received a
|
||||
# value, or if empty values are okay to save
|
||||
if value or simple.get('save_if_empty'):
|
||||
settings.append({'name': name,
|
||||
'value': value})
|
||||
|
||||
return settings
|
||||
|
||||
def configure_remove_settings(
|
||||
self,
|
||||
simple_settings=None,
|
||||
):
|
||||
"""
|
||||
Remove all "known" settings from the DB; this is called by
|
||||
:meth:`configure()`.
|
||||
|
||||
The point of this method is to ensure *all* "known" settings
|
||||
which are managed by this master view, are purged from the DB.
|
||||
|
||||
The default logic can handle this automatically for simple
|
||||
settings; subclass must override for any complex settings.
|
||||
|
||||
:param simple_settings: Optional list of simple settings, if
|
||||
already initialized. Otherwise it is retrieved via
|
||||
:meth:`configure_get_simple_settings()`.
|
||||
"""
|
||||
names = []
|
||||
|
||||
# simple settings
|
||||
if simple_settings is None:
|
||||
simple_settings = self.configure_get_simple_settings()
|
||||
if simple_settings:
|
||||
names.extend([simple['name']
|
||||
for simple in simple_settings])
|
||||
|
||||
if names:
|
||||
# nb. must avoid self.Session here in case that does not
|
||||
# point to our primary app DB
|
||||
session = Session()
|
||||
for name in names:
|
||||
self.app.delete_setting(session, name)
|
||||
|
||||
def configure_save_settings(self, settings):
|
||||
"""
|
||||
Save the given settings to the DB; this is called by
|
||||
:meth:`configure()`.
|
||||
|
||||
This method expected a list of name/value dicts and will
|
||||
simply save each to the DB, with no "conversion" logic.
|
||||
|
||||
:param settings: List of normalized setting definitions, as
|
||||
returned by :meth:`configure_gather_settings()`.
|
||||
"""
|
||||
# app = self.get_rattail_app()
|
||||
|
||||
# nb. must avoid self.Session here in case that does not point
|
||||
# to our primary app DB
|
||||
session = Session()
|
||||
for setting in settings:
|
||||
self.app.save_setting(session, setting['name'], setting['value'],
|
||||
force_create=True)
|
||||
|
||||
##############################
|
||||
# support methods
|
||||
|
@ -162,6 +480,16 @@ class MasterView(View):
|
|||
"""
|
||||
return self.get_model_title_plural()
|
||||
|
||||
def get_index_url(self, **kwargs):
|
||||
"""
|
||||
Returns the URL for master's :meth:`index()` view.
|
||||
|
||||
NB. this returns ``None`` if :attr:`listable` is false.
|
||||
"""
|
||||
if self.listable:
|
||||
route_prefix = self.get_route_prefix()
|
||||
return self.request.route_url(route_prefix, **kwargs)
|
||||
|
||||
def render_to_response(self, template, context):
|
||||
"""
|
||||
Locate and render an appropriate template, with the given
|
||||
|
@ -192,7 +520,11 @@ class MasterView(View):
|
|||
:returns: Response object containing the rendered template.
|
||||
"""
|
||||
defaults = {
|
||||
'master': self,
|
||||
'route_prefix': self.get_route_prefix(),
|
||||
'index_title': self.get_index_title(),
|
||||
'index_url': self.get_index_url(),
|
||||
'config_title': self.get_config_title(),
|
||||
}
|
||||
|
||||
# merge defaults + caller-provided context
|
||||
|
@ -406,6 +738,26 @@ class MasterView(View):
|
|||
|
||||
return cls.get_url_prefix()
|
||||
|
||||
@classmethod
|
||||
def get_config_title(cls):
|
||||
"""
|
||||
Returns the "config title" for the view/model.
|
||||
|
||||
The config title is used for page title in the
|
||||
:meth:`configure()` view, as well as links to it. It is
|
||||
usually plural, e.g. ``"Wutta Widgets"`` in which case that
|
||||
winds up being displayed in the web app as: **Configure Wutta
|
||||
Widgets**
|
||||
|
||||
The default logic will call :meth:`get_model_title_plural()`
|
||||
and return that as-is. A subclass may override by assigning
|
||||
:attr:`config_title`.
|
||||
"""
|
||||
if hasattr(cls, 'config_title'):
|
||||
return cls.config_title
|
||||
|
||||
return cls.get_model_title_plural()
|
||||
|
||||
##############################
|
||||
# configuration
|
||||
##############################
|
||||
|
@ -436,8 +788,15 @@ class MasterView(View):
|
|||
route_prefix = cls.get_route_prefix()
|
||||
url_prefix = cls.get_url_prefix()
|
||||
|
||||
# index view
|
||||
# index
|
||||
if cls.listable:
|
||||
config.add_route(route_prefix, f'{url_prefix}/')
|
||||
config.add_view(cls, attr='index',
|
||||
route_name=route_prefix)
|
||||
|
||||
# configure
|
||||
if cls.configurable:
|
||||
config.add_route(f'{route_prefix}.configure',
|
||||
f'{url_prefix}/configure')
|
||||
config.add_view(cls, attr='configure',
|
||||
route_name=f'{route_prefix}.configure')
|
||||
|
|
|
@ -24,16 +24,107 @@
|
|||
Views for app settings
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.util import get_libver, get_liburl
|
||||
|
||||
|
||||
class AppInfoView(MasterView):
|
||||
"""
|
||||
Master view for the overall app, to show/edit config etc.
|
||||
Master view for the core app info, to show/edit config etc.
|
||||
|
||||
Notable URLs provided by this class:
|
||||
|
||||
* ``/appinfo/``
|
||||
* ``/appinfo/configure``
|
||||
"""
|
||||
model_name = 'AppInfo'
|
||||
model_title_plural = "App Info"
|
||||
route_prefix = 'appinfo'
|
||||
configurable = True
|
||||
|
||||
def configure_get_simple_settings(self):
|
||||
""" """
|
||||
return [
|
||||
|
||||
# basics
|
||||
{'name': f'{self.app.appname}.app_title'},
|
||||
{'name': f'{self.app.appname}.production',
|
||||
'type': bool},
|
||||
|
||||
# web libs
|
||||
{'name': 'wuttaweb.libver.vue'},
|
||||
{'name': 'wuttaweb.liburl.vue'},
|
||||
{'name': 'wuttaweb.libver.vue_resource'},
|
||||
{'name': 'wuttaweb.liburl.vue_resource'},
|
||||
{'name': 'wuttaweb.libver.buefy'},
|
||||
{'name': 'wuttaweb.liburl.buefy'},
|
||||
{'name': 'wuttaweb.libver.buefy.css'},
|
||||
{'name': 'wuttaweb.liburl.buefy.css'},
|
||||
{'name': 'wuttaweb.libver.fontawesome'},
|
||||
{'name': 'wuttaweb.liburl.fontawesome'},
|
||||
{'name': 'wuttaweb.libver.bb_vue'},
|
||||
{'name': 'wuttaweb.liburl.bb_vue'},
|
||||
{'name': 'wuttaweb.libver.bb_oruga'},
|
||||
{'name': 'wuttaweb.liburl.bb_oruga'},
|
||||
{'name': 'wuttaweb.libver.bb_oruga_bulma'},
|
||||
{'name': 'wuttaweb.liburl.bb_oruga_bulma'},
|
||||
{'name': 'wuttaweb.libver.bb_oruga_bulma_css'},
|
||||
{'name': 'wuttaweb.liburl.bb_oruga_bulma_css'},
|
||||
{'name': 'wuttaweb.libver.bb_fontawesome_svg_core'},
|
||||
{'name': 'wuttaweb.liburl.bb_fontawesome_svg_core'},
|
||||
{'name': 'wuttaweb.libver.bb_free_solid_svg_icons'},
|
||||
{'name': 'wuttaweb.liburl.bb_free_solid_svg_icons'},
|
||||
{'name': 'wuttaweb.libver.bb_vue_fontawesome'},
|
||||
{'name': 'wuttaweb.liburl.bb_vue_fontawesome'},
|
||||
|
||||
]
|
||||
|
||||
def configure_get_context(self, **kwargs):
|
||||
""" """
|
||||
|
||||
# normal context
|
||||
context = super().configure_get_context(**kwargs)
|
||||
|
||||
# we will add `weblibs` to context, based on config values
|
||||
weblibs = OrderedDict([
|
||||
('vue', "Vue"),
|
||||
('vue_resource', "vue-resource"),
|
||||
('buefy', "Buefy"),
|
||||
('buefy.css', "Buefy CSS"),
|
||||
('fontawesome', "FontAwesome"),
|
||||
('bb_vue', "(BB) vue"),
|
||||
('bb_oruga', "(BB) @oruga-ui/oruga-next"),
|
||||
('bb_oruga_bulma', "(BB) @oruga-ui/theme-bulma (JS)"),
|
||||
('bb_oruga_bulma_css', "(BB) @oruga-ui/theme-bulma (CSS)"),
|
||||
('bb_fontawesome_svg_core', "(BB) @fortawesome/fontawesome-svg-core"),
|
||||
('bb_free_solid_svg_icons', "(BB) @fortawesome/free-solid-svg-icons"),
|
||||
('bb_vue_fontawesome', "(BB) @fortawesome/vue-fontawesome"),
|
||||
])
|
||||
|
||||
# import ipdb; ipdb.set_trace()
|
||||
|
||||
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,
|
||||
configured_only=True),
|
||||
'configured_url': get_liburl(self.request, key,
|
||||
configured_only=True),
|
||||
|
||||
# nb. these are for display 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 defaults(config, **kwargs):
|
||||
|
|
0
tests/libcache/bb_fontawesome_svg_core.js
Normal file
0
tests/libcache/bb_fontawesome_svg_core.js
Normal file
0
tests/libcache/bb_free_solid_svg_icons.js
Normal file
0
tests/libcache/bb_free_solid_svg_icons.js
Normal file
0
tests/libcache/bb_oruga.js
Normal file
0
tests/libcache/bb_oruga.js
Normal file
0
tests/libcache/bb_oruga_bulma.css
Normal file
0
tests/libcache/bb_oruga_bulma.css
Normal file
0
tests/libcache/bb_oruga_bulma.js
Normal file
0
tests/libcache/bb_oruga_bulma.js
Normal file
0
tests/libcache/bb_vue.js
Normal file
0
tests/libcache/bb_vue.js
Normal file
0
tests/libcache/bb_vue_fontawesome.js
Normal file
0
tests/libcache/bb_vue_fontawesome.js
Normal file
0
tests/libcache/buefy.css
Normal file
0
tests/libcache/buefy.css
Normal file
0
tests/libcache/buefy.js
Normal file
0
tests/libcache/buefy.js
Normal file
0
tests/libcache/fontawesome.js
Normal file
0
tests/libcache/fontawesome.js
Normal file
0
tests/libcache/vue.js
Normal file
0
tests/libcache/vue.js
Normal file
0
tests/libcache/vue_resource.js
Normal file
0
tests/libcache/vue_resource.js
Normal file
|
@ -8,6 +8,17 @@ from pyramid.config import Configurator
|
|||
from pyramid.router import Router
|
||||
|
||||
from wuttaweb import app as mod
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
|
||||
|
||||
class TestWebAppProvider(TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
# nb. just normal usage here, confirm it does the one thing we
|
||||
# need it to..
|
||||
config = WuttaConfig()
|
||||
app = config.get_app()
|
||||
handler = app.get_web_handler()
|
||||
|
||||
|
||||
class TestMakeWuttaConfig(FileConfigTestCase):
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from fanstatic import Library, Resource
|
||||
from pyramid import testing
|
||||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
|
@ -29,6 +31,10 @@ class TestGetLibVer(TestCase):
|
|||
version = util.get_libver(self.request, 'buefy')
|
||||
self.assertEqual(version, '0.9.29')
|
||||
|
||||
def test_buefy_configured_only(self):
|
||||
version = util.get_libver(self.request, 'buefy', configured_only=True)
|
||||
self.assertIsNone(version)
|
||||
|
||||
def test_buefy_default_only(self):
|
||||
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
|
||||
version = util.get_libver(self.request, 'buefy', default_only=True)
|
||||
|
@ -50,6 +56,10 @@ class TestGetLibVer(TestCase):
|
|||
version = util.get_libver(self.request, 'buefy.css')
|
||||
self.assertEqual(version, '0.9.29')
|
||||
|
||||
def test_buefy_css_configured_only(self):
|
||||
version = util.get_libver(self.request, 'buefy.css', configured_only=True)
|
||||
self.assertIsNone(version)
|
||||
|
||||
def test_buefy_css_default_only(self):
|
||||
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
|
||||
version = util.get_libver(self.request, 'buefy.css', default_only=True)
|
||||
|
@ -69,6 +79,10 @@ class TestGetLibVer(TestCase):
|
|||
version = util.get_libver(self.request, 'vue')
|
||||
self.assertEqual(version, '3.4.31')
|
||||
|
||||
def test_vue_configured_only(self):
|
||||
version = util.get_libver(self.request, 'vue', configured_only=True)
|
||||
self.assertIsNone(version)
|
||||
|
||||
def test_vue_default_only(self):
|
||||
self.config.setdefault('wuttaweb.libver.vue', '3.4.31')
|
||||
version = util.get_libver(self.request, 'vue', default_only=True)
|
||||
|
@ -149,12 +163,40 @@ class TestGetLibVer(TestCase):
|
|||
self.assertEqual(version, '3.0.8')
|
||||
|
||||
|
||||
libcache = Library('testing', 'libcache')
|
||||
vue_js = Resource(libcache, 'vue.js')
|
||||
vue_resource_js = Resource(libcache, 'vue_resource.js')
|
||||
buefy_js = Resource(libcache, 'buefy.js')
|
||||
buefy_css = Resource(libcache, 'buefy.css')
|
||||
fontawesome_js = Resource(libcache, 'fontawesome.js')
|
||||
bb_vue_js = Resource(libcache, 'bb_vue.js')
|
||||
bb_oruga_js = Resource(libcache, 'bb_oruga.js')
|
||||
bb_oruga_bulma_js = Resource(libcache, 'bb_oruga_bulma.js')
|
||||
bb_oruga_bulma_css = Resource(libcache, 'bb_oruga_bulma.css')
|
||||
bb_fontawesome_svg_core_js = Resource(libcache, 'bb_fontawesome_svg_core.js')
|
||||
bb_free_solid_svg_icons_js = Resource(libcache, 'bb_free_solid_svg_icons.js')
|
||||
bb_vue_fontawesome_js = Resource(libcache, 'bb_vue_fontawesome.js')
|
||||
|
||||
|
||||
class TestGetLibUrl(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.request = testing.DummyRequest()
|
||||
self.request.wutta_config = self.config
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
self.pyramid_config = testing.setUp(request=self.request)
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
||||
def setup_fanstatic(self):
|
||||
self.pyramid_config.include('pyramid_fanstatic')
|
||||
self.config.setdefault('wuttaweb.static_libcache.module',
|
||||
'tests.test_util')
|
||||
|
||||
needed = MagicMock()
|
||||
needed.library_url = MagicMock(return_value='/fanstatic')
|
||||
self.request.environ['fanstatic.needed'] = needed
|
||||
self.request.script_name = '/wutta'
|
||||
|
||||
def test_buefy_default(self):
|
||||
url = util.get_liburl(self.request, 'buefy')
|
||||
|
@ -165,6 +207,20 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'buefy')
|
||||
self.assertEqual(url, '/lib/buefy.js')
|
||||
|
||||
def test_buefy_default_only(self):
|
||||
self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js')
|
||||
url = util.get_liburl(self.request, 'buefy', default_only=True)
|
||||
self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.js')
|
||||
|
||||
def test_buefy_configured_only(self):
|
||||
url = util.get_liburl(self.request, 'buefy', configured_only=True)
|
||||
self.assertIsNone(url)
|
||||
|
||||
def test_buefy_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'buefy')
|
||||
self.assertEqual(url, '/wutta/fanstatic/buefy.js')
|
||||
|
||||
def test_buefy_css_default(self):
|
||||
url = util.get_liburl(self.request, 'buefy.css')
|
||||
self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.css')
|
||||
|
@ -174,6 +230,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'buefy.css')
|
||||
self.assertEqual(url, '/lib/buefy.css')
|
||||
|
||||
def test_buefy_css_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'buefy.css')
|
||||
self.assertEqual(url, '/wutta/fanstatic/buefy.css')
|
||||
|
||||
def test_vue_default(self):
|
||||
url = util.get_liburl(self.request, 'vue')
|
||||
self.assertEqual(url, 'https://unpkg.com/vue@2.6.14/dist/vue.min.js')
|
||||
|
@ -183,6 +244,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'vue')
|
||||
self.assertEqual(url, '/lib/vue.js')
|
||||
|
||||
def test_vue_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'vue')
|
||||
self.assertEqual(url, '/wutta/fanstatic/vue.js')
|
||||
|
||||
def test_vue_resource_default(self):
|
||||
url = util.get_liburl(self.request, 'vue_resource')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/vue-resource@latest')
|
||||
|
@ -192,6 +258,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'vue_resource')
|
||||
self.assertEqual(url, '/lib/vue-resource.js')
|
||||
|
||||
def test_vue_resource_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'vue_resource')
|
||||
self.assertEqual(url, '/wutta/fanstatic/vue_resource.js')
|
||||
|
||||
def test_fontawesome_default(self):
|
||||
url = util.get_liburl(self.request, 'fontawesome')
|
||||
self.assertEqual(url, 'https://use.fontawesome.com/releases/v5.3.1/js/all.js')
|
||||
|
@ -201,6 +272,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'fontawesome')
|
||||
self.assertEqual(url, '/lib/fontawesome.js')
|
||||
|
||||
def test_fontawesome_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'fontawesome')
|
||||
self.assertEqual(url, '/wutta/fanstatic/fontawesome.js')
|
||||
|
||||
def test_bb_vue_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_vue')
|
||||
self.assertEqual(url, 'https://unpkg.com/vue@3.4.31/dist/vue.esm-browser.prod.js')
|
||||
|
@ -210,6 +286,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_vue')
|
||||
self.assertEqual(url, '/lib/vue.js')
|
||||
|
||||
def test_bb_vue_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_vue')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_vue.js')
|
||||
|
||||
def test_bb_oruga_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_oruga')
|
||||
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/oruga-next@0.8.12/dist/oruga.mjs')
|
||||
|
@ -219,6 +300,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_oruga')
|
||||
self.assertEqual(url, '/lib/oruga.js')
|
||||
|
||||
def test_bb_oruga_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_oruga')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_oruga.js')
|
||||
|
||||
def test_bb_oruga_bulma_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.3.0/dist/bulma.mjs')
|
||||
|
@ -228,6 +314,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(url, '/lib/oruga_bulma.js')
|
||||
|
||||
def test_bb_oruga_bulma_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.js')
|
||||
|
||||
def test_bb_oruga_bulma_css_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.3.0/dist/bulma.css')
|
||||
|
@ -237,6 +328,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(url, '/lib/oruga-bulma.css')
|
||||
|
||||
def test_bb_oruga_bulma_css_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.css')
|
||||
|
||||
def test_bb_fontawesome_svg_core_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@6.5.2/+esm')
|
||||
|
@ -246,6 +342,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(url, '/lib/fontawesome-svg-core.js')
|
||||
|
||||
def test_bb_fontawesome_svg_core_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_fontawesome_svg_core.js')
|
||||
|
||||
def test_bb_free_solid_svg_icons_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@6.5.2/+esm')
|
||||
|
@ -255,6 +356,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(url, '/lib/free-solid-svg-icons.js')
|
||||
|
||||
def test_bb_free_solid_svg_icons_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_free_solid_svg_icons.js')
|
||||
|
||||
def test_bb_vue_fontawesome_default(self):
|
||||
url = util.get_liburl(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@3.0.6/+esm')
|
||||
|
@ -264,6 +370,11 @@ class TestGetLibUrl(TestCase):
|
|||
url = util.get_liburl(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(url, '/lib/vue-fontawesome.js')
|
||||
|
||||
def test_bb_vue_fontawesome_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = util.get_liburl(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_vue_fontawesome.js')
|
||||
|
||||
|
||||
class TestGetFormData(TestCase):
|
||||
|
||||
|
@ -290,3 +401,45 @@ class TestGetFormData(TestCase):
|
|||
request = self.make_request(POST=None, content_type='application/json')
|
||||
data = util.get_form_data(request)
|
||||
self.assertEqual(data, {'foo2': 'baz'})
|
||||
|
||||
|
||||
class TestGetCsrfToken(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
|
||||
def test_same_token(self):
|
||||
|
||||
# same token returned for same request
|
||||
# TODO: dummy request is always returning same token!
|
||||
# so this isn't really testing anything.. :(
|
||||
first = util.get_csrf_token(self.request)
|
||||
self.assertIsNotNone(first)
|
||||
second = util.get_csrf_token(self.request)
|
||||
self.assertEqual(first, second)
|
||||
|
||||
# TODO: ideally would make a new request here and confirm it
|
||||
# gets a different token, but see note above..
|
||||
|
||||
def test_new_token(self):
|
||||
|
||||
# nb. dummy request always returns same token, so must
|
||||
# trick it into thinking it doesn't have one yet
|
||||
with patch.object(self.request.session, 'get_csrf_token', return_value=None):
|
||||
token = util.get_csrf_token(self.request)
|
||||
self.assertIsNotNone(token)
|
||||
|
||||
|
||||
class TestRenderCsrfToken(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
|
||||
def test_basics(self):
|
||||
html = util.render_csrf_token(self.request)
|
||||
self.assertIn('type="hidden"', html)
|
||||
self.assertIn('name="_csrf"', html)
|
||||
token = util.get_csrf_token(self.request)
|
||||
self.assertIn(f'value="{token}"', html)
|
||||
|
|
|
@ -1,39 +1,21 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
import functools
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from pyramid import testing
|
||||
from pyramid.response import Response
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.views import master
|
||||
from wuttaweb.subscribers import new_request_set_user
|
||||
|
||||
from tests.views.utils import WebTestCase
|
||||
|
||||
class TestMasterView(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
|
||||
})
|
||||
self.app = self.config.get_app()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
})
|
||||
self.pyramid_config.include('pyramid_mako')
|
||||
self.pyramid_config.include('wuttaweb.static')
|
||||
self.pyramid_config.include('wuttaweb.views.essential')
|
||||
self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
|
||||
'pyramid.events.BeforeRender')
|
||||
|
||||
event = MagicMock(request=self.request)
|
||||
new_request_set_user(event)
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
class TestMasterView(WebTestCase):
|
||||
|
||||
def test_defaults(self):
|
||||
master.MasterView.model_name = 'Widget'
|
||||
|
@ -233,6 +215,37 @@ class TestMasterView(TestCase):
|
|||
self.assertEqual(master.MasterView.get_template_prefix(), '/machines')
|
||||
del master.MasterView.model_class
|
||||
|
||||
def test_get_config_title(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
self.assertRaises(AttributeError, master.MasterView.get_config_title)
|
||||
|
||||
# subclass may specify config title
|
||||
master.MasterView.config_title = 'Widgets'
|
||||
self.assertEqual(master.MasterView.get_config_title(), "Widgets")
|
||||
del master.MasterView.config_title
|
||||
|
||||
# subclass may specify *plural* model title
|
||||
master.MasterView.model_title_plural = 'People'
|
||||
self.assertEqual(master.MasterView.get_config_title(), "People")
|
||||
del master.MasterView.model_title_plural
|
||||
|
||||
# or it may specify *singular* model title
|
||||
master.MasterView.model_title = 'Wutta Widget'
|
||||
self.assertEqual(master.MasterView.get_config_title(), "Wutta Widgets")
|
||||
del master.MasterView.model_title
|
||||
|
||||
# or it may specify model name
|
||||
master.MasterView.model_name = 'Blaster'
|
||||
self.assertEqual(master.MasterView.get_config_title(), "Blasters")
|
||||
del master.MasterView.model_name
|
||||
|
||||
# or it may specify model class
|
||||
MyModel = MagicMock(__name__='Dinosaur')
|
||||
master.MasterView.model_class = MyModel
|
||||
self.assertEqual(master.MasterView.get_config_title(), "Dinosaurs")
|
||||
del master.MasterView.model_class
|
||||
|
||||
##############################
|
||||
# support methods
|
||||
##############################
|
||||
|
@ -245,6 +258,10 @@ class TestMasterView(TestCase):
|
|||
|
||||
def test_render_to_response(self):
|
||||
|
||||
def widgets(request): return {}
|
||||
self.pyramid_config.add_route('widgets', '/widgets/')
|
||||
self.pyramid_config.add_view(widgets, route_name='widgets')
|
||||
|
||||
# basic sanity check using /master/index.mako
|
||||
# (nb. it skips /widgets/index.mako since that doesn't exist)
|
||||
master.MasterView.model_name = 'Widget'
|
||||
|
@ -255,12 +272,14 @@ class TestMasterView(TestCase):
|
|||
|
||||
# basic sanity check using /appinfo/index.mako
|
||||
master.MasterView.model_name = 'AppInfo'
|
||||
master.MasterView.template_prefix = '/appinfo'
|
||||
master.MasterView.route_prefix = 'appinfo'
|
||||
master.MasterView.url_prefix = '/appinfo'
|
||||
view = master.MasterView(self.request)
|
||||
response = view.render_to_response('index', {})
|
||||
self.assertIsInstance(response, Response)
|
||||
del master.MasterView.model_name
|
||||
del master.MasterView.template_prefix
|
||||
del master.MasterView.route_prefix
|
||||
del master.MasterView.url_prefix
|
||||
|
||||
# bad template name causes error
|
||||
master.MasterView.model_name = 'Widget'
|
||||
|
@ -275,8 +294,77 @@ class TestMasterView(TestCase):
|
|||
|
||||
# basic sanity check using /appinfo
|
||||
master.MasterView.model_name = 'AppInfo'
|
||||
master.MasterView.route_prefix = 'appinfo'
|
||||
master.MasterView.template_prefix = '/appinfo'
|
||||
view = master.MasterView(self.request)
|
||||
response = view.index()
|
||||
del master.MasterView.model_name
|
||||
del master.MasterView.route_prefix
|
||||
del master.MasterView.template_prefix
|
||||
|
||||
def test_configure(self):
|
||||
model = self.app.model
|
||||
|
||||
# setup
|
||||
master.MasterView.model_name = 'AppInfo'
|
||||
master.MasterView.route_prefix = 'appinfo'
|
||||
master.MasterView.template_prefix = '/appinfo'
|
||||
|
||||
# mock settings
|
||||
settings = [
|
||||
{'name': 'wutta.app_title'},
|
||||
{'name': 'wutta.foo', 'value': 'bar'},
|
||||
{'name': 'wutta.flag', 'type': bool},
|
||||
{'name': 'wutta.number', 'type': int, 'default': 42},
|
||||
{'name': 'wutta.value1', 'save_if_empty': True},
|
||||
{'name': 'wutta.value2', 'save_if_empty': False},
|
||||
]
|
||||
|
||||
view = master.MasterView(self.request)
|
||||
with patch.object(self.request, 'current_route_url',
|
||||
return_value='/appinfo/configure'):
|
||||
with patch.object(master.MasterView, 'configure_get_simple_settings',
|
||||
return_value=settings):
|
||||
with patch.object(master, 'Session', return_value=self.session):
|
||||
|
||||
# get the form page
|
||||
response = view.configure()
|
||||
self.assertIsInstance(response, Response)
|
||||
|
||||
# post request to save settings
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {
|
||||
'wutta.app_title': 'Wutta',
|
||||
'wutta.foo': 'bar',
|
||||
'wutta.flag': 'true',
|
||||
}
|
||||
response = view.configure()
|
||||
# nb. should get redirect back to configure page
|
||||
self.assertIsInstance(response, HTTPFound)
|
||||
|
||||
# should now have 5 settings
|
||||
count = self.session.query(model.Setting).count()
|
||||
self.assertEqual(count, 5)
|
||||
get_setting = functools.partial(self.app.get_setting, self.session)
|
||||
self.assertEqual(get_setting('wutta.app_title'), 'Wutta')
|
||||
self.assertEqual(get_setting('wutta.foo'), 'bar')
|
||||
self.assertEqual(get_setting('wutta.flag'), 'true')
|
||||
self.assertEqual(get_setting('wutta.number'), '42')
|
||||
self.assertEqual(get_setting('wutta.value1'), '')
|
||||
self.assertEqual(get_setting('wutta.value2'), None)
|
||||
|
||||
# post request to remove settings
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'remove_settings': '1'}
|
||||
response = view.configure()
|
||||
# nb. should get redirect back to configure page
|
||||
self.assertIsInstance(response, HTTPFound)
|
||||
|
||||
# should now have 0 settings
|
||||
count = self.session.query(model.Setting).count()
|
||||
self.assertEqual(count, 0)
|
||||
|
||||
# teardown
|
||||
del master.MasterView.model_name
|
||||
del master.MasterView.route_prefix
|
||||
del master.MasterView.template_prefix
|
||||
|
|
|
@ -8,6 +8,16 @@ from wuttaweb.views import settings
|
|||
class TestAppInfoView(WebTestCase):
|
||||
|
||||
def test_index(self):
|
||||
# just a sanity check
|
||||
# sanity/coverage check
|
||||
view = settings.AppInfoView(self.request)
|
||||
response = view.index()
|
||||
|
||||
def test_configure_get_simple_settings(self):
|
||||
# sanity/coverage check
|
||||
view = settings.AppInfoView(self.request)
|
||||
simple = view.configure_get_simple_settings()
|
||||
|
||||
def test_configure_get_context(self):
|
||||
# sanity/coverage check
|
||||
view = settings.AppInfoView(self.request)
|
||||
context = view.configure_get_context()
|
||||
|
|
Loading…
Reference in a new issue