Ajout du suivi des visites : modèle Visit, middleware de tracking, mises à jour des vues et du tableau de bord statistiques.
This commit is contained in:
parent
bec74976ba
commit
6e8a2bc287
7 changed files with 286 additions and 5 deletions
117
core/middleware.py
Normal file
117
core/middleware.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
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'])
|
||||
Loading…
Add table
Add a link
Reference in a new issue