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
« 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
26It is assumed that most apps will include this module somewhere during
27startup. For instance this happens within
28:func:`~wuttaweb.app.main()`::
30 pyramid_config.include('wuttaweb.subscribers')
32This allows for certain common logic to be available for all apps.
34However some custom apps may need to supplement or replace the event
35hooks contained here, depending on the circumstance.
36"""
38import functools
39import json
40import logging
42from pyramid import threadlocal
44from wuttaweb import helpers
45from wuttaweb.db import Session
48log = logging.getLogger(__name__)
51def new_request(event):
52 """
53 Event hook called when processing a new :term:`request`.
55 The hook is auto-registered if this module is "included" by
56 Pyramid config object. Or you can explicitly register it::
58 pyramid_config.add_subscriber('wuttaweb.subscribers.new_request',
59 'pyramid.events.NewRequest')
61 This will add to the request object:
63 .. attribute:: request.wutta_config
65 Reference to the app :term:`config object`.
67 .. function:: request.get_referrer(default=None)
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.
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.
77 .. attribute:: request.use_oruga
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()
87 request.wutta_config = config
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
100 request.get_referrer = get_referrer
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
109 request.set_property(use_oruga, reify=True)
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)
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.
136 The hook is auto-registered if this module is "included" by
137 Pyramid config object. Or you can explicitly register it::
139 pyramid_config.add_subscriber('wuttaweb.subscribers.new_request_set_user',
140 'pyramid.events.NewRequest')
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:
146 :param user_getter: Optional getter function to retrieve the user
147 from database, instead of :func:`default_user_getter()`.
149 :param db_session: Optional :term:`db session` to use,
150 instead of :class:`wuttaweb.db.sess.Session`.
152 This will add to the request object:
154 .. attribute:: request.user
156 Reference to the authenticated
157 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` instance
158 (if logged in), or ``None``.
160 .. attribute:: request.is_admin
162 Flag indicating whether current user is a member of the
163 Administrator role.
165 .. attribute:: request.is_root
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.
171 .. attribute:: request.user_permissions
173 The ``set`` of permission names which are granted to the
174 current user.
176 This set is obtained by calling
177 :meth:`~wuttjamaican:wuttjamaican.auth.AuthHandler.get_permissions()`.
179 .. function:: request.has_perm(name)
181 Shortcut to check if current user has the given permission::
183 if not request.has_perm('users.edit'):
184 raise self.forbidden()
186 .. function:: request.has_any_perm(*names)
188 Shortcut to check if current user has any of the given
189 permissions::
191 if request.has_any_perm('users.list', 'users.view'):
192 return "can either list or view"
193 else:
194 raise self.forbidden()
196 """
197 request = event.request
198 config = request.registry.settings['wutta_config']
199 app = config.get_app()
200 auth = app.get_auth_handler()
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)
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)
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)
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)
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
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
244def before_render(event):
245 """
246 Event hook called just before rendering a template.
248 The hook is auto-registered if this module is "included" by
249 Pyramid config object. Or you can explicitly register it::
251 pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
252 'pyramid.events.BeforeRender')
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.:
257 .. code-block:: mako
259 ${app.get_title()}
261 Here are the keys added to context dict by this hook:
263 .. data:: 'app'
265 Reference to the :term:`app handler`.
267 .. data:: 'config'
269 Reference to the app :term:`config object`.
271 .. data:: 'h'
273 Reference to the helper module, :mod:`wuttaweb.helpers`.
275 .. data:: 'json'
277 Reference to the built-in module, :mod:`python:json`.
279 .. data:: 'menus'
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`.
285 .. data:: 'url'
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()
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
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)
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')