""" Middleware pour le tracking des visites de pages et des sessions utilisateurs. Ce middleware enregistre automatiquement les visites de pages et met à jour les informations de session pour le monitoring de l'application. """ import time import logging from django.utils import timezone from django.contrib.auth.signals import user_logged_in, user_logged_out # Import tardif pour éviter les dépendances circulaires # Ces imports sont effectués au niveau du module mais après la configuration Django from analytics.models import PageView, UserSession class AnalyticsMiddleware: """ Middleware qui capture les visites de pages pour le monitoring. Pour chaque requête, enregistre: - La page visitée - L'utilisateur (si authentifié) - La session - Le timestamp - Les métadonnées (IP, user agent, etc.) """ # URLs à ignorer pour ne pas polluer les statistiques IGNORED_PATHS = [ '/static/', '/media/', '/private-media/', '/favicon.ico', '/__debug__/', '/admin/jsi18n/', # Traductions JS de l'admin ] # Méthodes HTTP à tracker (ignorer les HEAD, OPTIONS, etc.) TRACKED_METHODS = ['GET', 'POST'] def __init__(self, get_response): self.get_response = get_response # Connecter les signaux pour tracker les connexions/déconnexions user_logged_in.connect(self._on_user_login) user_logged_out.connect(self._on_user_logout) def __call__(self, request): # Vérifier si cette requête doit être trackée if not self._should_track(request): return self.get_response(request) # Enregistrer le temps de début start_time = time.time() # Traiter la requête response = self.get_response(request) # Calculer le temps de réponse response_time_ms = int((time.time() - start_time) * 1000) # Enregistrer la visite de page (de manière asynchrone pour ne pas ralentir la réponse) try: self._record_page_view(request, response_time_ms) except Exception as e: # En cas d'erreur, ne pas faire planter la requête logger = logging.getLogger('analytics') logger.error(f"Erreur lors de l'enregistrement de la page view: {e}") return response def _should_track(self, request): """Détermine si la requête doit être trackée.""" # Ignorer certains chemins for path in self.IGNORED_PATHS: if request.path.startswith(path): return False # Ignorer certaines méthodes HTTP if request.method not in self.TRACKED_METHODS: return False # Ignorer les requêtes AJAX de type polling/refresh if request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest': # On peut choisir de tracker ou non les requêtes AJAX # Pour l'instant, on les ignore pour ne pas polluer les stats return False return True def _get_client_ip(self, request): """Récupère l'adresse IP du client.""" x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() else: ip = request.META.get('REMOTE_ADDR') return ip def _get_user_agent(self, request): """Récupère le user agent en le tronquant si nécessaire.""" user_agent = request.META.get('HTTP_USER_AGENT') return user_agent[:2000] if user_agent else None def _get_referer(self, request): """Récupère le referer en le tronquant si nécessaire.""" referer = request.META.get('HTTP_REFERER') return referer[:2048] if referer else None def _record_page_view(self, request, response_time_ms): """Enregistre la visite de page dans la base de données.""" # Créer l'enregistrement de page view page_view = PageView.objects.create( user=request.user if request.user.is_authenticated else None, session_key=request.session.session_key or '', path=request.path, full_url=request.build_absolute_uri(), method=request.method, ip_address=self._get_client_ip(request), user_agent=self._get_user_agent(request), referer=self._get_referer(request), response_time_ms=response_time_ms, ) # Mettre à jour la session utilisateur si l'utilisateur est authentifié if request.user.is_authenticated and request.session.session_key: self._update_user_session(request) def _update_user_session(self, request): """Met à jour les informations de session utilisateur.""" session_key = request.session.session_key if not session_key: return try: # Récupérer ou créer la session user_session, created = UserSession.objects.get_or_create( session_key=session_key, defaults={ 'user': request.user, 'login_time': timezone.now(), 'last_activity': timezone.now(), 'ip_address': self._get_client_ip(request), 'user_agent': self._get_user_agent(request), } ) # Mettre à jour l'activité et le compteur de pages vues if not created: user_session.last_activity = timezone.now() user_session.page_views_count += 1 user_session.save(update_fields=['last_activity', 'page_views_count']) except Exception as e: # En cas d'erreur (par exemple, clé de session dupliquée), ignorer logger = logging.getLogger('analytics') logger.warning(f"Erreur lors de la mise à jour de la session: {e}") def _on_user_login(self, sender, request, user, **kwargs): """ Signal handler appelé quand un utilisateur se connecte. Crée une nouvelle session utilisateur. """ # S'assurer que la session a une clé if not request.session.session_key: request.session.create() session_key = request.session.session_key # Créer ou mettre à jour la session UserSession.objects.update_or_create( session_key=session_key, defaults={ 'user': user, 'login_time': timezone.now(), 'last_activity': timezone.now(), 'ip_address': self._get_client_ip(request), 'user_agent': self._get_user_agent(request), 'is_active': True, } ) def _on_user_logout(self, sender, request, user, **kwargs): """ Signal handler appelé quand un utilisateur se déconnecte. Marque la session comme inactive. """ if request.session.session_key: try: user_session = UserSession.objects.get( session_key=request.session.session_key ) user_session.mark_inactive() except UserSession.DoesNotExist: pass