Modèle de données applicatif
Cette page explique comment le MCD de référence décrit dans Référence du MCD et du schéma PostgreSQL est implémenté dans le modèle Django. Le MCD donne la vision fonctionnelle de la base ; l’implémentation Django l’adapte aux besoins concrets de l’application, aux migrations ORM, à l’authentification Django et aux tests.
Positionnement du MCD
Le MCD fourni est une bonne base de conception. Il décrit les grands domaines du projet :
utilisateurs, profils, rôles et moyens d’authentification ;
consentements et journal de sécurité ;
stations, bornes, emplacements, devices ESP32 et capteurs ;
réservations, sessions de charge et télémétrie ;
alertes, techniciens et maintenances ;
pistes RGPD et performance avec vues, anonymisation, TimescaleDB et index.
Il ne doit cependant pas être appliqué tel quel comme script SQL dans le projet.
La source opérationnelle de la base est le modèle Django situé dans
backend/api/models.py et ses migrations dans backend/api/migrations/. La
référence MCD/PostgreSQL reste placée avant cette page dans le sommaire afin de
présenter d’abord la cible fonctionnelle, puis les choix d’implémentation.
Cette décision évite de maintenir deux schémas concurrents : un schéma SQL manuel et un schéma généré par Django. Toute évolution structurelle doit passer par une migration Django afin de rester versionnée, testable et reproductible dans Docker, en CI et sur les postes de développement.
Choix d’implémentation
Valeurs métier fermées
Les cycles de vie structurants sont déclarés dans backend/api/models.py avec
des classes TextChoices. Cette couche ferme les valeurs acceptées par les
serializers Django REST Framework et par les formulaires d’administration, tout
en conservant un stockage simple en VARCHAR.
Les migrations ajoutent aussi des contraintes CHECK sur ces champs. La
base refuse donc une valeur hors catalogue même si elle arrive par un script ou
une écriture directe. Une migration de normalisation précède ces contraintes
pour ramener les anciennes valeurs libres vers les valeurs métier retenues.
Les champs concernés sont notamment :
Utilisateur.statut:actif,suspendu,archive;Station.statut:active,maintenance,inactive;Emplacement.statut:disponible,reserve,occupe,maintenance;Emplacement.lock_state:verrouille,deverrouille,bloque;Reservation.auth_choice:rfid,qr,code;Reservation.statut:en_attente,confirmee,active,annulee,terminee;Session.charge_state:reserved,access_validated,waiting_plug,charging,stopping,stopped,error;Device.status:provisioned,online,idle,offline,charging,error;Alerte.status:nouvelle,active,prise_en_charge,resolue,closed,fermee;Maintenance.status:planifiee,en_cours,realisee,annulee.
Les types de capteurs, types d’alertes et types d’intervention restent plus ouverts. Ils dépendent du matériel, du firmware et de l’exploitation terrain ; les figer trop tôt rendrait l’intégration ESP32 moins souple.
Les champs Device.last_command_* conservent le dernier ACK MQTT reçu pour un
ESP32 : identifiant de commande, action, statut, message et horodatage. Ils
servent à l’exploitation et à la supervision, sans remplacer Device.status
ni Device.last_seen.
Le modèle DeviceCommandLog conserve l’historique récent des ACK de
commandes ESP32. Il rattache l’action au device, à la session de charge lorsque
le firmware renvoie session_id, au command_id et au payload brut reçu.
Ce journal sert à auditer les ordres START, STOP, LOCK et
UNLOCK sans dépendre uniquement du dernier état écrasé sur Device.
UUID
Le MCD prévoit CREATE EXTENSION "uuid-ossp" et des valeurs par défaut
uuid_generate_v4(). Dans le projet, les UUID sont générés côté Django avec
uuid.uuid4 sur les champs UUIDField.
Ce choix garde l’application compatible avec l’ORM et les tests SQLite. Il rend
l’extension uuid-ossp non indispensable pour la version actuelle.
Utilisateur Django
Le MCD décrit une table utilisateur autonome avec email,
password_hash, statut, created_at, last_login et
failed_attempts.
Le projet utilise un modèle Utilisateur personnalisé qui hérite de
AbstractBaseUser et PermissionsMixin. Cela ajoute les conventions Django
nécessaires : mot de passe géré par Django, permissions, groupes, is_staff,
is_active et date_joined.
Le pseudo est l’identifiant principal de connexion, et l’email reste unique. Cette structure permet d’utiliser l’administration Django, les permissions DRF, les jetons JWT et les tests d’authentification sans recréer toute la mécanique de sécurité.
Profil utilisateur
Le modèle ProfilUtilisateur reprend bien l’idée du MCD : les données
personnelles comme le nom et le téléphone sont séparées du compte
d’authentification.
La relation est un OneToOneField vers Utilisateur avec suppression en
cascade. Si le compte est supprimé, le profil associé disparaît aussi.
Rôles
Les tables role et utilisateur_role existent dans Django. Elles
permettent de conserver une représentation métier simple des rôles, en plus des
permissions Django natives.
La table de liaison garde la date d’affectation avec assigned_at et impose
l’unicité du couple utilisateur-rôle.
Authentification multi-moyens
Le MCD prévoit auth_type et auth_method. Le projet implémente ces
concepts avec TypeAuth et Authentification.
Authentification stocke le secret sous la colonne SQL secret_hash via le
champ Django hash_valeur. Le modèle ajoute aussi :
display_valuepour afficher une valeur masquee ou lisible quand c’est acceptable ;metadatapour stocker les informations nécessaires aux QR codes, codes temporaires et associations RFID ;expirationetis_activepour gérer révocation et durée de validité.
Cette structure est plus adaptée au projet que le MCD minimal, car elle couvre les QR codes, les codes temporaires et les badges RFID persistants.
RGPD et journalisation
Les tables consentement et journal_securite sont implémentées.
JournalSecurite utilise ON DELETE SET NULL plutôt qu’une cascade stricte
sur l’utilisateur. Ce choix permet de conserver une trace d’événements sensibles
même si le compte utilisateur est supprimé ou anonymisé.
La table consentement sert à tracer les choix explicites de l’utilisateur
lorsqu’un traitement repose réellement sur le consentement. Elle est pertinente
pour les traitements optionnels : acceptation des CGU, prise de connaissance de
la politique de confidentialité, notifications non indispensables ou mesure
d’audience.
Elle ne doit pas être utilisée comme justification générique pour tous les traitements. Les données nécessaires à la création du compte, à la réservation, à la sécurité ou à la maintenance du service relèvent plutôt du contrat, de l’intérêt légitime ou d’une obligation légale. Cette distinction renforce la crédibilité RGPD du projet : le consentement reste libre, spécifique et révocable.
La table est utilisée dès l’inscription. Le formulaire demande explicitement l’acceptation des CGU, la prise de connaissance de la politique de confidentialité et le choix relatif aux notifications optionnelles. L’API crée ensuite trois lignes historisées :
cgu/accepte;politique_confidentialite/accepte;notifications/accepteounotifications/refuseselon le choix de l’utilisateur.
Ce choix donne une preuve datée des décisions prises à la création du compte,
tout en séparant clairement les consentements optionnels des traitements
nécessaires au service. Le profil utilisateur expose ensuite les consentements
courants et permet de modifier les notifications optionnelles. Lorsqu’un choix
change, l’API ajoute une nouvelle ligne datée dans consentement au lieu
d’écraser l’historique existant.
Les mécanismes PostgreSQL avancés du MCD, comme l’extension anon, les
SECURITY LABEL et les vues SQL d’export, ne sont pas encore actives. Le
projet gère pour l’instant l’export et la suppression côté API Django.
Stations, bornes et emplacements
Station, Borne et Emplacement décrivent l’infrastructure physique.
Le modèle Django ajoute latitude et longitude sur Station afin de
supporter la carte de réservation. Borne représente une armoire ou un
kiosque physique rattaché à une station. Emplacement garde le numéro, l’état
métier et l’état de cadenas.
La hiérarchie métier est donc : station, borne, emplacement. Une contrainte
d’unicité empêche deux emplacements d’une même borne de porter le même numéro.
Le champ station reste aussi présent sur Emplacement pour conserver des
requêtes simples et une compatibilité avec les flux existants ; il est aligné
automatiquement avec la station de la borne.
Devices ESP32
Device correspond au MCD et représente le contrôleur embarqué d’un
emplacement. La relation avec Emplacement est un OneToOneField : un
emplacement ne doit avoir qu’un device principal dans la version actuelle.
Le modèle distingue l’identité logique et l’identité physique :
slot_id: identifiant MQTT de l’emplacement, par exempleslot1;hardware_mac: adresse MAC Wi-Fi réelle de la carte ESP32 ;ip_address: adresse IP courante, informative car elle peut changer ;firmware_version: version publiée par le firmware ;mac_address: ancien champ conservé temporairement pour compatibilité avec les données et tests déjà existants.
Le backend utilise slot_id pour router et afficher l’emplacement. Les
payloads status, ack et telemetry peuvent mettre à jour
automatiquement firmware_version, hardware_mac et ip_address.
Capteurs et données capteurs
Capteur et CapteurData reprennent le principe du MCD : un device porte
plusieurs capteurs, et chaque mesure horodatée est stockée dans capteur_data.
Les valeurs booléennes issues de l’ESP32, comme door et cable, sont
converties en 1.0 ou 0.0 pour rester compatibles avec valeur de type
flottant. L’API ajoute ensuite une valeur d’affichage lisible dans la supervision
administrateur.
Le MCD cible propose des hypertables TimescaleDB pour capteur_data. Elles ne
sont pas encore activées : la table reste une table Django standard afin de
garder la solution simple et portable.
Réservations
Reservation relie un utilisateur, un emplacement, un moyen
d’authentification et une fenêtre de temps.
Le projet ajoute reference_code et auth_choice afin de gérer le parcours
terminal : code de réservation, QR code, code SMS temporaire ou RFID.
La relation vers Utilisateur est en SET_NULL et non en cascade. Ce choix
évite d’effacer tout l’historique technique d’une réservation si le compte est
supprimé. La donnée personnelle peut disparaître, tandis que l’événement métier
reste exploitable.
Sessions de charge
Le MCD prévoit une table session avec id_user, date_debut,
date_fin, consommation_kwh et etat_batterie.
Le projet implémente ce concept dans la table SQL charging_session. Le
modèle Django s’appelle Session mais sa table n’est pas nommée session,
ce qui évite aussi la confusion avec la notion de session applicative.
La session réelle est rattachée à une réservation et à un emplacement. Elle
stocke start_time et end_time. L’utilisateur se retrouve via
session.reservation.utilisateur.
charge_state formalise le cycle technique de la charge côté backend :
reserved lorsque la session existe sans commande validée, access_validated
après authentification, waiting_plug après ACK START, charging dès
que l’ESP32 confirme la charge ou publie de la télémétrie active, stopping
après ACK STOP ou retour idle avant clôture backend, stopped lorsque
end_time est renseigné, et error si l’ESP32 remonte un état de défaut.
Ce champ complète end_time : il évite les doubles START et rend visibles
les sessions arrêtées physiquement mais pas encore clôturées côté backend.
La consommation instantanee ou cumulee n’est pas stockee directement dans
charging_session. Elle est portee par session_telemetry avec
energie_kwh. Cette séparation garde la session comme un objet métier et la
télémétrie comme une série temporelle.
Télémétrie de session
SessionTelemetry correspond au MCD : il stocke courant, tension, energie et
timestamp pour une session active.
La table est indexée par session et timestamp. Elle pourra devenir une hypertable TimescaleDB si la fréquence de mesures augmente fortement.
Alertes
Alerte correspond au MCD mais adapte les suppressions : les liens vers
CapteurData et Session sont en SET_NULL. Cela permet de garder une
alerte historique même si la mesure ou la session associée n’est plus disponible.
Le champ timestamp est stocké dans la colonne SQL date pour rester proche
du MCD tout en gardant un nom Django plus explicite.
Les champs first_seen et last_seen encadrent la durée de l’anomalie. Les
champs notified_at et notification_count permettent de tracer les emails
envoyés et d’appliquer un délai de relance sans dupliquer les alertes.
Le workflow technicien est porté directement par l’alerte. assigned_technician
identifie l’intervenant responsable, acknowledged_by et acknowledged_at
tracent la prise en charge, puis resolved_by, resolved_at et
resolution_note documentent la clôture opérationnelle. Les alertes restent
filtrées par station via TechnicienStation afin de respecter le principe du
moindre privilège.
Le cycle actuel est géré côté application :
nouvelleà la création ;activesi la même anomalie continue ;prise_en_chargelorsqu’un technicien accepte l’incident ;resoluequand la télémétrie revient à la normale.
Maintenance
Technicien et Maintenance reprennent le MCD. Le projet ajoute la
possibilité de lier un technicien à un compte Utilisateur. Cela permet à un
technicien de se connecter et de consulter ses interventions.
TechnicienStation applique le principe du moindre privilège côté métier :
un administrateur affecte explicitement un technicien à une ou plusieurs
stations. La supervision technicien ne lit ensuite que les devices, alertes,
mesures et commandes des stations actives dans ce périmètre. Grafana reste un
outil de supervision technique global ou administrateur ; l’accès terrain
quotidien des techniciens passe par l’application, où le filtrage est porté par
l’API Django.
Performance et TimescaleDB
Le conteneur db utilise l’image timescale/timescaledb:latest-pg15. Une
migration active l’extension timescaledb quand elle est disponible sur le
serveur PostgreSQL. Sur SQLite ou sur un PostgreSQL sans extension installée, la
migration reste sans effet pour préserver la portabilité du projet.
Dans l’état actuel, le projet utilise TimescaleDB comme un PostgreSQL compatible.
Les tables capteur_data et session_telemetry ne sont pas encore des
hypertables. Ce choix est volontaire : elles possèdent aujourd’hui une clé
primaire UUID simple, alors qu’une hypertable TimescaleDB impose que les
contraintes uniques incluent la colonne temporelle de partitionnement. Le passage
en hypertables doit donc être traité comme une refonte de schéma ciblée, pas
comme une simple commande SQL ajoutée en fin de migration.
Les index déjà présents couvrent les requêtes les plus importantes :
emplacements par borne, station et statut opérationnel ;
réservations par utilisateur, statut, emplacement et dates ;
sessions par emplacement et dates ;
devices par dernière activité ;
télémétrie par session et timestamp ;
données capteurs par capteur et timestamp ;
alertes par statut, session et timestamp ;
alertes par technicien assigné et statut ;
affectations technicien-station par technicien, station et statut actif ;
maintenances par technicien, station, statut et date planifiée.
Une migration complémentaire ajoute aussi des index orientés exploitation :
capteur(type, id_device)pour retrouver rapidement les séries par type de capteur et par device ;capteur_data(timestamp DESC)pour les dernières mesures et les fenêtres temporelles Grafana ;session_telemetry(timestamp DESC)pour les analyses temporelles de session ;alerte(date DESC)pour les alertes récentes affichées en supervision.
Une seconde migration complète cette logique avec des index métier plus proches des écrans d’exploitation et des filtres API :
emplacement(statut_cadenas, etat)pour identifier rapidement les points de charge selon l’état du cadenas et l’état opérationnel ;reservation(date_debut, date_fin)pour les recherches de disponibilité sur une fenêtre de temps ;charging_session(start_time, end_time)pour les sessions ouvertes, fermées ou analysées par période ;charging_session(charge_state, end_time)pour identifier les sessions en charge, en arrêt ou en erreur ;device(status, last_seen)pour détecter les équipements indisponibles ou silencieux ;alerte(niveau_severite, date)pour prioriser les incidents ;alerte(type, date)pour suivre les familles d’anomalies dans le temps ;alerte(assigned_technician_id, status)pour afficher rapidement les incidents ouverts par technicien.
Ces index ne changent pas le modèle fonctionnel. Ils rendent les parcours critiques plus prévisibles lorsque le volume augmente : supervision temps réel, historique d’exploitation, diagnostic terrain et tableaux de bord.
Les optimisations du MCD cible, comme create_hypertable, compression,
retention et vues matérialisées, restent pertinentes pour une phase production
ou une simulation à volume élevé. Elles nécessitent toutefois de revoir la clé
primaire des tables de télémétrie ou d’introduire des tables spécialisées pour
les séries temporelles.
Sécurité base de données
Le MCD propose Row Level Security sur session et reservation.
Dans le projet actuel, l’isolation est portée par l’API Django : les endpoints
utilisateur filtrent toujours avec request.user et les endpoints
administrateur exigent les droits staff.
Activer la RLS dans PostgreSQL serait possible, mais il faut alors garantir que
app.current_user_id est défini et nettoyé correctement pour chaque requête.
Comme Django réutilise ses connexions, une mauvaise gestion de ce contexte peut
créer des erreurs difficiles à diagnostiquer. La RLS est donc considérée comme
une évolution de durcissement, pas comme un prérequis de la version actuelle.
RGPD cible
Le MCD propose plusieurs mécanismes SQL :
vue
session_anonyme;vue
export_user_data;fonction
delete_user_rgpd;extension
anonet masquage dynamique ;hachage de l’identifiant utilisateur dans les exports statistiques.
Le projet implémente déjà une partie de ce besoin côté API :
profil séparé du compte ;
export de compte via endpoint authentifié ;
suppression depuis le profil ;
journalisation des actions sensibles ;
conservation possible d’événements techniques sans lien personnel direct.
Les vues SQL et l’anonymisation PostgreSQL sont traitées progressivement. La vue
session_anonyme est toujours disponible sur PostgreSQL. La migration
0027_optional_postgresql_anonymizer active aussi l’extension anon et ses
SECURITY LABEL quand le serveur PostgreSQL fournit PostgreSQL Anonymizer.
Cette migration est volontairement optionnelle : elle ne casse pas SQLite, ni une
image TimescaleDB qui ne contient pas encore l’extension système.
Vue session_anonyme
La première vue SQL ajoutée côté PostgreSQL est session_anonyme. Elle expose
un jeu de données dédié aux statistiques de sessions sans publier directement
l’identifiant utilisateur.
La vue s’appuie sur les tables reelles de l’application :
charging_sessionpour les dates de début et de fin ;reservationpour retrouver l’utilisateur lié à la session ;session_telemetrypour calculer la consommation observée.
Le champ user_hash est produit avec md5(id_user::text) lorsque la session
est encore rattachée à un utilisateur. Si le lien utilisateur a disparu, la vue
utilise l’identifiant de session comme valeur de repli afin de conserver une
ligne statistique sans recréer de donnée personnelle.
Cette vue ne remplace pas les contrôles d’accès applicatifs. Elle sert de base à des rapports d’usage anonymisés ou pseudonymisés, par exemple pour analyser le nombre de sessions, les plages horaires et les consommations sans exposer email, pseudo, nom ou téléphone.
La migration est volontairement limitee a PostgreSQL. Les tests unitaires du projet utilisent SQLite ; la migration devient donc un no-op sur SQLite afin de garder la suite rapide et portable.
Extension PostgreSQL Anonymizer
Quand l’extension anon est disponible dans PostgreSQL, la migration
0027_optional_postgresql_anonymizer pose des règles de masquage sur les
colonnes personnelles suivantes :
utilisateur.pseudoetutilisateur.email;profil_utilisateur.nometprofil_utilisateur.telephone;journal_securite.ip;technicien.nom,technicien.emailettechnicien.telephone.
L’objectif n’est pas de masquer les données dans l’application de production, mais de permettre des exports, jeux de tests ou accès SQL restreints sans exposer directement les données personnelles. L’installation de l’extension reste une décision d’infrastructure : l’image TimescaleDB standard du projet ne garantit pas sa présence.
Synthèse
Le MCD ne doit pas être appliqué tel quel dans le projet. Il doit rester une référence de conception et une cible d’évolution.
L’implémentation Django est le modèle opérationnel actuel parce qu’elle :
respecte les besoins fonctionnels réels de l’application ;
intègre l’authentification et les permissions Django ;
garde les migrations versionnées ;
reste testable avec pytest et SQLite ;
évite un double schéma SQL/ORM ;
prépare les évolutions PostgreSQL et TimescaleDB sans les imposer trop tôt.
La trajectoire reste progressive :
conserver le modèle Django comme source de vérité applicative ;
ajouter les optimisations SQL lorsqu’elles répondent à un usage mesuré ou clairement identifié ;
enrichir les vues SQL pour les exports ou statistiques ;
activer les hypertables TimescaleDB quand le volume de télémétrie augmente ;
activer l’image PostgreSQL/TimescaleDB incluant
anonlorsque des dumps anonymisés ou des rôles SQL masqués deviennent nécessaires ;envisager RLS, compression et rétention native pour une version durcie.