""" Modèles pour le monitoring de l'utilisation de l'application. Ce module fournit des modèles pour tracker: - Les visites de pages (PageView) - Les sessions utilisateurs (UserSession) - Les résumés quotidiens (DailyPageViewSummary) - Les statistiques d'utilisation """ from django.db import models, transaction from django.contrib.auth.models import User from django.conf import settings from django.utils import timezone class PageView(models.Model): """ Enregistre chaque visite de page par un utilisateur. Permet de tracker les pages visitées et la fréquence d'utilisation. """ user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Utilisateur", related_name="page_views" ) session_key = models.CharField( max_length=40, db_index=True, verbose_name="Clé de session" ) path = models.CharField( max_length=2048, verbose_name="Chemin URL", db_index=True ) full_url = models.CharField( max_length=4096, blank=True, null=True, verbose_name="URL complète" ) method = models.CharField( max_length=10, default='GET', verbose_name="Méthode HTTP" ) timestamp = models.DateTimeField( auto_now_add=True, verbose_name="Date et heure", db_index=True ) ip_address = models.GenericIPAddressField( null=True, blank=True, verbose_name="Adresse IP" ) user_agent = models.TextField( blank=True, null=True, verbose_name="User Agent" ) referer = models.CharField( max_length=2048, blank=True, null=True, verbose_name="Page précédente" ) response_time_ms = models.IntegerField( null=True, blank=True, verbose_name="Temps de réponse (ms)", help_text="Durée de traitement de la requête en millisecondes" ) class Meta: verbose_name = "Vue de page" verbose_name_plural = "Vues de pages" ordering = ["-timestamp"] indexes = [ models.Index(fields=['user', 'timestamp']), models.Index(fields=['session_key', 'timestamp']), models.Index(fields=['path', 'timestamp']), models.Index(fields=['timestamp']), ] def __str__(self): user_str = self.user.username if self.user else "Anonyme" return f"{user_str} - {self.path} - {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}" class UserSession(models.Model): """ Enrichit les informations de session Django avec des métadonnées supplémentaires. Permet de tracker les sessions actives et l'historique de connexion. """ user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Utilisateur", related_name="analytics_sessions" ) session_key = models.CharField( max_length=40, unique=True, db_index=True, verbose_name="Clé de session" ) login_time = models.DateTimeField( verbose_name="Heure de connexion", db_index=True ) last_activity = models.DateTimeField( verbose_name="Dernière activité", db_index=True ) logout_time = models.DateTimeField( null=True, blank=True, verbose_name="Heure de déconnexion" ) ip_address = models.GenericIPAddressField( null=True, blank=True, verbose_name="Adresse IP" ) user_agent = models.TextField( blank=True, null=True, verbose_name="User Agent" ) page_views_count = models.IntegerField( default=0, verbose_name="Nombre de pages vues" ) is_active = models.BooleanField( default=True, verbose_name="Session active", db_index=True ) class Meta: verbose_name = "Session utilisateur" verbose_name_plural = "Sessions utilisateurs" ordering = ["-login_time"] indexes = [ models.Index(fields=['user', 'login_time']), models.Index(fields=['is_active', 'last_activity']), ] def __str__(self): status = "Active" if self.is_active else "Terminée" return f"{self.user.username} - {self.login_time.strftime('%Y-%m-%d %H:%M')} - {status}" @property def duration(self): """Retourne la durée de la session.""" end_time = self.logout_time if self.logout_time else self.last_activity return end_time - self.login_time @property def duration_seconds(self): """Retourne la durée de la session en secondes.""" return self.duration.total_seconds() def mark_inactive(self): """Marque la session comme inactive de manière atomique.""" with transaction.atomic(): # Utiliser select_for_update pour éviter les race conditions session = UserSession.objects.select_for_update().filter(pk=self.pk).first() if session and session.is_active: session.is_active = False if not session.logout_time: session.logout_time = timezone.now() session.save(update_fields=['is_active', 'logout_time']) # Mettre à jour l'instance actuelle self.is_active = session.is_active self.logout_time = session.logout_time class DailyPageViewSummary(models.Model): """ Résumé quotidien des statistiques de pages vues. Ce modèle stocke les agrégats par jour pour conserver des statistiques d'utilisation même après la purge des données détaillées de PageView. """ date = models.DateField( unique=True, db_index=True, verbose_name="Date" ) total_page_views = models.IntegerField( default=0, verbose_name="Total pages vues" ) unique_users = models.IntegerField( default=0, verbose_name="Utilisateurs uniques" ) unique_sessions = models.IntegerField( default=0, verbose_name="Sessions uniques" ) unique_ips = models.IntegerField( default=0, verbose_name="IPs uniques" ) avg_response_time_ms = models.FloatField( null=True, blank=True, verbose_name="Temps de réponse moyen (ms)" ) max_response_time_ms = models.IntegerField( null=True, blank=True, verbose_name="Temps de réponse max (ms)" ) top_paths = models.JSONField( default=dict, blank=True, verbose_name="Top pages visitées", help_text="Les 20 pages les plus visitées avec leur nombre de vues" ) top_users = models.JSONField( default=dict, blank=True, verbose_name="Top utilisateurs", help_text="Les 20 utilisateurs les plus actifs avec leur nombre de vues" ) method_counts = models.JSONField( default=dict, blank=True, verbose_name="Répartition par méthode HTTP", help_text="Nombre de requêtes par méthode HTTP (GET, POST, ...)" ) total_sessions_started = models.IntegerField( default=0, verbose_name="Sessions démarrées" ) created_at = models.DateTimeField( auto_now_add=True, verbose_name="Créé le" ) updated_at = models.DateTimeField( auto_now=True, verbose_name="Mis à jour le" ) class Meta: verbose_name = "Résumé quotidien" verbose_name_plural = "Résumés quotidiens" ordering = ["-date"] def __str__(self): return f"{self.date} — {self.total_page_views} vues, {self.unique_users} utilisateurs"