from django.http import HttpResponseForbidden, HttpResponseRedirect, JsonResponse from django.urls import reverse from django.shortcuts import get_object_or_404 from django.db.models import Func, CharField from urllib.parse import urlencode import time import functools import unicodedata from common.models import Thematic, UserConfig, UserThematics def check_thematic_access(thematic_code, edit_permission_required=False): """Decorator to verify user access to a specific thematic (passed as a parameter).""" def decorator(view_func): @functools.wraps(view_func) def _wrapped_view(request, *args, **kwargs): user_config = UserConfig.objects.filter(user=request.user).first() # Get user access configuration thematic = get_object_or_404(Thematic, code=thematic_code) # Get thematic from the given parameter # Check if the user has a configuration if not user_config: return HttpResponseForbidden("Access denied: You do not have access to this thematic.") # Check if the user has access to the thematic if edit_permission_required: if not UserThematics.objects.filter( user_config=user_config, thematic=thematic, can_edit_assets=True ).exists(): return HttpResponseForbidden("Access denied: You do not have edit permissions for this thematic.") else: # Si l'utilisateur a un rôle 'admin' ou 'operator', il peut voir tous les assets if ( (not user_config.roles.filter(name__in=['admin','operator']).exists()) and (not UserThematics.objects.filter( user_config=user_config, thematic=thematic, can_view_assets=True ).exists()) ): return HttpResponseForbidden("Access denied: You do not have view permissions for this thematic.") # Execute the view if everything is OK return view_func(request, *args, **kwargs) return _wrapped_view return decorator def redirect_with_variant(viewname, variant=None, url_kwargs=None, extra_query_params=None): """ Redirige vers une vue avec des paramètres d'URL et le paramètre ?v=... si fourni. :param viewname: nom de la vue (ex: 'controls:controls_detail') :param variant: 'mobile' ou 'desktop' (ajouté comme ?v=mobile dans l'URL) :param url_kwargs: dictionnaire des arguments de reverse (ex: {'control_id': 3}) :param extra_query_params: dictionnaire de paramètres supplémentaires à ajouter dans la query string """ if url_kwargs is None: url_kwargs = {} if extra_query_params is None: extra_query_params = {} if variant: extra_query_params['v'] = variant base_url = reverse(viewname, kwargs=url_kwargs) if extra_query_params: query_string = urlencode(extra_query_params) full_url = f"{base_url}?{query_string}" else: full_url = base_url return HttpResponseRedirect(full_url) def measure_time(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.time() response = func(*args, **kwargs) elapsed = time.time() - start print(f"{func.__name__} exécutée en {elapsed:.4f} secondes") return response return wrapper # Appelle la fonction PostgreSQL unaccent(...) class Unaccent(Func): function = 'public.unaccent_immutable' output_field = CharField() def strip_accents_lower(s: str) -> str: if not s: return '' s = ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn') return s.lower() def get_short_name(user): """ Retourne 'Prénom N.' à partir d'un objet User. """ if not user: return "" first = getattr(user, "first_name", "") or "" last = getattr(user, "last_name", "") or "" initial = (last[0].upper() + ".") if last else "" return f"{first} {initial}".strip() from django.utils.translation import gettext_lazy as _ REGIONAL_TERMINOLOGY = { 'BE': { 'regional_standard_name': _('Belgique (PEB / Maison Communale / Sécurité PMR)'), 'admin_building': _('Hôtel de Ville / Maison Communale'), 'energy_diag_short': _('PEB'), 'energy_diag_full': _('Performance Énergétique des Bâtiments (PEB)'), 'carbon_diag_short': _('Bilan Carbone'), 'carbon_diag_full': _('Émissions de Carbone / Bilan Énergétique'), 'public_access_short': _('Accès public & PMR'), 'public_access_full': _('Accueil & Sécurité du Public (Normes PMR)'), 'public_access_category': _('Catégorie d\'accueil'), 'public_access_type': _('Type d\'affectation'), }, 'FR': { 'regional_standard_name': _('France (DPE / GES / Mairie / ERP)'), 'admin_building': _('Mairie / Hôtel de Ville'), 'energy_diag_short': _('DPE'), 'energy_diag_full': _('Diagnostic de Performance Énergétique (DPE)'), 'carbon_diag_short': _('GES'), 'carbon_diag_full': _('Émissions de Gaz à Effet de Serre (GES)'), 'public_access_short': _('ERP'), 'public_access_full': _('Établissement Recevant du Public (ERP)'), 'public_access_category': _('Catégorie ERP'), 'public_access_type': _('Type ERP'), }, 'INT': { 'regional_standard_name': _('International / Générique'), 'admin_building': _('Bâtiment Administratif / Hôtel de Ville'), 'energy_diag_short': _('PEB / DPE'), 'energy_diag_full': _('Performance Énergétique (PEB / DPE / EPC)'), 'carbon_diag_short': _('Émissions Carbone'), 'carbon_diag_full': _('Bilan Carbone & Émissions'), 'public_access_short': _('Accès Public'), 'public_access_full': _('Bâtiment d\'Accueil du Public (ERP / PMR)'), 'public_access_category': _('Capacité d\'accueil'), 'public_access_type': _('Type d\'établissement'), } } def get_regional_terms(standard_code='BE'): """Returns regional terminology dictionary for a given standard code ('BE', 'FR', 'INT').""" return REGIONAL_TERMINOLOGY.get(standard_code or 'BE', REGIONAL_TERMINOLOGY['BE'])