From 63764f54ba2cfc9888133bac74d716c93ac519b1 Mon Sep 17 00:00:00 2001 From: imperosol Date: Tue, 22 Sep 2026 11:04:18 +0200 Subject: [PATCH 1/4] better subscription generation in populate_more --- core/management/commands/populate_more.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/management/commands/populate_more.py b/core/management/commands/populate_more.py index 3262fe46..544be7c1 100644 --- a/core/management/commands/populate_more.py +++ b/core/management/commands/populate_more.py @@ -144,10 +144,22 @@ class Command(BaseCommand): ) def create_subscriptions(self, users: list[User]): + subscription_types = [ + "un-semestre", + "deux-semestres", + "cursus-tronc-commun", + "cursus-branche", + ] + def prepare_subscription(_user: User, start_date: date) -> Subscription: payment_method = random.choice(settings.SITH_SUBSCRIPTION_PAYMENT_METHOD)[0] - duration = random.randint(1, 4) - s = Subscription(member=_user, payment_method=payment_method) + subscription_type = random.choice(subscription_types) + s = Subscription( + member=_user, + payment_method=payment_method, + subscription_type=subscription_type, + ) + duration = settings.SITH_SUBSCRIPTIONS[subscription_type]["duration"] s.subscription_start = s.compute_start(d=start_date, duration=duration) s.subscription_end = s.compute_end(duration) return s From c8f4d12fc49a5b45f625bf78c506a1529092b15c Mon Sep 17 00:00:00 2001 From: imperosol Date: Tue, 22 Sep 2026 11:37:38 +0200 Subject: [PATCH 2/4] remove date selection from subscription stats view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ca n'a jamais marché depuis 2017 --- subscription/forms.py | 13 +--- .../templates/subscription/stats.jinja | 75 ++++++++----------- subscription/views.py | 25 +------ 3 files changed, 36 insertions(+), 77 deletions(-) diff --git a/subscription/forms.py b/subscription/forms.py index 7f8cd7d7..29a70b8d 100644 --- a/subscription/forms.py +++ b/subscription/forms.py @@ -8,22 +8,11 @@ from django.utils.translation import gettext_lazy as _ from core.models import User from core.utils import get_last_promo -from core.views.forms import SelectDate, SelectDateTime +from core.views.forms import SelectDate from core.views.widgets.ajax_select import AutoCompleteSelectUser from subscription.models import Subscription -class SelectionDateForm(forms.Form): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["start_date"] = forms.DateTimeField( - label=_("Start date"), widget=SelectDateTime, required=True - ) - self.fields["end_date"] = forms.DateTimeField( - label=_("End date"), widget=SelectDateTime, required=True - ) - - class SubscriptionForm(forms.ModelForm): allowed_payment_methods = ["CARD", "CASH", "AE_ACCOUNT"] diff --git a/subscription/templates/subscription/stats.jinja b/subscription/templates/subscription/stats.jinja index eb124032..c695d996 100644 --- a/subscription/templates/subscription/stats.jinja +++ b/subscription/templates/subscription/stats.jinja @@ -11,51 +11,38 @@ {% block content %} -

-

- {{ form.start_date.label }}
- {{ form.start_date }}

- {{ form.end_date.label }}
- {{ form.end_date }}
-

-
-

- -

- {% trans %}Total subscriptions{% endtrans %} : {{ subscriptions_total.count() }}

- {% trans %}Subscriptions by type{% endtrans %}

- {% for location in locations %} - {{ location[1] }} : {{ subscriptions_total.filter(location=location[0]).count() }}
+ {% trans %}Total subscriptions{% endtrans %} : {{ subscriptions_total.count() }}

+ {% trans %}Subscriptions by type{% endtrans %}

+ {% for location in locations %} + {{ location[1] }} : {{ subscriptions_total.filter(location=location[0]).count() }}
+ {% endfor %} + +
+ + + + {% for location in locations %} + + {% endfor %} + + + {% for type in subscriptions_types %} + + + {% set subscriptions_total_type = subscriptions_total.filter(subscription_type=type) %} + {% for location in locations %} + + {% endfor %} + + {% endfor %} -

- -
- -

{% trans %}Subscription type{% endtrans %}{{ location[1] }}{% trans %}Total{% endtrans %}
{{ subscriptions_types[type]['name'] }} + {% set subscriptions_total_type_location = subscriptions_total_type.filter(location=location[0]) %} + {% trans %}Total{% endtrans %} : {{ subscriptions_total_type_location.count()}}
+ {% for p_type in payment_types %} + {{ p_type[1] }} : {{ subscriptions_total_type_location.filter(payment_method=p_type[0]).count()}}
+ {% endfor %} +
{{subscriptions_total_type.count()}}
- - - {% for location in locations %} - - {% endfor %} - - {% for type in subscriptions_types %} - - - {% set subscriptions_total_type = subscriptions_total.filter(subscription_type=type) %} - {% for location in locations %} - - {% endfor %} - - {% endfor %} -
{% trans %}Subscription type{% endtrans %}{{ location[1] }}{% trans %}Total{% endtrans %}
{{ subscriptions_types[type]['name'] }} - {% set subscriptions_total_type_location = subscriptions_total_type.filter(location=location[0]) %} - {% trans %}Total{% endtrans %} : {{ subscriptions_total_type_location.count()}}
- {% for p_type in payment_types %} - {{ p_type[1] }} : {{ subscriptions_total_type_location.filter(payment_method=p_type[0]).count()}}
- {% endfor %} -
{{subscriptions_total_type.count()}} -
- + {% endblock %} diff --git a/subscription/views.py b/subscription/views.py index 505f9614..8c790647 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -17,16 +17,14 @@ from django.conf import settings from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied -from django.urls import reverse, reverse_lazy +from django.urls import reverse from django.utils.timezone import localdate from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, TemplateView -from django.views.generic.edit import FormView from core.views import FragmentMixin, UseFragmentsMixin from core.views.group import PermissionGroupsUpdateView from subscription.forms import ( - SelectionDateForm, SubscriptionExistingUserForm, SubscriptionNewUserForm, ) @@ -93,34 +91,19 @@ class SubscriptionPermissionView(PermissionGroupsUpdateView): extra_context = {"object_name": _("the groups that can create subscriptions")} -class SubscriptionsStatsView(FormView): +class SubscriptionsStatsView(TemplateView): template_name = "subscription/stats.jinja" - form_class = SelectionDateForm - success_url = reverse_lazy("subscriptions:stats") def dispatch(self, request, *arg, **kwargs): - self.start_date = localdate() - self.end_date = self.start_date if request.user.is_root or request.user.is_board_member: return super().dispatch(request, *arg, **kwargs) raise PermissionDenied - def post(self, request, *args, **kwargs): - self.form = self.get_form() - self.start_date = self.form["start_date"] - self.end_date = self.form["end_date"] - return super().post(request, *args, **kwargs) - - def get_initial(self): - return { - "start_date": self.start_date.strftime("%Y-%m-%d %H:%M:%S"), - "end_date": self.end_date.strftime("%Y-%m-%d %H:%M:%S"), - } - def get_context_data(self, **kwargs): kwargs = super().get_context_data(**kwargs) + today = localdate() kwargs["subscriptions_total"] = Subscription.objects.filter( - subscription_end__gte=self.end_date, subscription_start__lte=self.start_date + subscription_end__gte=today, subscription_start__lte=today ) kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS kwargs["payment_types"] = settings.SITH_SUBSCRIPTION_PAYMENT_METHOD From 37871b386adecc0319ac04f2931d13d8146910dd Mon Sep 17 00:00:00 2001 From: imperosol Date: Wed, 23 Sep 2026 08:39:54 +0200 Subject: [PATCH 3/4] optimize subscriptions stats --- .../templates/subscription/stats.jinja | 25 ++++++++++--------- subscription/views.py | 23 ++++++++++++++++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/subscription/templates/subscription/stats.jinja b/subscription/templates/subscription/stats.jinja index c695d996..a1dd22d0 100644 --- a/subscription/templates/subscription/stats.jinja +++ b/subscription/templates/subscription/stats.jinja @@ -13,12 +13,6 @@ - {% trans %}Total subscriptions{% endtrans %} : {{ subscriptions_total.count() }}

- {% trans %}Subscriptions by type{% endtrans %}

- {% for location in locations %} - {{ location[1] }} : {{ subscriptions_total.filter(location=location[0]).count() }}
- {% endfor %} -
@@ -28,20 +22,27 @@ {% endfor %} + + + {% for location in locations %} + + {% endfor %} + + {% for type in subscriptions_types %} - - {% set subscriptions_total_type = subscriptions_total.filter(subscription_type=type) %} + {% for location in locations %} {% endfor %} - + {% endfor %}
{% trans %}Total{% endtrans %}
{% trans %}All subscriptions{% endtrans %}{{ total_location[location[0]] }}
{{ total_location.values()|sum }}
{{ subscriptions_types[type]['name'] }}{{ subscriptions_types[type]['name'] }} - {% set subscriptions_total_type_location = subscriptions_total_type.filter(location=location[0]) %} - {% trans %}Total{% endtrans %} : {{ subscriptions_total_type_location.count()}}
{% for p_type in payment_types %} - {{ p_type[1] }} : {{ subscriptions_total_type_location.filter(payment_method=p_type[0]).count()}}
+ {% set subtotal = subscriptions[type][location[0]][p_type[0]] %} + {% if subtotal > 0 %} + {{ p_type[1] }} : {{ subtotal }}
+ {% endif %} {% endfor %}
{{subscriptions_total_type.count()}}{{ total_type[type] }}
diff --git a/subscription/views.py b/subscription/views.py index 8c790647..8ab28134 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -12,11 +12,13 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +from collections import defaultdict from django.conf import settings from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied +from django.db.models import Count from django.urls import reverse from django.utils.timezone import localdate from django.utils.translation import gettext_lazy as _ @@ -102,9 +104,28 @@ class SubscriptionsStatsView(TemplateView): def get_context_data(self, **kwargs): kwargs = super().get_context_data(**kwargs) today = localdate() - kwargs["subscriptions_total"] = Subscription.objects.filter( + qs = Subscription.objects.filter( subscription_end__gte=today, subscription_start__lte=today ) + grouped = qs.values("subscription_type", "location", "payment_method").annotate( + count=Count("*") + ) + by_location = qs.values("location").annotate(count=Count("*")) + by_type = qs.values("subscription_type").annotate(count=Count("*")) + kwargs["subscriptions"] = defaultdict( + lambda: defaultdict(lambda: defaultdict(int)) + ) + for sub in grouped: + kwargs["subscriptions"][sub["subscription_type"]][sub["location"]][ + sub["payment_method"] + ] = sub["count"] + kwargs["total_location"] = defaultdict( + int, {i["location"]: i["count"] for i in by_location} + ) + kwargs["total_type"] = defaultdict( + int, {i["subscription_type"]: i["count"] for i in by_type} + ) + kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS kwargs["payment_types"] = settings.SITH_SUBSCRIPTION_PAYMENT_METHOD kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS From 44f99655570171d1541f38d12cdebf654ccfb445 Mon Sep 17 00:00:00 2001 From: imperosol Date: Wed, 23 Sep 2026 08:40:08 +0200 Subject: [PATCH 4/4] show tooltips on subscription chart hover --- .../static/bundled/subscription/stats-index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/subscription/static/bundled/subscription/stats-index.ts b/subscription/static/bundled/subscription/stats-index.ts index 3fc992b3..fef50d1b 100644 --- a/subscription/static/bundled/subscription/stats-index.ts +++ b/subscription/static/bundled/subscription/stats-index.ts @@ -1,6 +1,13 @@ -import { BarController, BarElement, CategoryScale, Chart, LinearScale } from "chart.js"; +import { + BarController, + BarElement, + CategoryScale, + Chart, + LinearScale, + Tooltip, +} from "chart.js"; -Chart.register(BarController, BarElement, CategoryScale, LinearScale); +Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip); function getRandomColor() { const letters = "0123456789ABCDEF";