refactor: user stats view

This commit is contained in:
imperosol
2025-11-24 15:42:36 +01:00
parent 4ff4d179a1
commit 5271783e88
4 changed files with 71 additions and 82 deletions

View File

@@ -11,32 +11,35 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
{% if profile.permanencies %} {% if total_perm_time %}
<div> <div>
<h3>{% trans %}Permanencies{% endtrans %}</h3> <h3>{% trans %}Permanencies{% endtrans %}</h3>
<div class="flexed"> <div class="flexed">
<div><span>Foyer :</span><span>{{ total_foyer_time }}</span></div> {% for perm in perm_time %}
<div><span>Gommette :</span><span>{{ total_gommette_time }}</span></div> <div>
<div><span>MDE :</span><span>{{ total_mde_time }}</span></div> <span>{{ perm["counter__name"] }} :</span>
<div><b>Total :</b><b>{{ total_perm_time }}</b></div> <span>{{ perm["total"]|format_timedelta }}</span>
</div>
{% endfor %}
<div><b>Total :</b><b>{{ total_perm_time|format_timedelta }}</b></div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div> <div>
<h3>{% trans %}Buyings{% endtrans %}</h3> <h3>{% trans %}Buyings{% endtrans %}</h3>
<div class="flexed"> <div class="flexed">
<div><span>Foyer :</span><span>{{ total_foyer_buyings }}&nbsp;€</span></div> {% for sum in purchase_sums %}
<div><span>Gommette :</span><span>{{ total_gommette_buyings }}&nbsp;€</span></div>
<div><span>MDE :</span><span>{{ total_mde_buyings }}&nbsp;€</span></div>
<div><b>Total :</b><b>{{ total_foyer_buyings + total_gommette_buyings + total_mde_buyings }}&nbsp;€</b>
</div>
</div>
</div>
</div>
<div> <div>
<h3>{% trans %}Product top 10{% endtrans %}</h3> <span>{{ sum["counter__name"] }}</span>
<span>{{ sum["total"] }} €</span>
</div>
{% endfor %}
<div><b>Total : </b><b>{{ total_purchases }} €</b></div>
</div>
</div>
</div>
<div>
<h3>{% trans %}Product top 15{% endtrans %}</h3>
<table> <table>
<thead> <thead>
<tr> <tr>

View File

@@ -1,3 +1,4 @@
import itertools
from datetime import timedelta from datetime import timedelta
from unittest import mock from unittest import mock
@@ -23,7 +24,7 @@ from core.baker_recipes import (
from core.models import AnonymousUser, Group, User from core.models import AnonymousUser, Group, User
from core.views import UserTabsMixin from core.views import UserTabsMixin
from counter.baker_recipes import sale_recipe 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 counter.utils import is_logged_in_counter
from eboutic.models import Invoice, InvoiceItem from eboutic.models import Invoice, InvoiceItem
@@ -424,3 +425,28 @@ class TestUserQuerySetViewableBy:
user = user_factory() user = user_factory()
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user) viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
assert not viewable.exists() 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

View File

@@ -22,9 +22,9 @@
# #
# #
import itertools import itertools
from datetime import datetime, timedelta, timezone
# This file contains all the views that concern the user model # This file contains all the views that concern the user model
from datetime import date, timedelta
from operator import itemgetter from operator import itemgetter
from smtplib import SMTPException 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.forms import PasswordChangeForm
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied 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.db.models.functions import Trunc
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import Http404 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.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.models import Gift, Preferences, User from core.models import Gift, Preferences, User
from core.utils import get_start_of_semester
from core.views.forms import ( from core.views.forms import (
GiftForm, GiftForm,
LoginForm, LoginForm,
@@ -66,9 +67,8 @@ from core.views.forms import (
UserProfileForm, UserProfileForm,
) )
from core.views.mixins import TabedViewMixin, UseFragmentsMixin 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 eboutic.models import Invoice
from subscription.models import Subscription
from trombi.views import UserTrombiForm from trombi.views import UserTrombiForm
@@ -353,87 +353,47 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
context_object_name = "profile" context_object_name = "profile"
template_name = "core/user_stats.jinja" template_name = "core/user_stats.jinja"
current_tab = "stats" current_tab = "stats"
queryset = User.objects.exclude(customer=None).select_related("customer")
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
profile = self.get_object() profile = self.get_object()
if not hasattr(profile, "customer"):
raise Http404
if not ( if not (
profile == request.user or request.user.has_perm("counter.view_customer") profile == request.user or request.user.has_perm("counter.view_customer")
): ):
raise PermissionDenied raise PermissionDenied
return super().dispatch(request, *arg, **kwargs) return super().dispatch(request, *arg, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
from django.db.models import Sum
foyer = Counter.objects.filter(name="Foyer").first() start = get_start_of_semester()
mde = Counter.objects.filter(name="MDE").first() semester_start = datetime(
gommette = Counter.objects.filter(name="La Gommette").first() start.year, start.month, start.day, tzinfo=timezone.utc
semester_start = Subscription.compute_start(d=date.today(), duration=3) )
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( kwargs["total_perm_time"] = sum(
[p.end - p.start for p in self.object.permanencies.exclude(end=None)], [perm["total"] for perm in kwargs["perm_time"]], start=timedelta(seconds=0)
timedelta(),
) )
kwargs["total_foyer_time"] = sum( kwargs["purchase_sums"] = list(
[ self.object.customer.buyings.filter(
p.end - p.start date__gte=semester_start, counter__type="BAR"
for p in self.object.permanencies.filter(counter=foyer).exclude(
end=None
) )
], .values("counter", "counter__name")
timedelta(), .annotate(total=Sum(F("unit_price") * F("quantity")))
) .order_by("-total")
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["total_purchases"] = sum(s["total"] for s in kwargs["purchase_sums"])
kwargs["top_product"] = ( kwargs["top_product"] = (
self.object.customer.buyings.values("product__name") self.object.customer.buyings.values("product__name")
.annotate(product_sum=Sum("quantity")) .annotate(product_sum=Sum("quantity"))
.exclude(product_sum=None)
.order_by("-product_sum") .order_by("-product_sum")
.all()[:10] .all()[:15]
) )
return kwargs return kwargs

View File

@@ -2658,8 +2658,8 @@ msgid "Buyings"
msgstr "Achats" msgstr "Achats"
#: core/templates/core/user_stats.jinja #: core/templates/core/user_stats.jinja
msgid "Product top 10" msgid "Product top 15"
msgstr "Top 10 produits" msgstr "Top 15 produits"
#: core/templates/core/user_stats.jinja #: core/templates/core/user_stats.jinja
msgid "Product" msgid "Product"