import uuid from urllib.parse import urlparse from django.utils import timezone from .models import Visit class VisitTrackingMiddleware: """Middleware très léger pour enregistrer des statistiques de visites. - Assigne un cookie visiteur persistant (vid) si absent - Enregistre/Met à jour une ligne Visit par visiteur et par jour - Capture la source (UTM/referrer) et le pays si disponible via headers """ COOKIE_NAME = 'vid' COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 2 # 2 ans def __init__(self, get_response): self.get_response = get_response def __call__(self, request): vid = request.COOKIES.get(self.COOKIE_NAME) if not vid: vid = uuid.uuid4().hex request.visitor_id = vid # Enregistrer la visite (agrégée par jour) try: self._track(request, vid) except Exception: # On ne casse jamais la requête pour des stats pass response = self.get_response(request) # S'assurer que le cookie est posé if request.COOKIES.get(self.COOKIE_NAME) != vid: response.set_cookie( self.COOKIE_NAME, vid, max_age=self.COOKIE_MAX_AGE, httponly=True, samesite='Lax', ) return response def _track(self, request, vid): # On ignore l'admin et les assets statiques path = request.path if path.startswith('/admin') or path.startswith('/static') or path.startswith('/staticfiles'): return now = timezone.now() date = now.date() ref = request.META.get('HTTP_REFERER', '')[:512] utm_source = request.GET.get('utm_source', '')[:100] utm_medium = request.GET.get('utm_medium', '')[:100] utm_campaign = request.GET.get('utm_campaign', '')[:150] # Déterminer source source = '' if utm_source: source = utm_source elif ref: try: netloc = urlparse(ref).netloc source = netloc except Exception: source = ref[:150] # Déterminer pays via en-têtes si fournis par proxy/CDN country = ( request.META.get('HTTP_CF_IPCOUNTRY') or request.META.get('HTTP_X_APPENGINE_COUNTRY') or request.META.get('HTTP_X_COUNTRY') or '' )[:64] became_user_at = now if request.user.is_authenticated else None visit, created = Visit.objects.get_or_create( visitor_id=vid, date=date, defaults={ 'path': path[:512], 'referrer': ref, 'utm_source': utm_source, 'utm_medium': utm_medium, 'utm_campaign': utm_campaign, 'source': source, 'country': country, 'first_seen': now, 'last_seen': now, 'user': request.user if request.user.is_authenticated else None, 'became_user_at': became_user_at, } ) if not created: # Mise à jour basique dirty = False if not visit.source and source: visit.source = source dirty = True if not visit.country and country: visit.country = country dirty = True if request.user.is_authenticated and visit.user_id is None: visit.user = request.user dirty = True # Marquer la conversion si pas encore définie if visit.became_user_at is None: visit.became_user_at = now visit.last_seen = now dirty = True if dirty: visit.save(update_fields=['source', 'country', 'user', 'became_user_at', 'last_seen'])