Ajout de la vue stats_charts avec graphiques détaillés pour les superadministrateurs, mise à jour des templates et des routes associées, et configuration supplémentaire pour l'email backend.
This commit is contained in:
parent
083af6f9d4
commit
7869abf441
4 changed files with 102 additions and 1 deletions
|
|
@ -207,4 +207,9 @@ def get_git_version():
|
||||||
GIT_VERSION = get_git_version()
|
GIT_VERSION = get_git_version()
|
||||||
|
|
||||||
EMAIL_BACKEND = dotenv.get_key('.env', 'EMAIL_BACKEND')
|
EMAIL_BACKEND = dotenv.get_key('.env', 'EMAIL_BACKEND')
|
||||||
DEFAULT_FROM_EMAIL = dotenv.get_key('.env', 'EMAIL_HOST_USER')
|
EMAIL_HOST = dotenv.get_key('.env', 'EMAIL_HOST')
|
||||||
|
EMAIL_PORT = dotenv.get_key('.env', 'EMAIL_PORT')
|
||||||
|
EMAIL_USE_TLS = dotenv.get_key('.env', 'EMAIL_USE_TLS') == 'True'
|
||||||
|
EMAIL_HOST_USER = dotenv.get_key('.env', 'EMAIL_HOST_USER')
|
||||||
|
EMAIL_HOST_PASSWORD = dotenv.get_key('.env', 'EMAIL_HOST_PASSWORD')
|
||||||
|
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
|
|
@ -7,4 +7,5 @@ urlpatterns = [
|
||||||
path('premium/<int:course_id>', views.premium, name='premium'),
|
path('premium/<int:course_id>', views.premium, name='premium'),
|
||||||
# Tableau de bord statistiques (réservé superadministrateurs)
|
# Tableau de bord statistiques (réservé superadministrateurs)
|
||||||
path('dashboard/stats/', views.stats_dashboard, name='stats_dashboard'),
|
path('dashboard/stats/', views.stats_dashboard, name='stats_dashboard'),
|
||||||
|
path('dashboard/stats/charts/', views.stats_charts, name='stats_charts'),
|
||||||
]
|
]
|
||||||
|
|
@ -199,3 +199,97 @@ def stats_dashboard(request):
|
||||||
context['labels_json'] = json.dumps(context['series']['days'])
|
context['labels_json'] = json.dumps(context['series']['days'])
|
||||||
|
|
||||||
return render(request, 'home/stats_dashboard.html', context)
|
return render(request, 'home/stats_dashboard.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
@cache_page(60 * 15)
|
||||||
|
def stats_charts(request):
|
||||||
|
"""Page dédiée aux graphiques (réservée superadmins)."""
|
||||||
|
# Période
|
||||||
|
period_options = [7, 30, 90, 180]
|
||||||
|
try:
|
||||||
|
p = int(request.GET.get('p', 30))
|
||||||
|
except ValueError:
|
||||||
|
p = 30
|
||||||
|
if p not in period_options:
|
||||||
|
p = 30
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
start_dt = now - timezone.timedelta(days=p-1)
|
||||||
|
period_start_date = start_dt.date()
|
||||||
|
period_end_date = now.date()
|
||||||
|
|
||||||
|
# Trafic par jour (visiteurs uniques)
|
||||||
|
visits_qs = (
|
||||||
|
Visit.objects
|
||||||
|
.filter(date__gte=period_start_date, date__lte=period_end_date)
|
||||||
|
.values('date')
|
||||||
|
.annotate(c=Count('visitor_id', distinct=True))
|
||||||
|
.order_by('date')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Conversions par jour (visiteurs devenus utilisateurs)
|
||||||
|
conversions_qs = (
|
||||||
|
Visit.objects
|
||||||
|
.filter(
|
||||||
|
became_user_at__isnull=False,
|
||||||
|
became_user_at__date__gte=period_start_date,
|
||||||
|
became_user_at__date__lte=period_end_date,
|
||||||
|
)
|
||||||
|
.extra(select={'day': "date(became_user_at)"})
|
||||||
|
.values('day')
|
||||||
|
.annotate(c=Count('visitor_id', distinct=True))
|
||||||
|
.order_by('day')
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_series_dict(qs, date_key='date', count_key='c'):
|
||||||
|
counts = {str(item[date_key]): item[count_key] for item in qs}
|
||||||
|
days = []
|
||||||
|
values = []
|
||||||
|
d = period_start_date
|
||||||
|
while d <= period_end_date:
|
||||||
|
key = str(d)
|
||||||
|
days.append(key)
|
||||||
|
values.append(counts.get(key, 0))
|
||||||
|
d += timezone.timedelta(days=1)
|
||||||
|
return days, values
|
||||||
|
|
||||||
|
labels, visitors_series = build_series_dict(visits_qs, date_key='date')
|
||||||
|
_, conversions_series = build_series_dict(conversions_qs, date_key='day')
|
||||||
|
|
||||||
|
# Sources & Pays (sur la période)
|
||||||
|
period_visits = Visit.objects.filter(date__gte=period_start_date, date__lte=period_end_date)
|
||||||
|
top_sources_qs = (
|
||||||
|
period_visits
|
||||||
|
.values('source')
|
||||||
|
.annotate(c=Count('visitor_id', distinct=True))
|
||||||
|
.order_by('-c')[:10]
|
||||||
|
)
|
||||||
|
top_countries_qs = (
|
||||||
|
period_visits
|
||||||
|
.exclude(country='')
|
||||||
|
.values('country')
|
||||||
|
.annotate(c=Count('visitor_id', distinct=True))
|
||||||
|
.order_by('-c')[:10]
|
||||||
|
)
|
||||||
|
|
||||||
|
sources_labels = [(row['source'] or 'Direct/Unknown') for row in top_sources_qs]
|
||||||
|
sources_values = [row['c'] for row in top_sources_qs]
|
||||||
|
countries_labels = [row['country'] for row in top_countries_qs]
|
||||||
|
countries_values = [row['c'] for row in top_countries_qs]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'period_options': period_options,
|
||||||
|
'p': p,
|
||||||
|
'start_date': period_start_date,
|
||||||
|
'end_date': period_end_date,
|
||||||
|
'labels_json': json.dumps(labels),
|
||||||
|
'visitors_series_json': json.dumps(visitors_series),
|
||||||
|
'conversions_series_json': json.dumps(conversions_series),
|
||||||
|
'sources_labels_json': json.dumps(sources_labels),
|
||||||
|
'sources_values_json': json.dumps(sources_values),
|
||||||
|
'countries_labels_json': json.dumps(countries_labels),
|
||||||
|
'countries_values_json': json.dumps(countries_values),
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'home/stats_charts.html', context)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Tableau de bord statistiques</h1>
|
<h1>Tableau de bord statistiques</h1>
|
||||||
|
<p style="margin:8px 0"><a href="{% url 'home:stats_charts' %}">→ Voir la page de graphiques</a></p>
|
||||||
|
|
||||||
<form method="get" class="toolbar">
|
<form method="get" class="toolbar">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue