Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/views/settings.py: 100%
86 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-26 14:40 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-26 14:40 -0500
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},
133 # user/auth
134 {'name': 'wuttaweb.home_redirect_to_login',
135 'type': bool, 'default': False},
137 # email
138 {'name': f'{self.config.appname}.mail.send_emails',
139 'type': bool, 'default': False},
140 {'name': f'{self.config.appname}.email.default.sender'},
141 {'name': f'{self.config.appname}.email.default.subject'},
142 {'name': f'{self.config.appname}.email.default.to'},
143 {'name': f'{self.config.appname}.email.feedback.subject'},
144 {'name': f'{self.config.appname}.email.feedback.to'},
146 ]
148 def getval(key):
149 return self.config.get(f'wuttaweb.{key}')
151 weblibs = self.get_weblibs()
152 for key, title in weblibs.items():
154 simple_settings.append({
155 'name': f'wuttaweb.libver.{key}',
156 'default': getval(f'libver.{key}'),
157 })
158 simple_settings.append({
159 'name': f'wuttaweb.liburl.{key}',
160 'default': getval(f'liburl.{key}'),
161 })
163 return simple_settings
165 def configure_get_context(self, **kwargs):
166 """ """
168 # normal context
169 context = super().configure_get_context(**kwargs)
171 # we will add `weblibs` to context, based on config values
172 weblibs = self.get_weblibs()
173 for key in weblibs:
174 title = weblibs[key]
175 weblibs[key] = {
176 'key': key,
177 'title': title,
179 # nb. these values are exactly as configured, and are
180 # used for editing the settings
181 'configured_version': get_libver(self.request, key,
182 prefix=self.weblib_config_prefix,
183 configured_only=True),
184 'configured_url': get_liburl(self.request, key,
185 prefix=self.weblib_config_prefix,
186 configured_only=True),
188 # nb. these are for display only
189 'default_version': get_libver(self.request, key,
190 prefix=self.weblib_config_prefix,
191 default_only=True),
192 'live_url': get_liburl(self.request, key,
193 prefix=self.weblib_config_prefix),
194 }
196 context['weblibs'] = list(weblibs.values())
197 return context
200class SettingView(MasterView):
201 """
202 Master view for the "raw" settings table.
204 Default route prefix is ``settings``.
206 Notable URLs provided by this class:
208 * ``/settings/``
210 See also :class:`AppInfoView`.
211 """
212 model_class = Setting
213 model_title = "Raw Setting"
214 deletable_bulk = True
215 filter_defaults = {
216 'name': {'active': True},
217 }
218 sort_defaults = 'name'
220 # TODO: master should handle this (per model key)
221 def configure_grid(self, g):
222 """ """
223 super().configure_grid(g)
225 # name
226 g.set_link('name')
228 def configure_form(self, f):
229 """ """
230 super().configure_form(f)
232 # name
233 f.set_validator('name', self.unique_name)
235 # value
236 # TODO: master should handle this (per column nullable)
237 f.set_required('value', False)
239 def unique_name(self, node, value):
240 """ """
241 model = self.app.model
242 session = self.Session()
244 query = session.query(model.Setting)\
245 .filter(model.Setting.name == value)
247 if self.editing:
248 name = self.request.matchdict['name']
249 query = query.filter(model.Setting.name != name)
251 if query.count():
252 node.raise_invalid("Setting name must be unique")
255def defaults(config, **kwargs):
256 base = globals()
258 AppInfoView = kwargs.get('AppInfoView', base['AppInfoView'])
259 AppInfoView.defaults(config)
261 SettingView = kwargs.get('SettingView', base['SettingView'])
262 SettingView.defaults(config)
265def includeme(config):
266 defaults(config)