from __future__ import annotations import os from typing import Iterable, List from django.contrib.auth.models import AnonymousUser from django.urls import resolve from .models import UserConfig, AppView SESSION_ALLOWED_IDS_KEY = "account_switch_allowed_user_ids" SESSION_ORIGIN_ID_KEY = "account_switch_origin_user_id" from django.conf import settings def environment_info(request): """ Context processor that exposes environment information. Uses ENVIRONMENT variable which can be 'development', 'test', or 'production'. Makes 'environment_name' available in all templates. """ env = os.getenv('ENVIRONMENT', '').lower() # Map environment names to display labels env_labels = { 'development': 'DEV', 'dev': 'DEV', 'test': 'TEST', 'staging': 'STAGING', } # Only show label for non-production environments environment_label = env_labels.get(env, '') return { 'environment_name': environment_label, 'MAP_STYLE_LIGHT': getattr(settings, 'MAP_STYLE_LIGHT', None) or 'https://api.maptiler.com/maps/streets-v2-light/style.json', 'MAP_STYLE_STANDARD': getattr(settings, 'MAP_STYLE_STANDARD', None) or 'https://api.maptiler.com/maps/streets-v2/style.json', } def compute_initial_allowed_ids(config: UserConfig) -> List[int]: linked_ids: Iterable[int] = config.linked_configs.values_list("user_id", flat=True) allowed = list(linked_ids) allowed.append(config.user_id) return allowed def current_app_view(request): """ Context processor that determines the current active AppView based on the URL namespace. Makes 'current_view' available in all templates. Only 'sign' and 'documents' have their own dedicated views. All other apps (assets, interventions, controls, contracts, etc.) use 'asset_management' view. """ current_view = None # Apps that have their own dedicated view DEDICATED_VIEW_APPS = ['sign', 'documents', 'reporting', 'maps', 'authorizations', 'panoramax'] try: # Get the namespace from the current URL resolved = resolve(request.path) namespace = resolved.namespace # e.g., 'sign', 'home', 'assets' if namespace: if namespace in DEDICATED_VIEW_APPS: # These apps have their own dedicated view current_view = AppView.objects.filter(url_name__startswith=f"{namespace}:").first() else: # All other apps (home, assets, interventions, controls, contracts, etc.) # should show 'asset_management' view current_view = AppView.objects.filter(code='asset_management').first() except Exception: pass return { 'current_view': current_view, } def linked_accounts(request): user = getattr(request, "user", None) if not user or isinstance(user, AnonymousUser) or not user.is_authenticated: request.session.pop(SESSION_ALLOWED_IDS_KEY, None) request.session.pop(SESSION_ORIGIN_ID_KEY, None) return { "switchable_accounts": [], "account_switch_origin_user_id": None, } try: current_config: UserConfig = user.config except UserConfig.DoesNotExist: request.session.pop(SESSION_ALLOWED_IDS_KEY, None) request.session.pop(SESSION_ORIGIN_ID_KEY, None) return { "switchable_accounts": [], "account_switch_origin_user_id": None, } session = getattr(request, "session", None) if session is None: return { "switchable_accounts": [], "account_switch_origin_user_id": None, } allowed_ids = session.get(SESSION_ALLOWED_IDS_KEY) origin_user_id = session.get(SESSION_ORIGIN_ID_KEY) if allowed_ids is None or origin_user_id is None: allowed_ids = compute_initial_allowed_ids(current_config) origin_user_id = user.pk else: allowed_ids = list(dict.fromkeys(int(pk) for pk in allowed_ids)) if user.pk not in allowed_ids: allowed_ids.append(user.pk) session[SESSION_ALLOWED_IDS_KEY] = allowed_ids session[SESSION_ORIGIN_ID_KEY] = origin_user_id switchable_accounts = ( UserConfig.objects.select_related("user") .filter(user_id__in=allowed_ids) .exclude(user_id=user.pk) .order_by("user__first_name", "user__last_name") ) return { "switchable_accounts": switchable_accounts, "account_switch_origin_user_id": origin_user_id, } def organization_info(request): """ Context processor that exposes organization details from settings. """ return { 'product_name': getattr(settings, 'PRODUCT_NAME', 'StreetUp'), 'organization_name': getattr(settings, 'ORGANIZATION_NAME', 'Brussels Mobility'), 'organization_logo_header': getattr(settings, 'ORGANIZATION_LOGO_HEADER', 'common/android-192.png'), 'organization_primary_color': getattr(settings, 'ORGANIZATION_PRIMARY_COLOR_HEX', '#2f3e8b'), 'map_default_center': [getattr(settings, 'MAP_DEFAULT_CENTER_LON', 4.3572), getattr(settings, 'MAP_DEFAULT_CENTER_LAT', 50.8477)], 'map_default_zoom': getattr(settings, 'MAP_DEFAULT_ZOOM', 11), 'map_default_extent': getattr(settings, 'MAP_DEFAULT_EXTENT', None), 'map_ortho_wms_url': getattr(settings, 'MAP_ORTHO_WMS_URL', ''), 'map_ortho_wms_name': getattr(settings, 'MAP_ORTHO_WMS_NAME', ''), 'map_ortho_wms_attribution': getattr(settings, 'MAP_ORTHO_WMS_ATTRIBUTION', ''), } def regional_terminology(request): """ Context processor providing regional terminology & standard definitions (e.g., PEB in Belgium vs DPE/GES in France) based on the user's UserConfig or system default. Makes 'regional_terms' and 'regional_standard' available in all templates. """ from .utils import get_regional_terms standard_code = getattr(settings, 'REGIONAL_STANDARD', 'BE') if request.user.is_authenticated: user_config = getattr(request.user, 'config', None) if user_config and getattr(user_config, 'regional_standard', None): standard_code = user_config.regional_standard return { 'regional_standard': standard_code, 'regional_terms': get_regional_terms(standard_code), }