mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 03:49:24 +00:00
Optimize barmen timeout and counter state fetch
Le timeout se fait en une seule requête et la récupération de l'état des comptoirs en une seule requête aussi. Grâce à ça, on peut en grande partie retirer le cache pour l'affichage de l'état des comptoirs, ce qui a des implications excellentes en termes d'UX (comme le fait que la redirection vers la page de comptoir ou d'activité aura plus une apparence de truc aléatoire)
This commit is contained in:
@ -23,15 +23,17 @@ from counter.schemas import CounterSchema
|
||||
class CounterController(ControllerBase):
|
||||
@route.get("", response=list[CounterSchema], permissions=[IsRoot])
|
||||
def fetch_all(self):
|
||||
return Counter.objects.all()
|
||||
return Counter.objects.annotate_is_open()
|
||||
|
||||
@route.get("{counter_id}/", response=CounterSchema, permissions=[CanView])
|
||||
def fetch_one(self, counter_id: int):
|
||||
return self.get_object_or_exception(Counter, pk=counter_id)
|
||||
return self.get_object_or_exception(
|
||||
Counter.objects.annotate_is_open(), pk=counter_id
|
||||
)
|
||||
|
||||
@route.get("bar/", response=list[CounterSchema], permissions=[CanView])
|
||||
def fetch_bars(self):
|
||||
counters = list(Counter.objects.filter(type="BAR"))
|
||||
counters = list(Counter.objects.annotate_is_open().filter(type="BAR"))
|
||||
for c in counters:
|
||||
self.check_object_permissions(c)
|
||||
return counters
|
||||
|
@ -358,7 +358,7 @@ class Product(models.Model):
|
||||
|
||||
|
||||
class CounterQuerySet(models.QuerySet):
|
||||
def annotate_has_barman(self, user: User) -> CounterQuerySet:
|
||||
def annotate_has_barman(self, user: User) -> Self:
|
||||
"""Annotate the queryset with the `user_is_barman` field.
|
||||
|
||||
For each counter, this field has value True if the user
|
||||
@ -383,6 +383,29 @@ class CounterQuerySet(models.QuerySet):
|
||||
subquery = user.counters.filter(pk=OuterRef("pk"))
|
||||
return self.annotate(has_annotated_barman=Exists(subquery))
|
||||
|
||||
def annotate_is_open(self) -> Self:
|
||||
"""Annotate tue queryset with the `is_open` field.
|
||||
|
||||
For each counter, if `is_open=True`, then the counter is currently opened.
|
||||
Else the counter is closed.
|
||||
"""
|
||||
return self.annotate(
|
||||
is_open=Exists(
|
||||
Permanency.objects.filter(counter_id=OuterRef("pk"), end=None)
|
||||
)
|
||||
)
|
||||
|
||||
def handle_timeout(self) -> int:
|
||||
"""Disconnect the barmen who are inactive in the given counters.
|
||||
|
||||
Returns:
|
||||
The number of affected rows (ie, the number of timeouted permanences)
|
||||
"""
|
||||
timeout = timezone.now() - timedelta(minutes=settings.SITH_BARMAN_TIMEOUT)
|
||||
return Permanency.objects.filter(
|
||||
counter__in=self, end=None, activity__lt=timeout
|
||||
).update(end=F("activity"))
|
||||
|
||||
|
||||
class Counter(models.Model):
|
||||
name = models.CharField(_("name"), max_length=30)
|
||||
@ -450,20 +473,10 @@ class Counter(models.Model):
|
||||
|
||||
@cached_property
|
||||
def barmen_list(self) -> list[User]:
|
||||
return self.get_barmen_list()
|
||||
|
||||
def get_barmen_list(self) -> list[User]:
|
||||
"""Returns the barman list as list of User.
|
||||
|
||||
Also handle the timeout of the barmen
|
||||
"""
|
||||
perms = self.permanencies.filter(end=None)
|
||||
|
||||
# disconnect barmen who are inactive
|
||||
timeout = timezone.now() - timedelta(minutes=settings.SITH_BARMAN_TIMEOUT)
|
||||
perms.filter(activity__lte=timeout).update(end=F("activity"))
|
||||
|
||||
return [p.user for p in perms.select_related("user")]
|
||||
"""Returns the barman list as list of User."""
|
||||
return [
|
||||
p.user for p in self.permanencies.filter(end=None).select_related("user")
|
||||
]
|
||||
|
||||
def get_random_barman(self) -> User:
|
||||
"""Return a random user being currently a barman."""
|
||||
|
@ -15,20 +15,29 @@
|
||||
import json
|
||||
import re
|
||||
import string
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import timedelta
|
||||
from django.utils.timezone import now
|
||||
from freezegun import freeze_time
|
||||
from model_bakery import baker
|
||||
|
||||
from club.models import Club, Membership
|
||||
from core.baker_recipes import subscriber_user
|
||||
from core.models import User
|
||||
from counter.models import BillingInfo, Counter, Customer, Permanency, Product, Selling
|
||||
from sith.settings import SITH_MAIN_CLUB
|
||||
from counter.models import (
|
||||
BillingInfo,
|
||||
Counter,
|
||||
Customer,
|
||||
Permanency,
|
||||
Product,
|
||||
Selling,
|
||||
)
|
||||
|
||||
|
||||
class TestCounter(TestCase):
|
||||
@ -219,7 +228,7 @@ class TestCounterStats(TestCase):
|
||||
s = Selling(
|
||||
label=barbar.name,
|
||||
product=barbar,
|
||||
club=Club.objects.get(name=SITH_MAIN_CLUB["name"]),
|
||||
club=Club.objects.get(name=settings.SITH_MAIN_CLUB["name"]),
|
||||
counter=cls.counter,
|
||||
unit_price=2,
|
||||
seller=cls.skia,
|
||||
@ -497,6 +506,29 @@ class TestBarmanConnection(TestCase):
|
||||
assert not '<li><a href="/user/1/">S' Kia</a></li>' in str(response.content)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_barman_timeout():
|
||||
"""Test that barmen timeout is well managed."""
|
||||
bar = baker.make(Counter, type="BAR")
|
||||
user = baker.make(User)
|
||||
bar.sellers.add(user)
|
||||
baker.make(Permanency, counter=bar, user=user, start=now())
|
||||
|
||||
qs = Counter.objects.annotate_is_open().filter(pk=bar.pk)
|
||||
|
||||
bar = qs[0]
|
||||
assert bar.is_open
|
||||
assert bar.barmen_list == [user]
|
||||
qs.handle_timeout() # handling timeout before the actual timeout should be no-op
|
||||
assert qs[0].is_open
|
||||
with freeze_time() as frozen_time:
|
||||
frozen_time.tick(timedelta(minutes=settings.SITH_BARMAN_TIMEOUT + 1))
|
||||
qs.handle_timeout()
|
||||
bar = qs[0]
|
||||
assert not bar.is_open
|
||||
assert bar.barmen_list == []
|
||||
|
||||
|
||||
class TestStudentCard(TestCase):
|
||||
"""Tests for adding and deleting Stundent Cards
|
||||
Test that an user can be found with it's student card.
|
||||
|
@ -239,6 +239,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
||||
"""
|
||||
|
||||
model = Counter
|
||||
queryset = Counter.objects.annotate_is_open()
|
||||
template_name = "counter/counter_click.jinja"
|
||||
pk_url_kwarg = "counter_id"
|
||||
current_tab = "counter"
|
||||
|
Reference in New Issue
Block a user