Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/views/settings.py: 100%
90 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-13 13:36 -0600
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-13 13:36 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Views for app settings
25"""
27import json
28import os
29import sys
30import subprocess
31from collections import OrderedDict
33from wuttjamaican.db.model import Setting
34from wuttaweb.views import MasterView
35from wuttaweb.util import get_libver, get_liburl
38class AppInfoView(MasterView):
39 """
40 Master view for the core app info, to show/edit config etc.
42 Default route prefix is ``appinfo``.
44 Notable URLs provided by this class:
46 * ``/appinfo/``
47 * ``/appinfo/configure``
49 See also :class:`SettingView`.
50 """
51 model_name = 'AppInfo'
52 model_title_plural = "App Info"
53 route_prefix = 'appinfo'
54 filterable = False
55 sort_on_backend = False
56 sort_defaults = 'name'
57 paginated = False
58 creatable = False
59 viewable = False
60 editable = False
61 deletable = False
62 configurable = True
64 grid_columns = [
65 'name',
66 'version',
67 'editable_project_location',
68 ]
70 # TODO: for tailbone backward compat with get_liburl() etc.
71 weblib_config_prefix = None
73 def get_grid_data(self, columns=None, session=None):
74 """ """
76 # nb. init with empty data, only load it upon user request
77 if not self.request.GET.get('partial'):
78 return []
80 # TODO: pretty sure this is not cross-platform. probably some
81 # sort of pip methods belong on the app handler? or it should
82 # have a pip handler for all that?
83 pip = os.path.join(sys.prefix, 'bin', 'pip')
84 output = subprocess.check_output([pip, 'list', '--format=json'], text=True)
85 data = json.loads(output.strip())
87 # must avoid null values for sort to work right
88 for pkg in data:
89 pkg.setdefault('editable_project_location', '')
91 return data
93 def configure_grid(self, g):
94 """ """
95 super().configure_grid(g)
97 g.sort_multiple = False
99 # name
100 g.set_searchable('name')
102 # editable_project_location
103 g.set_searchable('editable_project_location')
105 def get_weblibs(self):
106 """ """
107 return OrderedDict([
108 ('vue', "(Vue2) Vue"),
109 ('vue_resource', "(Vue2) vue-resource"),
110 ('buefy', "(Vue2) Buefy"),
111 ('buefy.css', "(Vue2) Buefy CSS"),
112 ('fontawesome', "(Vue2) FontAwesome"),
113 ('bb_vue', "(Vue3) vue"),
114 ('bb_oruga', "(Vue3) @oruga-ui/oruga-next"),
115 ('bb_oruga_bulma', "(Vue3) @oruga-ui/theme-bulma (JS)"),
116 ('bb_oruga_bulma_css', "(Vue3) @oruga-ui/theme-bulma (CSS)"),
117 ('bb_fontawesome_svg_core', "(Vue3) @fortawesome/fontawesome-svg-core"),
118 ('bb_free_solid_svg_icons', "(Vue3) @fortawesome/free-solid-svg-icons"),
119 ('bb_vue_fontawesome', "(Vue3) @fortawesome/vue-fontawesome"),
120 ])
122 def configure_get_simple_settings(self):
123 """ """
124 simple_settings = [
126 # basics
127 {'name': f'{self.config.appname}.app_title'},
128 {'name': f'{self.config.appname}.node_type'},
129 {'name': f'{self.config.appname}.node_title'},
130 {'name': f'{self.config.appname}.production',
131 'type': bool},
132 {'name': f'{self.config.appname}.web.menus.handler.spec'},
133 # nb. this is deprecated; we define so it is auto-deleted
134 # when we replace with newer setting
135 {'name': f'{self.config.appname}.web.menus.handler_spec'},
137 # user/auth
138 {'name': 'wuttaweb.home_redirect_to_login',
139 'type': bool, 'default': False},
141 # email
142 {'name': f'{self.config.appname}.mail.send_emails',
143 'type': bool, 'default': False},
144 {'name': f'{self.config.appname}.email.default.sender'},
145 {'name': f'{self.config.appname}.email.default.subject'},
146 {'name': f'{self.config.appname}.email.default.to'},
147 {'name': f'{self.config.appname}.email.feedback.subject'},
148 {'name': f'{self.config.appname}.email.feedback.to'},
150 ]
152 def getval(key):
153 return self.config.get(f'wuttaweb.{key}')
155 weblibs = self.get_weblibs()
156 for key, title in weblibs.items():
158 simple_settings.append({
159 'name': f'wuttaweb.libver.{key}',
160 'default': getval(f'libver.{key}'),
161 })
162 simple_settings.append({
163 'name': f'wuttaweb.liburl.{key}',
164 'default': getval(f'liburl.{key}'),
165 })
167 return simple_settings
169 def configure_get_context(self, **kwargs):
170 """ """
171 context = super().configure_get_context(**kwargs)
173 # add registered menu handlers
174 web = self.app.get_web_handler()
175 handlers = web.get_menu_handler_specs()
176 handlers = [{'spec': spec} for spec in handlers]
177 context['menu_handlers'] = handlers
179 # add `weblibs` to context, based on config values
180 weblibs = self.get_weblibs()
181 for key in weblibs:
182 title = weblibs[key]
183 weblibs[key] = {
184 'key': key,
185 'title': title,
187 # nb. these values are exactly as configured, and are
188 # used for editing the settings
189 'configured_version': get_libver(self.request, key,
190 prefix=self.weblib_config_prefix,
191 configured_only=True),
192 'configured_url': get_liburl(self.request, key,
193 prefix=self.weblib_config_prefix,
194 configured_only=True),
196 # nb. these are for display only
197 'default_version': get_libver(self.request, key,
198 prefix=self.weblib_config_prefix,
199 default_only=True),
200 'live_url': get_liburl(self.request, key,
201 prefix=self.weblib_config_prefix),
202 }
203 context['weblibs'] = list(weblibs.values())
205 return context
208class SettingView(MasterView):
209 """
210 Master view for the "raw" settings table.
212 Default route prefix is ``settings``.
214 Notable URLs provided by this class:
216 * ``/settings/``
218 See also :class:`AppInfoView`.
219 """
220 model_class = Setting
221 model_title = "Raw Setting"
222 deletable_bulk = True
223 filter_defaults = {
224 'name': {'active': True},
225 }
226 sort_defaults = 'name'
228 # TODO: master should handle this (per model key)
229 def configure_grid(self, g):
230 """ """
231 super().configure_grid(g)
233 # name
234 g.set_link('name')
236 def configure_form(self, f):
237 """ """
238 super().configure_form(f)
240 # name
241 f.set_validator('name', self.unique_name)
243 # value
244 # TODO: master should handle this (per column nullable)
245 f.set_required('value', False)
247 def unique_name(self, node, value):
248 """ """
249 model = self.app.model
250 session = self.Session()
252 query = session.query(model.Setting)\
253 .filter(model.Setting.name == value)
255 if self.editing:
256 name = self.request.matchdict['name']
257 query = query.filter(model.Setting.name != name)
259 if query.count():
260 node.raise_invalid("Setting name must be unique")
263def defaults(config, **kwargs):
264 base = globals()
266 AppInfoView = kwargs.get('AppInfoView', base['AppInfoView'])
267 AppInfoView.defaults(config)
269 SettingView = kwargs.get('SettingView', base['SettingView'])
270 SettingView.defaults(config)
273def includeme(config):
274 defaults(config)