196 lines
7.3 KiB
Python
196 lines
7.3 KiB
Python
"""
|
|
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
|