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

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""" 

26 

27import json 

28import os 

29import sys 

30import subprocess 

31from collections import OrderedDict 

32 

33from wuttjamaican.db.model import Setting 

34from wuttaweb.views import MasterView 

35from wuttaweb.util import get_libver, get_liburl 

36 

37 

38class AppInfoView(MasterView): 

39 """ 

40 Master view for the core app info, to show/edit config etc. 

41 

42 Default route prefix is ``appinfo``. 

43 

44 Notable URLs provided by this class: 

45 

46 * ``/appinfo/`` 

47 * ``/appinfo/configure`` 

48 

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 

63 

64 grid_columns = [ 

65 'name', 

66 'version', 

67 'editable_project_location', 

68 ] 

69 

70 # TODO: for tailbone backward compat with get_liburl() etc. 

71 weblib_config_prefix = None 

72 

73 def get_grid_data(self, columns=None, session=None): 

74 """ """ 

75 

76 # nb. init with empty data, only load it upon user request 

77 if not self.request.GET.get('partial'): 

78 return [] 

79 

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()) 

86 

87 # must avoid null values for sort to work right 

88 for pkg in data: 

89 pkg.setdefault('editable_project_location', '') 

90 

91 return data 

92 

93 def configure_grid(self, g): 

94 """ """ 

95 super().configure_grid(g) 

96 

97 g.sort_multiple = False 

98 

99 # name 

100 g.set_searchable('name') 

101 

102 # editable_project_location 

103 g.set_searchable('editable_project_location') 

104 

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 ]) 

121 

122 def configure_get_simple_settings(self): 

123 """ """ 

124 simple_settings = [ 

125 

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'}, 

136 

137 # user/auth 

138 {'name': 'wuttaweb.home_redirect_to_login', 

139 'type': bool, 'default': False}, 

140 

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'}, 

149 

150 ] 

151 

152 def getval(key): 

153 return self.config.get(f'wuttaweb.{key}') 

154 

155 weblibs = self.get_weblibs() 

156 for key, title in weblibs.items(): 

157 

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 }) 

166 

167 return simple_settings 

168 

169 def configure_get_context(self, **kwargs): 

170 """ """ 

171 context = super().configure_get_context(**kwargs) 

172 

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 

178 

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, 

186 

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), 

195 

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()) 

204 

205 return context 

206 

207 

208class SettingView(MasterView): 

209 """ 

210 Master view for the "raw" settings table. 

211 

212 Default route prefix is ``settings``. 

213 

214 Notable URLs provided by this class: 

215 

216 * ``/settings/`` 

217 

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' 

227 

228 # TODO: master should handle this (per model key) 

229 def configure_grid(self, g): 

230 """ """ 

231 super().configure_grid(g) 

232 

233 # name 

234 g.set_link('name') 

235 

236 def configure_form(self, f): 

237 """ """ 

238 super().configure_form(f) 

239 

240 # name 

241 f.set_validator('name', self.unique_name) 

242 

243 # value 

244 # TODO: master should handle this (per column nullable) 

245 f.set_required('value', False) 

246 

247 def unique_name(self, node, value): 

248 """ """ 

249 model = self.app.model 

250 session = self.Session() 

251 

252 query = session.query(model.Setting)\ 

253 .filter(model.Setting.name == value) 

254 

255 if self.editing: 

256 name = self.request.matchdict['name'] 

257 query = query.filter(model.Setting.name != name) 

258 

259 if query.count(): 

260 node.raise_invalid("Setting name must be unique") 

261 

262 

263def defaults(config, **kwargs): 

264 base = globals() 

265 

266 AppInfoView = kwargs.get('AppInfoView', base['AppInfoView']) 

267 AppInfoView.defaults(config) 

268 

269 SettingView = kwargs.get('SettingView', base['SettingView']) 

270 SettingView.defaults(config) 

271 

272 

273def includeme(config): 

274 defaults(config)