mirror of
https://github.com/ae-utbm/sith.git
synced 2025-11-25 14:16:56 +00:00
refactor: user stats view
This commit is contained in:
@@ -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 }} €</span></div>
|
{% for sum in purchase_sums %}
|
||||||
<div><span>Gommette :</span><span>{{ total_gommette_buyings }} €</span></div>
|
|
||||||
<div><span>MDE :</span><span>{{ total_mde_buyings }} €</span></div>
|
|
||||||
<div><b>Total :</b><b>{{ total_foyer_buyings + total_gommette_buyings + total_mde_buyings }} €</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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user