7 Commits

Author SHA1 Message Date
thomas girod 455b81cff6 Merge pull request #1424 from ae-utbm/taiste
Basket timeout, clic limit, club-election link, CGV, counter barmen and other
2026-06-06 09:46:41 +02:00
thomas girod 9ceb51a54e Merge pull request #1404 from ae-utbm/taiste
Club roles, club links, notifications and other
2026-05-22 09:43:08 +02:00
Titouan 790a1e15b1 Merge pull request #1383 from ae-utbm/taiste
MAJ cotisation, CI, style et fix
2026-05-11 13:03:22 +02:00
thomas girod b2ffcd3a37 Merge pull request #1365 from ae-utbm/taiste
Product prices, club list page rework and bug fixes
2026-05-01 19:14:03 +02:00
Titouan ca37996d6a Merge pull request #1332 from ae-utbm/taiste
Stats & Whitelist, Eurockéenne, fix pagination, Vite 8, delete unused settings
2026-03-29 16:35:16 +02:00
Titouan 173311c1d5 Merge pull request #1315 from ae-utbm/taiste
Product history, formula management, test election
2026-03-12 11:33:45 +01:00
thomas girod 2995823d6e Merge pull request #1293 from ae-utbm/taiste
Refactors, updates and db optimisations
2026-02-13 15:25:04 +01:00
6 changed files with 29 additions and 73 deletions
+5 -7
View File
@@ -39,7 +39,6 @@ from counter.models import (
Customer, Customer,
Eticket, Eticket,
InvoiceCall, InvoiceCall,
Permanency,
Price, Price,
Product, Product,
ProductFormula, ProductFormula,
@@ -152,13 +151,12 @@ class CounterLoginForm(LoginForm):
raise ValidationError( raise ValidationError(
message=_("You are not a barman of this counter."), code="not_barman" message=_("You are not a barman of this counter."), code="not_barman"
) )
if Permanency.objects.filter(end=None, user=user).exists():
if user in self.request.barmen: if user in self.request.barmen:
message = _("You are already logged in this counter.") message = (
elif user in self.counter.barmen_list: _("You are already logged in this counter.")
message = _("You are already logged in another counter.") if user in self.counter.barmen_list
else: else _("You are already logged in another counter.")
message = _("You are already logged on another device") )
raise ValidationError(message=message, code="already_logged_in") raise ValidationError(message=message, code="already_logged_in")
+19 -21
View File
@@ -1,7 +1,8 @@
from typing import TYPE_CHECKING, Callable from typing import TYPE_CHECKING, Callable
from django.db.models import Exists, OuterRef
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject, empty
from core.models import User from core.models import User
from counter.models import Permanency from counter.models import Permanency
@@ -10,31 +11,20 @@ if TYPE_CHECKING:
from django.contrib.sessions.backends.base import SessionBase from django.contrib.sessions.backends.base import SessionBase
SESSION_PERMANENCES_KEY = "permanence_ids" SESSION_BARMEN_KEY = "barmen_ids"
def get_cached_barmen(request: HttpRequest) -> set[User]: def get_cached_barmen(request: HttpRequest) -> set[User]:
if not hasattr(request, "_cached_barmen"): if not hasattr(request, "_cached_barmen"):
session: SessionBase = request.session session: SessionBase = request.session
barmen_ids = session.get(SESSION_BARMEN_KEY, [])
if session_ids := session.get(SESSION_PERMANENCES_KEY, None): if barmen_ids:
# Get ongoing permanences which id is in session. request._cached_barmen = set(
# Note : we store permanence ids rather than user id to be sure User.objects.filter(
# not to wrongfully mark someone as logged here, Exists(Permanency.objects.filter(user=OuterRef("pk"), end=None)),
# even if it logged out then logged in elsewhere. id__in=barmen_ids,
permanences = ( )
Permanency.objects.filter(end=None, id__in=session_ids)
.order_by("id")
.select_related("user")
) )
# if the list of permanences occurring on this device has changed
# since the last page load, change the ids stored in session
real_ids = [p.id for p in permanences]
if real_ids != session_ids:
session[SESSION_PERMANENCES_KEY] = real_ids
request._cached_barmen = {p.user for p in permanences}
else: else:
request._cached_barmen = set() request._cached_barmen = set()
@@ -63,4 +53,12 @@ class BarmenMiddleware:
def __call__(self, request: HttpRequest): def __call__(self, request: HttpRequest):
request.barmen = SimpleLazyObject(lambda: get_cached_barmen(request)) request.barmen = SimpleLazyObject(lambda: get_cached_barmen(request))
return self.get_response(request) response = self.get_response(request)
if request.barmen._wrapped is not empty and {
b.id for b in request.barmen
} != set(request.session.get(SESSION_BARMEN_KEY, [])):
# update the session data only if `session.barmen`
# has been accessed and modified.
request.session[SESSION_BARMEN_KEY] = [b.id for b in request.barmen]
return response
+1 -1
View File
@@ -1105,7 +1105,7 @@ class Permanency(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
start = models.DateTimeField(_("start date")) start = models.DateTimeField(_("start date"))
end = models.DateTimeField(_("end date"), null=True, blank=True, db_index=True) end = models.DateTimeField(_("end date"), null=True, db_index=True)
activity = models.DateTimeField(_("last activity date"), auto_now=True) activity = models.DateTimeField(_("last activity date"), auto_now=True)
class Meta: class Meta:
+1 -36
View File
@@ -760,10 +760,10 @@ class TestBarmanConnection(TestCase):
assert last_perm.counter == self.counter assert last_perm.counter == self.counter
assert last_perm.user == self.barman assert last_perm.user == self.barman
assert last_perm.end is None assert last_perm.end is None
assert self.barman in response.wsgi_request.barmen
response = self.client.get( response = self.client.get(
self.detail_url, {"username": self.barman.username, "password": "plop"} self.detail_url, {"username": self.barman.username, "password": "plop"}
) )
assert self.barman in response.wsgi_request.barmen
assert response.context_data.get("barmen") == [self.barman] assert response.context_data.get("barmen") == [self.barman]
soup = BeautifulSoup(response.text, "lxml") soup = BeautifulSoup(response.text, "lxml")
assert soup.find("form", id="select-user-form") is not None assert soup.find("form", id="select-user-form") is not None
@@ -804,41 +804,6 @@ class TestBarmanConnection(TestCase):
) )
self.assert_counter_login_fails(self.barman) self.assert_counter_login_fails(self.barman)
def test_barman_already_logged_in_another_device(self):
"""Test when the barman is already logged in the current counter on another device."""
other_client = Client()
other_client.post(
self.login_url, {"username": self.barman.username, "password": "plop"}
)
self.assert_counter_login_fails(self.barman)
def test_barman_login_elsewhere(self):
"""Test when the barman log himself out then log in on another device."""
self.client.post(
self.login_url, {"username": self.barman.username, "password": "plop"}
)
other_client = Client()
other_client.post(
reverse("counter:logout", kwargs={"counter_id": self.counter.id}),
data={"user_id": self.barman.id},
)
response = other_client.post(
self.login_url, {"username": self.barman.username, "password": "plop"}
)
assert response.status_code == 200
assert response.headers["HX-Redirect"] == self.detail_url
# the barmen should now be logged in `other_client`...
response = other_client.get(
self.detail_url, {"username": self.barman.username, "password": "plop"}
)
assert self.barman in response.wsgi_request.barmen
# ... but not in `self.client`
response = self.client.get(
self.detail_url, {"username": self.barman.username, "password": "plop"}
)
assert self.barman not in response.wsgi_request.barmen
def test_barman_already_logged_elsewhere(self): def test_barman_already_logged_elsewhere(self):
"""Test when the barman is already logged in another counter.""" """Test when the barman is already logged in another counter."""
other_counter = baker.make(Counter, type="BAR") other_counter = baker.make(Counter, type="BAR")
+2 -3
View File
@@ -30,7 +30,6 @@ from django.views.generic.edit import FormView
from core.auth.mixins import CanViewMixin from core.auth.mixins import CanViewMixin
from core.views import FragmentMixin, UseFragmentsMixin from core.views import FragmentMixin, UseFragmentsMixin
from counter.forms import CounterLoginForm, GetUserForm from counter.forms import CounterLoginForm, GetUserForm
from counter.middleware import SESSION_PERMANENCES_KEY
from counter.models import Counter, Permanency from counter.models import Counter, Permanency
from counter.utils import is_logged_in_counter from counter.utils import is_logged_in_counter
from counter.views.mixins import CounterTabsMixin from counter.views.mixins import CounterTabsMixin
@@ -59,8 +58,8 @@ class CounterLoginFragment(FragmentMixin, SingleObjectMixin, FormView):
def form_valid(self, form: CounterLoginForm): def form_valid(self, form: CounterLoginForm):
user = form.get_user() user = form.get_user()
perm = self.object.permanencies.create(user=user, start=timezone.now()) self.object.permanencies.create(user=user, start=timezone.now())
self.request.session.setdefault(SESSION_PERMANENCES_KEY, []).append(perm.id) self.request.barmen.add(user)
self.success_url = reverse( self.success_url = reverse(
"counter:details", kwargs={"counter_id": self.object.id} "counter:details", kwargs={"counter_id": self.object.id}
) )
-4
View File
@@ -3217,10 +3217,6 @@ msgstr "Vous êtes déjà connecté à ce comptoir."
msgid "You are already logged in another counter." msgid "You are already logged in another counter."
msgstr "Vous êtes déjà connecté à un autre comptoir." msgstr "Vous êtes déjà connecté à un autre comptoir."
#: counter/forms.py
msgid "You are already logged on another device"
msgstr "Vous êtes déjà connecté sur un autre appareil"
#: counter/forms.py #: counter/forms.py
msgid "Regular barmen" msgid "Regular barmen"
msgstr "Barmen réguliers" msgstr "Barmen réguliers"