feat: add regional_standard configuration and dynamic terminology context processor

This commit is contained in:
kdeterme 2026-07-26 19:40:39 +02:00
parent efc0e3f955
commit a1d9c2022c
7 changed files with 111 additions and 14 deletions

View file

@ -14,7 +14,7 @@ from common.storages import PrivateMediaStorage
private_storage = PrivateMediaStorage() private_storage = PrivateMediaStorage()
USAGE_TYPE_CHOICES = ( USAGE_TYPE_CHOICES = (
('administrative', _('Administratif / Mairie')), ('administrative', _('Administratif (Hôtel de Ville / Maison Communale / Mairie)')),
('education', _('Enseignement / École / Crèche')), ('education', _('Enseignement / École / Crèche')),
('sports', _('Sport & Équipement sportif')), ('sports', _('Sport & Équipement sportif')),
('culture', _('Culture / Médiathèque / Musée')), ('culture', _('Culture / Médiathèque / Musée')),
@ -102,15 +102,15 @@ class PublicBuilding(AbstractGeoAsset):
total_surface_m2 = models.FloatField(null=True, blank=True, verbose_name=_("Surface brute totale (m²)")) total_surface_m2 = models.FloatField(null=True, blank=True, verbose_name=_("Surface brute totale (m²)"))
usable_surface_m2 = models.FloatField(null=True, blank=True, verbose_name=_("Surface utile (m²)")) usable_surface_m2 = models.FloatField(null=True, blank=True, verbose_name=_("Surface utile (m²)"))
# Réglementation ERP # Réglementation & Accueil du Public
is_erp = models.BooleanField(default=True, verbose_name=_("Établissement Recevant du Public (ERP)")) is_erp = models.BooleanField(default=True, verbose_name=_("Accueil & Sécurité du Public (ERP / PMR)"))
erp_category = models.CharField(max_length=10, choices=ERP_CATEGORY_CHOICES, null=True, blank=True, verbose_name=_("Catégorie ERP")) erp_category = models.CharField(max_length=10, choices=ERP_CATEGORY_CHOICES, null=True, blank=True, verbose_name=_("Catégorie d'accueil / ERP"))
erp_type = models.CharField(max_length=10, choices=ERP_TYPE_CHOICES, null=True, blank=True, verbose_name=_("Type ERP")) erp_type = models.CharField(max_length=10, choices=ERP_TYPE_CHOICES, null=True, blank=True, verbose_name=_("Type d'établissement / ERP"))
max_capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Jauge maximale d'accueil du public")) max_capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Jauge maximale d'accueil du public"))
# Diagnostics & Énergie # Diagnostics & Énergie
dpe_rating = models.CharField(max_length=2, choices=DPE_RATING_CHOICES, null=True, blank=True, verbose_name=_("Diagnostic de Performance Énergétique (DPE)")) dpe_rating = models.CharField(max_length=2, choices=DPE_RATING_CHOICES, null=True, blank=True, verbose_name=_("Performance Énergétique (PEB / DPE / EPC)"))
ges_rating = models.CharField(max_length=2, choices=DPE_RATING_CHOICES, null=True, blank=True, verbose_name=_("Émission de Gaz à Effet de Serre (GES)")) ges_rating = models.CharField(max_length=2, choices=DPE_RATING_CHOICES, null=True, blank=True, verbose_name=_("Émissions de Carbone & GES"))
has_asbestos_diag = models.BooleanField(default=False, verbose_name=_("Diagnostic Amiante réalisé")) has_asbestos_diag = models.BooleanField(default=False, verbose_name=_("Diagnostic Amiante réalisé"))
has_lead_diag = models.BooleanField(default=False, verbose_name=_("Diagnostic Plomb réalisé")) has_lead_diag = models.BooleanField(default=False, verbose_name=_("Diagnostic Plomb réalisé"))
pmr_accessible = models.BooleanField(default=False, verbose_name=_("Accessible PMR")) pmr_accessible = models.BooleanField(default=False, verbose_name=_("Accessible PMR"))

View file

@ -36,20 +36,20 @@
<span class="badge bg-primary-subtle text-primary border">{{ building.get_usage_type_display }}</span> <span class="badge bg-primary-subtle text-primary border">{{ building.get_usage_type_display }}</span>
<span class="badge bg-success-subtle text-success border">{{ building.get_building_status_display }}</span> <span class="badge bg-success-subtle text-success border">{{ building.get_building_status_display }}</span>
{% if building.is_erp %} {% if building.is_erp %}
<span class="badge bg-info-subtle text-info border">ERP (Catégorie {{ building.erp_category|default:"-" }})</span> <span class="badge bg-info-subtle text-info border">{{ regional_terms.public_access_short }} (Cat. {{ building.erp_category|default:"-" }})</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-md-4 text-md-end mt-3 mt-md-0"> <div class="col-md-4 text-md-end mt-3 mt-md-0">
{% if building.dpe_rating %} {% if building.dpe_rating %}
<div class="d-inline-block text-center me-3"> <div class="d-inline-block text-center me-3">
<span class="d-block text-muted small">DPE</span> <span class="d-block text-muted small">{{ regional_terms.energy_diag_short }}</span>
<span class="badge bg-success fs-5 px-3 py-2">Classe {{ building.dpe_rating }}</span> <span class="badge bg-success fs-5 px-3 py-2">Classe {{ building.dpe_rating }}</span>
</div> </div>
{% endif %} {% endif %}
{% if building.ges_rating %} {% if building.ges_rating %}
<div class="d-inline-block text-center"> <div class="d-inline-block text-center">
<span class="d-block text-muted small">GES</span> <span class="d-block text-muted small">{{ regional_terms.carbon_diag_short }}</span>
<span class="badge bg-primary fs-5 px-3 py-2">Classe {{ building.ges_rating }}</span> <span class="badge bg-primary fs-5 px-3 py-2">Classe {{ building.ges_rating }}</span>
</div> </div>
{% endif %} {% endif %}
@ -100,7 +100,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow-sm h-100"> <div class="card shadow-sm h-100">
<div class="card-header bg-light fw-bold"><i class="bi bi-rulers me-2"></i>{% translate "Caractéristiques physiques & ERP" %}</div> <div class="card-header bg-light fw-bold"><i class="bi bi-rulers me-2"></i>{% translate "Caractéristiques physiques &" %} {{ regional_terms.public_access_short }}</div>
<div class="card-body"> <div class="card-body">
<table class="table table-borderless mb-0"> <table class="table table-borderless mb-0">
<tr> <tr>
@ -116,8 +116,8 @@
<td>{{ building.usable_surface_m2|default:"-" }} m² / {{ building.total_surface_m2|default:"-" }} m²</td> <td>{{ building.usable_surface_m2|default:"-" }} m² / {{ building.total_surface_m2|default:"-" }} m²</td>
</tr> </tr>
<tr> <tr>
<th class="text-muted">{% translate "Jauge ERP" %} :</th> <th class="text-muted">{{ regional_terms.public_access_category }} :</th>
<td>{{ building.max_capacity|default:"-" }} personnes</td> <td>{{ building.erp_category|default:"-" }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-muted">{% translate "Accessibilité PMR" %} :</th> <th class="text-muted">{% translate "Accessibilité PMR" %} :</th>

View file

@ -152,4 +152,24 @@ def organization_info(request):
'map_ortho_wms_url': getattr(settings, 'MAP_ORTHO_WMS_URL', ''), 'map_ortho_wms_url': getattr(settings, 'MAP_ORTHO_WMS_URL', ''),
'map_ortho_wms_name': getattr(settings, 'MAP_ORTHO_WMS_NAME', ''), 'map_ortho_wms_name': getattr(settings, 'MAP_ORTHO_WMS_NAME', ''),
'map_ortho_wms_attribution': getattr(settings, 'MAP_ORTHO_WMS_ATTRIBUTION', ''), '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),
} }

View file

@ -0,0 +1,18 @@
# Generated by Django 6.0.7 on 2026-07-26 17:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('common', '0004_initial'),
]
operations = [
migrations.AddField(
model_name='userconfig',
name='regional_standard',
field=models.CharField(choices=[('BE', 'Belgique (PEB, Maison Communale / Hôtel de Ville, Sécurité PMR)'), ('FR', 'France (DPE, GES, Mairie, Normes ERP)'), ('INT', 'International / Générique')], default='BE', help_text='Définit la terminologie et les normes réglementaires adaptées à la région.', max_length=10, verbose_name='Norme régionale & vocabulaire'),
),
]

View file

@ -110,6 +110,18 @@ class UserConfig(models.Model):
help_text="Limite les raccourcis et les compteurs de la page d'accueil aux contrats pour lesquels l'utilisateur dispose de permissions explicites.", help_text="Limite les raccourcis et les compteurs de la page d'accueil aux contrats pour lesquels l'utilisateur dispose de permissions explicites.",
) )
generic_email = models.EmailField(blank=True, null=True) # Email générique de l'utilisateur generic_email = models.EmailField(blank=True, null=True) # Email générique de l'utilisateur
REGIONAL_STANDARD_CHOICES = [
('BE', 'Belgique (PEB, Maison Communale / Hôtel de Ville, Sécurité PMR)'),
('FR', 'France (DPE, GES, Mairie, Normes ERP)'),
('INT', 'International / Générique'),
]
regional_standard = models.CharField(
max_length=10,
choices=REGIONAL_STANDARD_CHOICES,
default='BE',
verbose_name="Norme régionale & vocabulaire",
help_text="Définit la terminologie et les normes réglementaires adaptées à la région.",
)
hide_intervention_map_by_default = models.BooleanField( hide_intervention_map_by_default = models.BooleanField(
default=False, default=False,
verbose_name="Masquer la carte des interventions par défaut", verbose_name="Masquer la carte des interventions par défaut",

View file

@ -116,4 +116,49 @@ def get_short_name(user):
first = getattr(user, "first_name", "") or "" first = getattr(user, "first_name", "") or ""
last = getattr(user, "last_name", "") or "" last = getattr(user, "last_name", "") or ""
initial = (last[0].upper() + ".") if last else "" initial = (last[0].upper() + ".") if last else ""
return f"{first} {initial}".strip() return f"{first} {initial}".strip()
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'])

View file

@ -38,6 +38,7 @@ SECRET_KEY = getenv("SECRET_KEY")
DEBUG = getenv("DEBUG") == "True" DEBUG = getenv("DEBUG") == "True"
ROOT_URL = getenv("ROOT_URL", "http://localhost:8000") ROOT_URL = getenv("ROOT_URL", "http://localhost:8000")
REGIONAL_STANDARD = getenv("REGIONAL_STANDARD", "BE")
ALLOWED_HOSTS = getenv("ALLOWED_HOSTS").split(',') ALLOWED_HOSTS = getenv("ALLOWED_HOSTS").split(',')
CSRF_TRUSTED_ORIGINS = getenv("CSRF_TRUSTED_ORIGINS").split(',') CSRF_TRUSTED_ORIGINS = getenv("CSRF_TRUSTED_ORIGINS").split(',')
@ -111,6 +112,7 @@ TEMPLATES = [
'common.context_processors.current_app_view', 'common.context_processors.current_app_view',
'common.context_processors.environment_info', 'common.context_processors.environment_info',
'common.context_processors.organization_info', 'common.context_processors.organization_info',
'common.context_processors.regional_terminology',
'notifications.context_processors.unread_notifications_count', 'notifications.context_processors.unread_notifications_count',
], ],
}, },