Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/subscribers.py: 100%

89 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-27 21:18 -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""" 

24Event Subscribers 

25 

26It is assumed that most apps will include this module somewhere during 

27startup. For instance this happens within 

28:func:`~wuttaweb.app.main()`:: 

29 

30 pyramid_config.include('wuttaweb.subscribers') 

31 

32This allows for certain common logic to be available for all apps. 

33 

34However some custom apps may need to supplement or replace the event 

35hooks contained here, depending on the circumstance. 

36""" 

37 

38import functools 

39import json 

40import logging 

41 

42from pyramid import threadlocal 

43 

44from wuttaweb import helpers 

45from wuttaweb.db import Session 

46 

47 

48log = logging.getLogger(__name__) 

49 

50 

51def new_request(event): 

52 """ 

53 Event hook called when processing a new :term:`request`. 

54 

55 The hook is auto-registered if this module is "included" by 

56 Pyramid config object. Or you can explicitly register it:: 

57 

58 pyramid_config.add_subscriber('wuttaweb.subscribers.new_request', 

59 'pyramid.events.NewRequest') 

60 

61 This will add to the request object: 

62 

63 .. attribute:: request.wutta_config 

64 

65 Reference to the app :term:`config object`. 

66 

67 .. function:: request.get_referrer(default=None) 

68 

69 Request method to get the "canonical" HTTP referrer value. 

70 This has logic to check for referrer in the request params, 

71 user session etc. 

72 

73 :param default: Optional default URL if none is found in 

74 request params/session. If no default is specified, 

75 the ``'home'`` route is used. 

76 

77 .. attribute:: request.use_oruga 

78 

79 Flag indicating whether the frontend should be displayed using 

80 Vue 3 + Oruga (if ``True``), or else Vue 2 + Buefy (if 

81 ``False``). This flag is ``False`` by default. 

82 """ 

83 request = event.request 

84 config = request.registry.settings['wutta_config'] 

85 app = config.get_app() 

86 

87 request.wutta_config = config 

88 

89 def get_referrer(default=None): 

90 if request.params.get('referrer'): 

91 return request.params['referrer'] 

92 if request.session.get('referrer'): 

93 return request.session.pop('referrer') 

94 referrer = getattr(request, 'referrer', None) 

95 if (not referrer or referrer == request.current_route_url() 

96 or not referrer.startswith(request.host_url)): 

97 referrer = default or request.route_url('home') 

98 return referrer 

99 

100 request.get_referrer = get_referrer 

101 

102 def use_oruga(request): 

103 spec = config.get('wuttaweb.oruga_detector.spec') 

104 if spec: 

105 func = app.load_object(spec) 

106 return func(request) 

107 return False 

108 

109 request.set_property(use_oruga, reify=True) 

110 

111 

112def default_user_getter(request, db_session=None): 

113 """ 

114 This is the default function used to retrieve user object from 

115 database. Result of this is then assigned to :attr:`request.user` 

116 as part of the :func:`new_request_set_user()` hook. 

117 """ 

118 uuid = request.authenticated_userid 

119 if uuid: 

120 config = request.wutta_config 

121 app = config.get_app() 

122 model = app.model 

123 session = db_session or Session() 

124 return session.get(model.User, uuid) 

125 

126 

127def new_request_set_user( 

128 event, 

129 user_getter=default_user_getter, 

130 db_session=None, 

131): 

132 """ 

133 Event hook called when processing a new :term:`request`, for sake 

134 of setting the :attr:`request.user` and similar properties. 

135 

136 The hook is auto-registered if this module is "included" by 

137 Pyramid config object. Or you can explicitly register it:: 

138 

139 pyramid_config.add_subscriber('wuttaweb.subscribers.new_request_set_user', 

140 'pyramid.events.NewRequest') 

141 

142 You may wish to "supplement" this hook by registering your own 

143 custom hook and then invoking this one as needed. You can then 

144 pass certain params to override only parts of the logic: 

145 

146 :param user_getter: Optional getter function to retrieve the user 

147 from database, instead of :func:`default_user_getter()`. 

148 

149 :param db_session: Optional :term:`db session` to use, 

150 instead of :class:`wuttaweb.db.sess.Session`. 

151 

152 This will add to the request object: 

153 

154 .. attribute:: request.user 

155 

156 Reference to the authenticated 

157 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` instance 

158 (if logged in), or ``None``. 

159 

160 .. attribute:: request.is_admin 

161 

162 Flag indicating whether current user is a member of the 

163 Administrator role. 

164 

165 .. attribute:: request.is_root 

166 

167 Flag indicating whether user is currently elevated to root 

168 privileges. This is only possible if :attr:`request.is_admin` 

169 is also true. 

170 

171 .. attribute:: request.user_permissions 

172 

173 The ``set`` of permission names which are granted to the 

174 current user. 

175 

176 This set is obtained by calling 

177 :meth:`~wuttjamaican:wuttjamaican.auth.AuthHandler.get_permissions()`. 

178 

179 .. function:: request.has_perm(name) 

180 

181 Shortcut to check if current user has the given permission:: 

182 

183 if not request.has_perm('users.edit'): 

184 raise self.forbidden() 

185 

186 .. function:: request.has_any_perm(*names) 

187 

188 Shortcut to check if current user has any of the given 

189 permissions:: 

190 

191 if request.has_any_perm('users.list', 'users.view'): 

192 return "can either list or view" 

193 else: 

194 raise self.forbidden() 

195 

196 """ 

197 request = event.request 

198 config = request.registry.settings['wutta_config'] 

199 app = config.get_app() 

200 auth = app.get_auth_handler() 

201 

202 # request.user 

203 if db_session: 

204 user_getter = functools.partial(user_getter, db_session=db_session) 

205 request.set_property(user_getter, name='user', reify=True) 

206 

207 # request.is_admin 

208 def is_admin(request): 

209 return auth.user_is_admin(request.user) 

210 request.set_property(is_admin, reify=True) 

211 

212 # request.is_root 

213 def is_root(request): 

214 if request.is_admin: 

215 if request.session.get('is_root', False): 

216 return True 

217 return False 

218 request.set_property(is_root, reify=True) 

219 

220 # request.user_permissions 

221 def user_permissions(request): 

222 session = db_session or Session() 

223 return auth.get_permissions(session, request.user) 

224 request.set_property(user_permissions, reify=True) 

225 

226 # request.has_perm() 

227 def has_perm(name): 

228 if request.is_root: 

229 return True 

230 if name in request.user_permissions: 

231 return True 

232 return False 

233 request.has_perm = has_perm 

234 

235 # request.has_any_perm() 

236 def has_any_perm(*names): 

237 for name in names: 

238 if request.has_perm(name): 

239 return True 

240 return False 

241 request.has_any_perm = has_any_perm 

242 

243 

244def before_render(event): 

245 """ 

246 Event hook called just before rendering a template. 

247 

248 The hook is auto-registered if this module is "included" by 

249 Pyramid config object. Or you can explicitly register it:: 

250 

251 pyramid_config.add_subscriber('wuttaweb.subscribers.before_render', 

252 'pyramid.events.BeforeRender') 

253 

254 This will add some things to the template context dict. Each of 

255 these may be used "directly" in a template then, e.g.: 

256 

257 .. code-block:: mako 

258 

259 ${app.get_title()} 

260 

261 Here are the keys added to context dict by this hook: 

262 

263 .. data:: 'app' 

264 

265 Reference to the :term:`app handler`. 

266 

267 .. data:: 'config' 

268 

269 Reference to the app :term:`config object`. 

270 

271 .. data:: 'h' 

272 

273 Reference to the helper module, :mod:`wuttaweb.helpers`. 

274 

275 .. data:: 'json' 

276 

277 Reference to the built-in module, :mod:`python:json`. 

278 

279 .. data:: 'menus' 

280 

281 Set of entries to be shown in the main menu. This is obtained 

282 by calling :meth:`~wuttaweb.menus.MenuHandler.do_make_menus()` 

283 on the configured :class:`~wuttaweb.menus.MenuHandler`. 

284 

285 .. data:: 'url' 

286 

287 Reference to the request method, 

288 :meth:`~pyramid:pyramid.request.Request.route_url()`. 

289 """ 

290 request = event.get('request') or threadlocal.get_current_request() 

291 config = request.wutta_config 

292 app = config.get_app() 

293 web = app.get_web_handler() 

294 

295 context = event 

296 context['app'] = app 

297 context['config'] = config 

298 context['h'] = helpers 

299 context['url'] = request.route_url 

300 context['json'] = json 

301 context['b'] = 'o' if request.use_oruga else 'b' # for buefy 

302 

303 # TODO: this should be avoided somehow, for non-traditional web 

304 # apps, esp. "API" web apps. (in the meantime can configure the 

305 # app to use NullMenuHandler which avoids most of the overhead.) 

306 menus = web.get_menu_handler() 

307 context['menus'] = menus.do_make_menus(request) 

308 

309 

310def includeme(config): 

311 config.add_subscriber(new_request, 'pyramid.events.NewRequest') 

312 config.add_subscriber(new_request_set_user, 'pyramid.events.NewRequest') 

313 config.add_subscriber(before_render, 'pyramid.events.BeforeRender')