diff --git a/home/views.py b/home/views.py index 76fba86..22ab337 100644 --- a/home/views.py +++ b/home/views.py @@ -11,6 +11,49 @@ from progression.models import Progression import json from django.http import JsonResponse + +# -------------------- +# Helpers Stats Module +# -------------------- + +def _parse_period(request, default=30, options=None): + """Parse period parameter 'p' from request and compute date range. + + Returns (p, now_dt, start_dt, start_date, end_date) + - now_dt is timezone-aware now + - start_dt is datetime at start of range (inclusive) + - start_date/end_date are date objects for convenient filtering + """ + if options is None: + options = [7, 30, 90, 180] + try: + p = int(request.GET.get('p', default)) + except (TypeError, ValueError): + p = default + if p not in options: + p = default + now_dt = timezone.now() + start_dt = now_dt - timezone.timedelta(days=p - 1) + return p, now_dt, start_dt, start_dt.date(), now_dt.date() + + +def _build_series_for_range(start_date, end_date, qs, date_key='day', count_key='c'): + """Build a continuous daily series from an aggregated queryset. + + qs must yield dicts with date_key (date as string or date) and count_key. + Returns (labels_days, values_counts) + """ + counts = {str(item[date_key]): item[count_key] for item in qs} + days = [] + values = [] + d = start_date + while d <= end_date: + key = str(d) + days.append(key) + values.append(counts.get(key, 0)) + d += timezone.timedelta(days=1) + return days, values + def home(request): courses = Course.objects.order_by('-created_at')[:6] return render(request, 'home.html', {'courses': courses}) @@ -30,19 +73,13 @@ def stats_dashboard(request): # 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) # inclut aujourd'hui + p, now, start_dt, period_start_date, period_end_date = _parse_period( + request, default=30, options=period_options + ) # Utilisateurs total_users = User.objects.count() - new_users_qs = User.objects.filter(date_joined__date__gte=start_dt.date(), date_joined__date__lte=now.date()) + new_users_qs = User.objects.filter(date_joined__date__gte=period_start_date, date_joined__date__lte=period_end_date) # Séries quotidiennes nouveaux utilisateurs new_users_by_day = ( new_users_qs @@ -53,7 +90,7 @@ def stats_dashboard(request): # Activité approximée via Progression mise à jour active_users_qs = ( - Progression.objects.filter(updated_at__date__gte=start_dt.date(), updated_at__date__lte=now.date()) + Progression.objects.filter(updated_at__date__gte=period_start_date, updated_at__date__lte=period_end_date) .values('user').distinct() ) active_users_count = active_users_qs.count() @@ -62,7 +99,7 @@ def stats_dashboard(request): total_courses = Course.objects.count() total_courses_enabled = Course.objects.filter(enable=True).count() new_courses_by_day = ( - Course.objects.filter(created_at__date__gte=start_dt.date(), created_at__date__lte=now.date()) + Course.objects.filter(created_at__date__gte=period_start_date, created_at__date__lte=period_end_date) .extra(select={'day': "date(created_at)"}) .values('day').annotate(c=Count('id')) ) @@ -83,7 +120,7 @@ def stats_dashboard(request): # Si la table M2M n'a pas de timestamps, on utilisera updated_at de Progression pour l'activité par jour # donc on refait une série quotidienne d'activité progression progress_activity_by_day = ( - Progression.objects.filter(updated_at__date__gte=start_dt.date(), updated_at__date__lte=now.date()) + Progression.objects.filter(updated_at__date__gte=period_start_date, updated_at__date__lte=period_end_date) .extra(select={'day': "date(updated_at)"}) .values('day').annotate(c=Count('id')) ) @@ -91,7 +128,7 @@ def stats_dashboard(request): # Blog total_posts = Post.objects.count() new_posts_by_day = ( - Post.objects.filter(created_at__date__gte=start_dt.date(), created_at__date__lte=now.date()) + Post.objects.filter(created_at__date__gte=period_start_date, created_at__date__lte=period_end_date) .extra(select={'day': "date(created_at)"}) .values('day').annotate(c=Count('id')) ) @@ -101,8 +138,6 @@ def stats_dashboard(request): technique_disponible = False # Visites / Trafic - period_start_date = start_dt.date() - period_end_date = now.date() period_visits = Visit.objects.filter(date__gte=period_start_date, date__lte=period_end_date) unique_visitors = period_visits.values('visitor_id').distinct().count() @@ -135,16 +170,8 @@ def stats_dashboard(request): # Helper pour avoir toutes les dates de la période et remplir les trous def build_series_dict(qs, date_key='day', count_key='c'): - counts = {str(item[date_key]): item[count_key] for item in qs} - days = [] - values = [] - d = start_dt.date() - while d <= now.date(): - key = str(d) - days.append(key) - values.append(counts.get(key, 0)) - d += timezone.timedelta(days=1) - return days, values + # Wrapper conservant l'API locale mais utilisant le helper commun + return _build_series_for_range(period_start_date, period_end_date, qs, date_key=date_key, count_key=count_key) days_users, values_new_users = build_series_dict(new_users_by_day) days_courses, values_new_courses = build_series_dict(new_courses_by_day) @@ -158,8 +185,8 @@ def stats_dashboard(request): context = { 'period_options': period_options, 'p': p, - 'start_date': start_dt.date(), - 'end_date': now.date(), + 'start_date': period_start_date, + 'end_date': period_end_date, # KPI 'kpi': { @@ -206,19 +233,11 @@ def stats_dashboard(request): @cache_page(60 * 15) def stats_charts(request): """Page dédiée aux graphiques (réservée superadmins).""" - # Période + # Période (utilise les mêmes helpers que le dashboard pour harmonisation) 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() + p, _now, _start_dt, period_start_date, period_end_date = _parse_period( + request, default=30, options=period_options + ) # Trafic par jour (visiteurs uniques) visits_qs = ( @@ -255,8 +274,8 @@ def stats_charts(request): 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') + labels, visitors_series = _build_series_for_range(period_start_date, period_end_date, visits_qs, date_key='date') + _, conversions_series = _build_series_for_range(period_start_date, period_end_date, 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) diff --git a/templates/home/stats_charts.html b/templates/home/stats_charts.html index 159bdcd..15f4972 100644 --- a/templates/home/stats_charts.html +++ b/templates/home/stats_charts.html @@ -4,32 +4,14 @@ {% block title %}- Stats · Graphiques{% endblock %} {% block extra_head %} - - + {% include "partials/_stats_head.html" %} {% endblock %} {% block content %}