 70cc754f3e
			
		
	
	
		70cc754f3e
		
	
	
	
	
		
			
			instead of webhelpers2.html.tags.select() which seems to break for me in dev now with python 3.10
		
			
				
	
	
		
			270 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8; -*-
 | |
| ################################################################################
 | |
| #
 | |
| #  Rattail -- Retail Software Framework
 | |
| #  Copyright © 2010-2023 Lance Edgar
 | |
| #
 | |
| #  This file is part of Rattail.
 | |
| #
 | |
| #  Rattail is free software: you can redistribute it and/or modify it under the
 | |
| #  terms of the GNU General Public License as published by the Free Software
 | |
| #  Foundation, either version 3 of the License, or (at your option) any later
 | |
| #  version.
 | |
| #
 | |
| #  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 | |
| #  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
| #  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | |
| #  details.
 | |
| #
 | |
| #  You should have received a copy of the GNU General Public License along with
 | |
| #  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 | |
| #
 | |
| ################################################################################
 | |
| """
 | |
| Event Subscribers
 | |
| """
 | |
| 
 | |
| import six
 | |
| import json
 | |
| import datetime
 | |
| 
 | |
| import rattail
 | |
| 
 | |
| import colander
 | |
| import deform
 | |
| from pyramid import threadlocal
 | |
| from webhelpers2.html import tags
 | |
| 
 | |
| import tailbone
 | |
| from tailbone import helpers
 | |
| from tailbone.db import Session
 | |
| from tailbone.config import csrf_header_name, should_expose_websockets
 | |
| from tailbone.menus import make_simple_menus
 | |
| from tailbone.util import get_global_search_options
 | |
| 
 | |
| 
 | |
| def new_request(event):
 | |
|     """
 | |
|     Identify the current user, and cache their current permissions.  Also adds
 | |
|     the ``rattail_config`` attribute to the request.
 | |
| 
 | |
|     A global Rattail ``config`` should already be present within the Pyramid
 | |
|     application registry's settings, which would normally be accessed via::
 | |
|     
 | |
|        request.registry.settings['rattail_config']
 | |
| 
 | |
|     This function merely "promotes" that config object so that it is more
 | |
|     directly accessible, a la::
 | |
| 
 | |
|        request.rattail_config
 | |
| 
 | |
|     .. note::
 | |
|        This of course assumes that a Rattail ``config`` object *has* in fact
 | |
|        already been placed in the application registry settings.  If this is
 | |
|        not the case, this function will do nothing.
 | |
| 
 | |
|     Also, attach some goodies to the request object:
 | |
| 
 | |
|     * The currently logged-in user instance (if any), as ``user``.
 | |
| 
 | |
|     * ``is_admin`` flag indicating whether user has the Administrator role.
 | |
| 
 | |
|     * ``is_root`` flag indicating whether user is currently elevated to root.
 | |
| 
 | |
|     * A shortcut method for permission checking, as ``has_perm()``.
 | |
|     """
 | |
|     request = event.request
 | |
|     rattail_config = request.registry.settings.get('rattail_config')
 | |
|     # TODO: why would this ever be null?
 | |
|     if rattail_config:
 | |
|         request.rattail_config = rattail_config
 | |
| 
 | |
|     def user(request):
 | |
|         user = None
 | |
|         uuid = request.authenticated_userid
 | |
|         if uuid:
 | |
|             model = request.rattail_config.get_model()
 | |
|             user = Session.get(model.User, uuid)
 | |
|             if user:
 | |
|                 Session().set_continuum_user(user)
 | |
|         return user
 | |
| 
 | |
|     request.set_property(user, reify=True)
 | |
| 
 | |
|     # assign client IP address to the session, for sake of versioning
 | |
|     Session().continuum_remote_addr = request.client_addr
 | |
| 
 | |
|     request.is_admin = bool(request.user) and request.user.is_admin()
 | |
|     request.is_root = request.is_admin and request.session.get('is_root', False)
 | |
| 
 | |
|     # TODO: why would this ever be null?
 | |
|     if rattail_config:
 | |
| 
 | |
|         app = rattail_config.get_app()
 | |
|         auth = app.get_auth_handler()
 | |
|         request.tailbone_cached_permissions = auth.get_permissions(
 | |
|             Session(), request.user)
 | |
| 
 | |
|         def has_perm(name):
 | |
|             if name in request.tailbone_cached_permissions:
 | |
|                 return True
 | |
|             return request.is_root
 | |
|         request.has_perm = has_perm
 | |
| 
 | |
|         def has_any_perm(*names):
 | |
|             for name in names:
 | |
|                 if has_perm(name):
 | |
|                     return True
 | |
|             return False
 | |
|         request.has_any_perm = has_any_perm
 | |
| 
 | |
| 
 | |
| def before_render(event):
 | |
|     """
 | |
|     Adds goodies to the global template renderer context.
 | |
|     """
 | |
| 
 | |
|     request = event.get('request') or threadlocal.get_current_request()
 | |
|     rattail_config = request.rattail_config
 | |
| 
 | |
|     renderer_globals = event
 | |
|     renderer_globals['rattail_app'] = request.rattail_config.get_app()
 | |
|     renderer_globals['app_title'] = request.rattail_config.app_title()
 | |
|     renderer_globals['h'] = helpers
 | |
|     renderer_globals['url'] = request.route_url
 | |
|     renderer_globals['rattail'] = rattail
 | |
|     renderer_globals['tailbone'] = tailbone
 | |
|     renderer_globals['model'] = request.rattail_config.get_model()
 | |
|     renderer_globals['enum'] = request.rattail_config.get_enum()
 | |
|     renderer_globals['six'] = six
 | |
|     renderer_globals['json'] = json
 | |
|     renderer_globals['datetime'] = datetime
 | |
|     renderer_globals['colander'] = colander
 | |
|     renderer_globals['deform'] = deform
 | |
|     renderer_globals['csrf_header_name'] = csrf_header_name(request.rattail_config)
 | |
| 
 | |
|     # theme  - we only want do this for classic web app, *not* API
 | |
|     # TODO: so, clearly we need a better way to distinguish the two
 | |
|     if 'tailbone.theme' in request.registry.settings:
 | |
|         renderer_globals['theme'] = request.registry.settings['tailbone.theme']
 | |
|         # note, this is just a global flag; user still needs permission to see picker
 | |
|         expose_picker = request.rattail_config.getbool('tailbone', 'themes.expose_picker',
 | |
|                                                        default=False)
 | |
|         renderer_globals['expose_theme_picker'] = expose_picker
 | |
|         if expose_picker:
 | |
|             # tailbone's config extension provides a default theme selection,
 | |
|             # so the default we specify here *probably* should not matter
 | |
|             available = request.rattail_config.getlist('tailbone', 'themes',
 | |
|                                                        default=['falafel'])
 | |
|             if 'default' not in available:
 | |
|                 available.insert(0, 'default')
 | |
|             options = [tags.Option(theme, value=theme) for theme in available]
 | |
|             renderer_globals['theme_picker_options'] = options
 | |
| 
 | |
|         # heck while we're assuming the classic web app here...
 | |
|         # (we don't want this to happen for the API either!)
 | |
|         # TODO: just..awful *shrug*
 | |
|         # note that we assume "simple" menus nowadays
 | |
|         if request.rattail_config.getbool('tailbone', 'menus.simple', default=True):
 | |
|             renderer_globals['menus'] = make_simple_menus(request)
 | |
| 
 | |
|         # TODO: ugh, same deal here
 | |
|         renderer_globals['messaging_enabled'] = request.rattail_config.getbool(
 | |
|             'tailbone', 'messaging.enabled', default=False)
 | |
| 
 | |
|         # background color may be set per-request, by some apps
 | |
|         if hasattr(request, 'background_color') and request.background_color:
 | |
|             renderer_globals['background_color'] = request.background_color
 | |
|         else: # otherwise we use the one from config
 | |
|             renderer_globals['background_color'] = request.rattail_config.get(
 | |
|                 'tailbone', 'background_color')
 | |
| 
 | |
|         # TODO: remove this hack once nothing references it
 | |
|         renderer_globals['buefy_0_8'] = False
 | |
| 
 | |
|         # maybe set custom stylesheet
 | |
|         css = None
 | |
|         if request.user:
 | |
|             css = request.rattail_config.get('tailbone.{}'.format(request.user.uuid),
 | |
|                                              'buefy_css')
 | |
|         if not css:
 | |
|             css = request.rattail_config.get('tailbone', 'theme.falafel.buefy_css')
 | |
|         renderer_globals['buefy_css'] = css
 | |
| 
 | |
|         # add global search data for quick access
 | |
|         renderer_globals['global_search_data'] = get_global_search_options(request)
 | |
| 
 | |
|         # here we globally declare widths for grid filter pseudo-columns
 | |
|         widths = request.rattail_config.get('tailbone', 'grids.filters.column_widths')
 | |
|         if widths:
 | |
|             widths = widths.split(';')
 | |
|             if len(widths) < 2:
 | |
|                 widths = None
 | |
|         if not widths:
 | |
|             widths = ['15em', '15em']
 | |
|         renderer_globals['filter_fieldname_width'] = widths[0]
 | |
|         renderer_globals['filter_verb_width'] = widths[1]
 | |
| 
 | |
|         # declare global support for websockets, or lack thereof
 | |
|         renderer_globals['expose_websockets'] = should_expose_websockets(rattail_config)
 | |
| 
 | |
| 
 | |
| def add_inbox_count(event):
 | |
|     """
 | |
|     Adds the current user's inbox message count to the global renderer context.
 | |
| 
 | |
|     Note that this is not enabled by default; to turn it on you must do this:
 | |
| 
 | |
|        config.add_subscriber('tailbone.subscribers.add_inbox_count', 'pyramid.events.BeforeRender')
 | |
|     """
 | |
|     request = event.get('request') or threadlocal.get_current_request()
 | |
|     if request.user:
 | |
|         renderer_globals = event
 | |
|         enum = request.rattail_config.get_enum()
 | |
|         model = request.rattail_config.get_model()
 | |
|         renderer_globals['inbox_count'] = Session.query(model.Message)\
 | |
|                                                  .outerjoin(model.MessageRecipient)\
 | |
|                                                  .filter(model.MessageRecipient.recipient == Session.merge(request.user))\
 | |
|                                                  .filter(model.MessageRecipient.status == enum.MESSAGE_STATUS_INBOX)\
 | |
|                                                  .count()
 | |
| 
 | |
| 
 | |
| def context_found(event):
 | |
|     """
 | |
|     Attach some more goodies to the request object:
 | |
| 
 | |
|     The following is attached to the request:
 | |
| 
 | |
|     * ``get_referrer()`` function
 | |
| 
 | |
|     * ``get_session_timeout()`` function
 | |
|     """
 | |
|     request = event.request
 | |
| 
 | |
|     def get_referrer(default=None, **kwargs):
 | |
|         if request.params.get('referrer'):
 | |
|             return request.params['referrer']
 | |
|         if request.session.get('referrer'):
 | |
|             return request.session.pop('referrer')
 | |
|         referrer = request.referrer
 | |
|         if (not referrer or referrer == request.current_route_url()
 | |
|             or not referrer.startswith(request.host_url)):
 | |
|             if default:
 | |
|                 referrer = default
 | |
|             else:
 | |
|                 referrer = request.route_url('home')
 | |
|         return referrer
 | |
|     request.get_referrer = get_referrer
 | |
| 
 | |
|     def get_session_timeout():
 | |
|         """
 | |
|         Returns the timeout in effect for the current session
 | |
|         """
 | |
|         return request.session.get('_timeout')
 | |
|     request.get_session_timeout = get_session_timeout
 | |
| 
 | |
| 
 | |
| def includeme(config):
 | |
|     config.add_subscriber(new_request, 'pyramid.events.NewRequest')
 | |
|     config.add_subscriber(before_render, 'pyramid.events.BeforeRender')
 | |
|     config.add_subscriber(context_found, 'pyramid.events.ContextFound')
 |