117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
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'])
|