from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import user_passes_test from django.utils import timezone from django.db.models import Count from django.views.decorators.cache import cache_page from django.contrib.auth.models import User from courses.models import Course, Lesson from blog.models import Post from progression.models import Progression import json def home(request): courses = Course.objects.order_by('-created_at')[:6] return render(request, 'home.html', {'courses': courses}) def premium(request, course_id): """Landing page présentant les avantages du Premium.""" course = get_object_or_404(Course, pk=course_id) return render(request, 'premium.html', {'course': course}) # 15 minutes de cache sur la page complète (sauf si décochée plus tard pour des blocs en direct) @user_passes_test(lambda u: u.is_superuser) @cache_page(60 * 15) def stats_dashboard(request): """ Tableau de bord statistiques réservé aux superadministrateurs. Périodes supportées: 7, 30, 90, 180 jours (GET param 'p'). """ # 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 # 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()) # Séries quotidiennes nouveaux utilisateurs new_users_by_day = ( new_users_qs .extra(select={'day': "date(date_joined)"}) .values('day') .annotate(c=Count('id')) ) # 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()) .values('user').distinct() ) active_users_count = active_users_qs.count() # Cours 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()) .extra(select={'day': "date(created_at)"}) .values('day').annotate(c=Count('id')) ) # Leçons total_lessons = Lesson.objects.count() # Achèvements de leçons (via table de liaison M2M) through = Progression.completed_lessons.through lesson_completions_by_day = ( through.objects.filter( progression__updated_at__date__gte=start_dt.date(), progression__updated_at__date__lte=now.date(), ) .extra(select={'day': "date(progression_updated_at)"}) if 'progression_updated_at' in [f.name for f in through._meta.fields] else through.objects.extra(select={'day': "date(created_at)"}) # fallback si champs créé n'existe pas ) # 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()) .extra(select={'day': "date(updated_at)"}) .values('day').annotate(c=Count('id')) ) # 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()) .extra(select={'day': "date(created_at)"}) .values('day').annotate(c=Count('id')) ) # Revenus/Paiements & Technique (placeholders faute de sources) revenus_disponibles = False technique_disponible = False # 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 days_users, values_new_users = build_series_dict(new_users_by_day) days_courses, values_new_courses = build_series_dict(new_courses_by_day) days_posts, values_new_posts = build_series_dict(new_posts_by_day) days_activity, values_activity = build_series_dict(progress_activity_by_day) # Tables simples (jour, valeur) new_users_table = list(zip(days_users, values_new_users)) new_courses_table = list(zip(days_courses, values_new_courses)) context = { 'period_options': period_options, 'p': p, 'start_date': start_dt.date(), 'end_date': now.date(), # KPI 'kpi': { 'total_users': total_users, 'new_users_period': sum(values_new_users), 'active_users_period': active_users_count, 'total_courses': total_courses, 'courses_enabled': total_courses_enabled, 'total_lessons': total_lessons, 'total_posts': total_posts, }, # Séries pour graphiques 'series': { 'days': days_users, # mêmes intervalles pour tous 'new_users': values_new_users, 'new_courses': values_new_courses, 'new_posts': values_new_posts, 'activity_progress': values_activity, }, # Disponibilité des sections 'revenus_disponibles': revenus_disponibles, 'technique_disponible': technique_disponible, # Tables 'new_users_table': new_users_table, 'new_courses_table': new_courses_table, } # Sérialisation JSON pour Chart.js context['series_json'] = json.dumps(context['series']) context['labels_json'] = json.dumps(context['series']['days']) return render(request, 'home/stats_dashboard.html', context)