1 Commits

Author SHA1 Message Date
imperosol
00acdcd1a5 refactor: remove useless Group methods 2025-11-24 18:15:28 +01:00
8 changed files with 106 additions and 83 deletions

View File

@@ -38,7 +38,6 @@ from django.contrib.auth.models import AnonymousUser as AuthAnonymousUser
from django.contrib.auth.models import Group as AuthGroup from django.contrib.auth.models import Group as AuthGroup
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.core import validators from django.core import validators
from django.core.cache import cache
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.core.files import File from django.core.files import File
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
@@ -77,16 +76,6 @@ class Group(AuthGroup):
def get_absolute_url(self) -> str: def get_absolute_url(self) -> str:
return reverse("core:group_list") return reverse("core:group_list")
def save(self, *args, **kwargs) -> None:
super().save(*args, **kwargs)
cache.set(f"sith_group_{self.id}", self)
cache.set(f"sith_group_{self.name.replace(' ', '_')}", self)
def delete(self, *args, **kwargs) -> None:
super().delete(*args, **kwargs)
cache.delete(f"sith_group_{self.id}")
cache.delete(f"sith_group_{self.name.replace(' ', '_')}")
def validate_promo(value: int) -> None: def validate_promo(value: int) -> None:
last_promo = get_last_promo() last_promo = get_last_promo()

View File

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

View File

@@ -55,17 +55,31 @@ def phonenumber(
return value return value
@register.filter(name="truncate_time")
def truncate_time(value, time_unit):
"""Remove everything in the time format lower than the specified unit.
Args:
value: the value to truncate
time_unit: the lowest unit to display
"""
value = str(value)
return {
"millis": lambda: value.split(".")[0],
"seconds": lambda: value.rsplit(":", maxsplit=1)[0],
"minutes": lambda: value.split(":", maxsplit=1)[0],
"hours": lambda: value.rsplit(" ")[0],
}[time_unit]()
@register.filter(name="format_timedelta") @register.filter(name="format_timedelta")
def format_timedelta(value: datetime.timedelta) -> str: def format_timedelta(value: datetime.timedelta) -> str:
value = value - datetime.timedelta(microseconds=value.microseconds)
days = value.days days = value.days
if days == 0: if days == 0:
return str(value) return str(value)
remainder = value - datetime.timedelta(days=days) remainder = value - datetime.timedelta(days=days)
return ngettext( return ngettext(
"%(nb_days)d day, %(remainder)s", "%(nb_days)d day, %(remainder)s", "%(nb_days)d days, %(remainder)s", days
"%(nb_days)d days, %(remainder)s",
days,
) % {"nb_days": days, "remainder": str(remainder)} ) % {"nb_days": days, "remainder": str(remainder)}

View File

@@ -1,4 +1,3 @@
import itertools
from datetime import timedelta from datetime import timedelta
from unittest import mock from unittest import mock
@@ -24,7 +23,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, Permanency, Refilling, Selling from counter.models import Counter, Customer, 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
@@ -425,28 +424,3 @@ 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 timedelta
# 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, F, QuerySet, Sum from django.db.models import DateField, QuerySet
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
@@ -66,8 +66,9 @@ 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 Refilling, Selling from counter.models import Counter, 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
@@ -352,40 +353,87 @@ 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
kwargs["perm_time"] = list( foyer = Counter.objects.filter(name="Foyer").first()
self.object.permanencies.filter(end__isnull=False, counter__type="BAR") mde = Counter.objects.filter(name="MDE").first()
.values("counter", "counter__name") gommette = Counter.objects.filter(name="La Gommette").first()
.annotate(total=Sum(F("end") - F("start"), default=timedelta(seconds=0))) semester_start = Subscription.compute_start(d=date.today(), duration=3)
.order_by("-total")
)
kwargs["total_perm_time"] = sum( kwargs["total_perm_time"] = sum(
[perm["total"] for perm in kwargs["perm_time"]], start=timedelta(seconds=0) [p.end - p.start for p in self.object.permanencies.exclude(end=None)],
timedelta(),
) )
kwargs["purchase_sums"] = list( kwargs["total_foyer_time"] = sum(
self.object.customer.buyings.filter(counter__type="BAR") [
.values("counter", "counter__name") p.end - p.start
.annotate(total=Sum(F("unit_price") * F("quantity"))) for p in self.object.permanencies.filter(counter=foyer).exclude(
.order_by("-total") 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["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()[:15] .all()[:10]
) )
return kwargs return kwargs

View File

@@ -51,7 +51,7 @@
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
<td>{{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %}</td> <td>{{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %}</td>
<td>{{ barman.promo or '' }}</td> <td>{{ barman.promo or '' }}</td>
<td>{{ barman.perm_sum|format_timedelta }}</td> <td>{{ barman.perm_sum|format_timedelta|truncate_time("millis") }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -73,7 +73,7 @@
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
<td>{{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %}</td> <td>{{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %}</td>
<td>{{ barman.promo or '' }}</td> <td>{{ barman.promo or '' }}</td>
<td>{{ barman.perm_sum|format_timedelta }}</td> <td>{{ barman.perm_sum|format_timedelta|truncate_time("millis") }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-24 11:05+0100\n" "POT-Creation-Date: 2025-11-19 21:00+0100\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -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 15" msgid "Product top 10"
msgstr "Top 15 produits" msgstr "Top 10 produits"
#: core/templates/core/user_stats.jinja #: core/templates/core/user_stats.jinja
msgid "Product" msgid "Product"
@@ -2819,8 +2819,8 @@ msgstr "Outils Trombi"
#, python-format #, python-format
msgid "%(nb_days)d day, %(remainder)s" msgid "%(nb_days)d day, %(remainder)s"
msgid_plural "%(nb_days)d days, %(remainder)s" msgid_plural "%(nb_days)d days, %(remainder)s"
msgstr[0] "%(nb_days)d jour, %(remainder)s" msgstr[0] ""
msgstr[1] "%(nb_days)d jours, %(remainder)s" msgstr[1] ""
#: core/views/files.py #: core/views/files.py
msgid "Add a new folder" msgid "Add a new folder"

View File

@@ -177,6 +177,7 @@ TEMPLATES = [
"filters": { "filters": {
"markdown": "core.templatetags.renderer.markdown", "markdown": "core.templatetags.renderer.markdown",
"phonenumber": "core.templatetags.renderer.phonenumber", "phonenumber": "core.templatetags.renderer.phonenumber",
"truncate_time": "core.templatetags.renderer.truncate_time",
"format_timedelta": "core.templatetags.renderer.format_timedelta", "format_timedelta": "core.templatetags.renderer.format_timedelta",
"add_attr": "core.templatetags.renderer.add_attr", "add_attr": "core.templatetags.renderer.add_attr",
}, },