From 083af6f9d44b2e0ddccd8cc40c7ceffe9ce69651 Mon Sep 17 00:00:00 2001 From: mrtoine Date: Tue, 16 Dec 2025 10:28:55 +0100 Subject: [PATCH 01/27] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20la=20version?= =?UTF-8?q?=20applicative=20dans=20`VERSION.txt`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 63c4a6a..0d7e59a 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.3.3 (b3b18bd) \ No newline at end of file +1.3.3 (6e8a2bc) \ No newline at end of file From 7869abf44138c00bd00f7f825e683f910fa6bb00 Mon Sep 17 00:00:00 2001 From: mrtoine Date: Tue, 16 Dec 2025 10:48:12 +0100 Subject: [PATCH 02/27] =?UTF-8?q?Ajout=20de=20la=20vue=20`stats=5Fcharts`?= =?UTF-8?q?=20avec=20graphiques=20d=C3=A9taill=C3=A9s=20pour=20les=20super?= =?UTF-8?q?administrateurs,=20mise=20=C3=A0=20jour=20des=20templates=20et?= =?UTF-8?q?=20des=20routes=20associ=C3=A9es,=20et=20configuration=20suppl?= =?UTF-8?q?=C3=A9mentaire=20pour=20l'email=20backend.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devart/settings.py | 7 ++- home/urls.py | 1 + home/views.py | 94 +++++++++++++++++++++++++++++ templates/home/stats_dashboard.html | 1 + 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/devart/settings.py b/devart/settings.py index d5b3597..f846daa 100644 --- a/devart/settings.py +++ b/devart/settings.py @@ -207,4 +207,9 @@ def get_git_version(): GIT_VERSION = get_git_version() EMAIL_BACKEND = dotenv.get_key('.env', 'EMAIL_BACKEND') -DEFAULT_FROM_EMAIL = dotenv.get_key('.env', 'EMAIL_HOST_USER') \ No newline at end of file +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 \ No newline at end of file diff --git a/home/urls.py b/home/urls.py index 80f5069..1dbd960 100644 --- a/home/urls.py +++ b/home/urls.py @@ -7,4 +7,5 @@ urlpatterns = [ path('premium/', views.premium, name='premium'), # Tableau de bord statistiques (réservé superadministrateurs) path('dashboard/stats/', views.stats_dashboard, name='stats_dashboard'), + path('dashboard/stats/charts/', views.stats_charts, name='stats_charts'), ] \ No newline at end of file diff --git a/home/views.py b/home/views.py index 159fb3f..ebd2cf2 100644 --- a/home/views.py +++ b/home/views.py @@ -199,3 +199,97 @@ def stats_dashboard(request): context['labels_json'] = json.dumps(context['series']['days']) 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) diff --git a/templates/home/stats_dashboard.html b/templates/home/stats_dashboard.html index 84a7094..3dddab3 100644 --- a/templates/home/stats_dashboard.html +++ b/templates/home/stats_dashboard.html @@ -20,6 +20,7 @@ {% block content %}

Tableau de bord statistiques

+

→ Voir la page de graphiques

From f9e2df559c378a2758c55f4f6fc40255ce4f4496 Mon Sep 17 00:00:00 2001 From: mrtoine Date: Tue, 16 Dec 2025 10:48:48 +0100 Subject: [PATCH 03/27] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20la=20version?= =?UTF-8?q?=20applicative=20dans=20`VERSION.txt`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 0d7e59a..3894f62 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.3.3 (6e8a2bc) \ No newline at end of file +1.3.5 (7869abf) \ No newline at end of file From 1b0ccc54a2bb450a52a38281fa83b3d649671ed9 Mon Sep 17 00:00:00 2001 From: mrtoine Date: Tue, 16 Dec 2025 10:52:55 +0100 Subject: [PATCH 04/27] Ajout du template `stats_charts.html` pour afficher des graphiques statistiques interactifs avec Chart.js. --- templates/home/stats_charts.html | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 templates/home/stats_charts.html diff --git a/templates/home/stats_charts.html b/templates/home/stats_charts.html new file mode 100644 index 0000000..159bdcd --- /dev/null +++ b/templates/home/stats_charts.html @@ -0,0 +1,96 @@ +{% extends "layout.html" %} +{% load static %} + +{% block title %}- Stats · Graphiques{% endblock %} + +{% block extra_head %} + + +{% endblock %} + +{% block content %} +
+

Graphiques statistiques

+ + +
+ + + Du {{ start_date }} au {{ end_date }} +
+
Mise en cache 15 minutes
+ + +
+
+

Visiteurs uniques par jour

+ +
+
+

Conversions (visiteurs devenus utilisateurs) par jour

+ +
+
+ +
+
+

Top sources (visiteurs uniques)

+ +
+
+

Top pays (visiteurs uniques)

+ +
+
+ +

← Retour au tableau de bord

+
+ + +{% endblock %} From e1f8a23f3df8c5781a0c220bdc251db746553fb0 Mon Sep 17 00:00:00 2001 From: mrtoine Date: Tue, 16 Dec 2025 10:53:26 +0100 Subject: [PATCH 05/27] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20la=20version?= =?UTF-8?q?=20applicative=20dans=20`VERSION.txt`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 3894f62..299b2e2 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.3.5 (7869abf) \ No newline at end of file +1.3.5 (1b0ccc5) \ No newline at end of file From 91f7f795469c69ad71ccf5ff5f930ed9976ff5d0 Mon Sep 17 00:00:00 2001 From: mrtoine Date: Tue, 16 Dec 2025 13:10:43 +0100 Subject: [PATCH 06/27] =?UTF-8?q?Ajout=20des=20d=C3=A9corations=20et=20ani?= =?UTF-8?q?mations=20de=20neige=20pour=20les=20f=C3=AAtes=20de=20fin=20d'a?= =?UTF-8?q?nn=C3=A9e,=20charg=C3=A9es=20conditionnellement=20en=20d=C3=A9c?= =?UTF-8?q?embre.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/functions.js | 26 +----------------------- templates/layout.html | 36 +++++++++++++++++++++++++++++++++ templates/partials/_footer.html | 6 +++++- templates/partials/_header.html | 7 ++++++- 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/static/js/functions.js b/static/js/functions.js index db8ff92..500562a 100644 --- a/static/js/functions.js +++ b/static/js/functions.js @@ -94,28 +94,4 @@ document.addEventListener('DOMContentLoaded', function() { }); } } catch(e) {} -}); - -// Fonction pour générer des flocons de neige -function createSnowflake() { - const snowflake = document.createElement('div'); - snowflake.classList.add('snowflake'); - snowflake.textContent = '•'; - - snowflake.style.left = `${Math.random() * 100}vw`; - - const size = Math.random() * 1.5 + 0.5; - snowflake.style.fontSize = `${size}em`; - - const duration = Math.random() * 5 + 5; - snowflake.style.animationDuration = `${duration}s`; - - document.body.appendChild(snowflake); - - setTimeout(() => { - snowflake.remove(); - }, duration * 1000); -} - -// On génère les flocons toutes les 300ms -setInterval(createSnowflake, 300); \ No newline at end of file +}); \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index 2e7a521..2a39086 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -31,6 +31,37 @@ + {% now "n" as month %} + {% if month == '12' %} + + + + {% endif %} + {% block extra_head %}{% endblock %} @@ -53,6 +84,11 @@ + {% now "n" as month %} + {% if month == '12' %} + + + {% endif %} {% block header %} {% include "partials/_header.html" %} {% endblock %} diff --git a/templates/partials/_footer.html b/templates/partials/_footer.html index 29b2d41..934a5e3 100644 --- a/templates/partials/_footer.html +++ b/templates/partials/_footer.html @@ -1,4 +1,5 @@ -