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

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 

133 # user/auth 

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

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

136 

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

145 

146 ] 

147 

148 def getval(key): 

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

150 

151 weblibs = self.get_weblibs() 

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

153 

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

162 

163 return simple_settings 

164 

165 def configure_get_context(self, **kwargs): 

166 """ """ 

167 

168 # normal context 

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

170 

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, 

178 

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

187 

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 } 

195 

196 context['weblibs'] = list(weblibs.values()) 

197 return context 

198 

199 

200class SettingView(MasterView): 

201 """ 

202 Master view for the "raw" settings table. 

203 

204 Default route prefix is ``settings``. 

205 

206 Notable URLs provided by this class: 

207 

208 * ``/settings/`` 

209 

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' 

219 

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

221 def configure_grid(self, g): 

222 """ """ 

223 super().configure_grid(g) 

224 

225 # name 

226 g.set_link('name') 

227 

228 def configure_form(self, f): 

229 """ """ 

230 super().configure_form(f) 

231 

232 # name 

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

234 

235 # value 

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

237 f.set_required('value', False) 

238 

239 def unique_name(self, node, value): 

240 """ """ 

241 model = self.app.model 

242 session = self.Session() 

243 

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

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

246 

247 if self.editing: 

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

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

250 

251 if query.count(): 

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

253 

254 

255def defaults(config, **kwargs): 

256 base = globals() 

257 

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

259 AppInfoView.defaults(config) 

260 

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

262 SettingView.defaults(config) 

263 

264 

265def includeme(config): 

266 defaults(config)