From 5271783e8802f70d0c7dd2c2f26561596e9b7e8a Mon Sep 17 00:00:00 2001 From: imperosol Date: Mon, 24 Nov 2025 15:42:36 +0100 Subject: [PATCH] refactor: user stats view --- core/templates/core/user_stats.jinja | 29 +++++---- core/tests/test_user.py | 28 ++++++++- core/views/user.py | 92 ++++++++-------------------- locale/fr/LC_MESSAGES/django.po | 4 +- 4 files changed, 71 insertions(+), 82 deletions(-) diff --git a/core/templates/core/user_stats.jinja b/core/templates/core/user_stats.jinja index 9b900117..621c4956 100644 --- a/core/templates/core/user_stats.jinja +++ b/core/templates/core/user_stats.jinja @@ -11,32 +11,35 @@ {% block content %}
- {% if profile.permanencies %} + {% if total_perm_time %}

{% trans %}Permanencies{% endtrans %}

-
Foyer :{{ total_foyer_time }}
-
Gommette :{{ total_gommette_time }}
-
MDE :{{ total_mde_time }}
-
Total :{{ total_perm_time }}
+ {% for perm in perm_time %} +
+ {{ perm["counter__name"] }} : + {{ perm["total"]|format_timedelta }} +
+ {% endfor %} +
Total :{{ total_perm_time|format_timedelta }}
{% endif %} -

{% trans %}Buyings{% endtrans %}

-
Foyer :{{ total_foyer_buyings }} €
-
Gommette :{{ total_gommette_buyings }} €
-
MDE :{{ total_mde_buyings }} €
-
Total :{{ total_foyer_buyings + total_gommette_buyings + total_mde_buyings }} € -
+ {% for sum in purchase_sums %} +
+ {{ sum["counter__name"] }} + {{ sum["total"] }} € +
+ {% endfor %} +
Total : {{ total_purchases }} €
-
-

{% trans %}Product top 10{% endtrans %}

+

{% trans %}Product top 15{% endtrans %}

diff --git a/core/tests/test_user.py b/core/tests/test_user.py index b90e626e..62a05e1b 100644 --- a/core/tests/test_user.py +++ b/core/tests/test_user.py @@ -1,3 +1,4 @@ +import itertools from datetime import timedelta from unittest import mock @@ -23,7 +24,7 @@ from core.baker_recipes import ( from core.models import AnonymousUser, Group, User from core.views import UserTabsMixin from counter.baker_recipes import sale_recipe -from counter.models import Counter, Customer, Refilling, Selling +from counter.models import Counter, Customer, Permanency, Refilling, Selling from counter.utils import is_logged_in_counter from eboutic.models import Invoice, InvoiceItem @@ -424,3 +425,28 @@ class TestUserQuerySetViewableBy: user = user_factory() viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user) assert not viewable.exists() + + +@pytest.mark.django_db +def test_user_stats(client: Client): + user = subscriber_user.make() + baker.make(Refilling, customer=user.customer, amount=99999) + bars = [b[0] for b in settings.SITH_COUNTER_BARS] + baker.make( + Permanency, + end=now() - timedelta(days=5), + start=now() - timedelta(days=5, hours=3), + counter_id=itertools.cycle(bars), + _quantity=5, + _bulk_create=True, + ) + sale_recipe.make( + counter_id=itertools.cycle(bars), + customer=user.customer, + unit_price=1, + quantity=1, + _quantity=5, + ) + client.force_login(user) + response = client.get(reverse("core:user_stats", kwargs={"user_id": user.id})) + assert response.status_code == 200 diff --git a/core/views/user.py b/core/views/user.py index 4b9c32cb..fae3b9f9 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -22,9 +22,9 @@ # # import itertools +from datetime import datetime, timedelta, timezone # This file contains all the views that concern the user model -from datetime import date, timedelta from operator import itemgetter from smtplib import SMTPException @@ -32,7 +32,7 @@ from django.contrib.auth import login, views from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied -from django.db.models import DateField, QuerySet +from django.db.models import DateField, F, QuerySet, Sum from django.db.models.functions import Trunc from django.forms.models import modelform_factory from django.http import Http404 @@ -57,6 +57,7 @@ from honeypot.decorators import check_honeypot from core.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin from core.models import Gift, Preferences, User +from core.utils import get_start_of_semester from core.views.forms import ( GiftForm, LoginForm, @@ -66,9 +67,8 @@ from core.views.forms import ( UserProfileForm, ) from core.views.mixins import TabedViewMixin, UseFragmentsMixin -from counter.models import Counter, Refilling, Selling +from counter.models import Refilling, Selling from eboutic.models import Invoice -from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -353,87 +353,47 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): context_object_name = "profile" template_name = "core/user_stats.jinja" current_tab = "stats" + queryset = User.objects.exclude(customer=None).select_related("customer") def dispatch(self, request, *arg, **kwargs): profile = self.get_object() - - if not hasattr(profile, "customer"): - raise Http404 - if not ( profile == request.user or request.user.has_perm("counter.view_customer") ): raise PermissionDenied - return super().dispatch(request, *arg, **kwargs) def get_context_data(self, **kwargs): kwargs = super().get_context_data(**kwargs) - from django.db.models import Sum - foyer = Counter.objects.filter(name="Foyer").first() - mde = Counter.objects.filter(name="MDE").first() - gommette = Counter.objects.filter(name="La Gommette").first() - semester_start = Subscription.compute_start(d=date.today(), duration=3) + start = get_start_of_semester() + semester_start = datetime( + start.year, start.month, start.day, tzinfo=timezone.utc + ) + + kwargs["perm_time"] = list( + self.object.permanencies.filter(end__isnull=False, counter__type="BAR") + .values("counter", "counter__name") + .annotate(total=Sum(F("end") - F("start"), default=timedelta(seconds=0))) + .order_by("-total") + ) kwargs["total_perm_time"] = sum( - [p.end - p.start for p in self.object.permanencies.exclude(end=None)], - timedelta(), + [perm["total"] for perm in kwargs["perm_time"]], start=timedelta(seconds=0) ) - kwargs["total_foyer_time"] = sum( - [ - p.end - p.start - for p in self.object.permanencies.filter(counter=foyer).exclude( - end=None - ) - ], - timedelta(), - ) - kwargs["total_mde_time"] = sum( - [ - p.end - p.start - for p in self.object.permanencies.filter(counter=mde).exclude(end=None) - ], - timedelta(), - ) - kwargs["total_gommette_time"] = sum( - [ - p.end - p.start - for p in self.object.permanencies.filter(counter=gommette).exclude( - end=None - ) - ], - timedelta(), - ) - kwargs["total_foyer_buyings"] = sum( - [ - b.unit_price * b.quantity - for b in self.object.customer.buyings.filter( - counter=foyer, date__gte=semester_start - ) - ] - ) - kwargs["total_mde_buyings"] = sum( - [ - b.unit_price * b.quantity - for b in self.object.customer.buyings.filter( - counter=mde, date__gte=semester_start - ) - ] - ) - kwargs["total_gommette_buyings"] = sum( - [ - b.unit_price * b.quantity - for b in self.object.customer.buyings.filter( - counter=gommette, date__gte=semester_start - ) - ] + kwargs["purchase_sums"] = list( + self.object.customer.buyings.filter( + date__gte=semester_start, counter__type="BAR" + ) + .values("counter", "counter__name") + .annotate(total=Sum(F("unit_price") * F("quantity"))) + .order_by("-total") ) + kwargs["total_purchases"] = sum(s["total"] for s in kwargs["purchase_sums"]) kwargs["top_product"] = ( self.object.customer.buyings.values("product__name") .annotate(product_sum=Sum("quantity")) - .exclude(product_sum=None) .order_by("-product_sum") - .all()[:10] + .all()[:15] ) return kwargs diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index a9197f75..de4ade36 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -2658,8 +2658,8 @@ msgid "Buyings" msgstr "Achats" #: core/templates/core/user_stats.jinja -msgid "Product top 10" -msgstr "Top 10 produits" +msgid "Product top 15" +msgstr "Top 15 produits" #: core/templates/core/user_stats.jinja msgid "Product"