Actually fix wrong counter date

Le serveur ne percevait pas le changement de semestre, parce
que la valeur par défaut passée à la fonction `get_start_of_semester()` était une fonction appelée une seule fois, lors du lancement du serveur. Bref, c'était ça : https://beta.ruff.rs/docs/rules/function-call-in-default-argument/
This commit is contained in:
imperosol 2023-09-08 00:09:21 +02:00
parent 1f23de3fc7
commit a27a130f29
9 changed files with 142 additions and 93 deletions

View File

@ -1031,7 +1031,7 @@ thead {
} }
tbody > tr { tbody > tr {
&:nth-child(even) { &:nth-child(even):not(.highlight) {
background: $primary-neutral-light-color; background: $primary-neutral-light-color;
} }
&.clickable:hover { &.clickable:hover {

View File

@ -17,16 +17,16 @@
import os import os
from datetime import date, timedelta from datetime import date, timedelta
import freezegun
from django.core.cache import cache from django.core.cache import cache
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.core.management import call_command
from django.utils.timezone import now from django.utils.timezone import now
from club.models import Membership from club.models import Membership
from core.models import User, Group, Page, AnonymousUser
from core.markdown import markdown from core.markdown import markdown
from core.utils import get_semester, get_start_of_semester from core.models import AnonymousUser, Group, Page, User
from core.utils import get_semester_code, get_start_of_semester
from sith import settings from sith import settings
""" """
@ -620,48 +620,73 @@ class UserIsInGroupTest(TestCase):
self.assertFalse(self.skia.is_in_group(name="This doesn't exist")) self.assertFalse(self.skia.is_in_group(name="This doesn't exist"))
class UtilsTest(TestCase): class DateUtilsTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.autumn_month = settings.SITH_SEMESTER_START_AUTUMN[0]
cls.autumn_day = settings.SITH_SEMESTER_START_AUTUMN[1]
cls.spring_month = settings.SITH_SEMESTER_START_SPRING[0]
cls.spring_day = settings.SITH_SEMESTER_START_SPRING[1]
cls.autumn_semester_january = date(2025, 1, 4)
cls.autumn_semester_september = date(2024, 9, 4)
cls.autumn_first_day = date(2024, cls.autumn_month, cls.autumn_day)
cls.spring_semester_march = date(2023, 3, 4)
cls.spring_first_day = date(2023, cls.spring_month, cls.spring_day)
def test_get_semester(self): def test_get_semester(self):
autumn_month, autumn_day = settings.SITH_SEMESTER_START_AUTUMN """
spring_month, spring_day = settings.SITH_SEMESTER_START_SPRING Test that the get_semester function returns the correct semester string
"""
self.assertEqual(get_semester_code(self.autumn_semester_january), "A24")
self.assertEqual(get_semester_code(self.autumn_semester_september), "A24")
self.assertEqual(get_semester_code(self.autumn_first_day), "A24")
t1_autumn_day = date(2025, 1, 4) # between 1st January and 15 February self.assertEqual(get_semester_code(self.spring_semester_march), "P23")
t2_autumn_day = date(2024, 9, 1) # between 15 August and 31 December self.assertEqual(get_semester_code(self.spring_first_day), "P23")
t3_autumn_day = date(2024, autumn_month, autumn_day) # on 15 August
t1_spring_day = date(2023, 3, 1) # between 15 February and 15 August
t2_spring_day = date(2023, spring_month, spring_day) # on 15 February
self.assertEqual(get_semester(t1_autumn_day), "A24")
self.assertEqual(get_semester(t2_autumn_day), "A24")
self.assertEqual(get_semester(t3_autumn_day), "A24")
self.assertEqual(get_semester(t1_spring_day), "P23")
self.assertEqual(get_semester(t2_spring_day), "P23")
def test_get_start_of_semester(self):
autumn_month, autumn_day = settings.SITH_SEMESTER_START_AUTUMN
spring_month, spring_day = settings.SITH_SEMESTER_START_SPRING
t1_autumn_day = date(2025, 1, 4) # between 1st January and 15 February
t2_autumn_day = date(2024, 9, 1) # between 15 August and 31 December
t3_autumn_day = date(2024, autumn_month, autumn_day) # on 15 August
t1_spring_day = date(2023, 3, 1) # between 15 February and 15 August
t2_spring_day = date(2023, spring_month, spring_day) # on 15 February
def test_get_start_of_semester_fixed_date(self):
"""
Test that the get_start_of_semester correctly the starting date of the semester.
"""
automn_2024 = date(2024, self.autumn_month, self.autumn_day)
self.assertEqual( self.assertEqual(
get_start_of_semester(t1_autumn_day), date(2024, autumn_month, autumn_day) get_start_of_semester(self.autumn_semester_january), automn_2024
) )
self.assertEqual( self.assertEqual(
get_start_of_semester(t2_autumn_day), date(2024, autumn_month, autumn_day) get_start_of_semester(self.autumn_semester_september), automn_2024
)
self.assertEqual(
get_start_of_semester(t3_autumn_day), date(2024, autumn_month, autumn_day)
)
self.assertEqual(
get_start_of_semester(t1_spring_day), date(2023, spring_month, spring_day)
)
self.assertEqual(
get_start_of_semester(t2_spring_day), date(2023, spring_month, spring_day)
) )
self.assertEqual(get_start_of_semester(self.autumn_first_day), automn_2024)
spring_2023 = date(2023, self.spring_month, self.spring_day)
self.assertEqual(get_start_of_semester(self.spring_semester_march), spring_2023)
self.assertEqual(get_start_of_semester(self.spring_first_day), spring_2023)
def test_get_start_of_semester_today(self):
"""
Test that the get_start_of_semester returns the start of the current semester
when no date is given
"""
with freezegun.freeze_time(self.autumn_semester_september):
self.assertEqual(get_start_of_semester(), self.autumn_first_day)
with freezegun.freeze_time(self.spring_semester_march):
self.assertEqual(get_start_of_semester(), self.spring_first_day)
def test_get_start_of_semester_changing_date(self):
"""
Test that the get_start_of_semester correctly gives the starting date of the semester,
even when the semester changes while the server isn't restarted.
"""
spring_2023 = date(2023, self.spring_month, self.spring_day)
autumn_2023 = date(2023, self.autumn_month, self.autumn_day)
mid_spring = spring_2023 + timedelta(days=45)
mid_autumn = autumn_2023 + timedelta(days=45)
with freezegun.freeze_time(mid_spring) as frozen_time:
self.assertEqual(get_start_of_semester(), spring_2023)
# forward time to the middle of the next semester
frozen_time.move_to(mid_autumn)
self.assertEqual(get_start_of_semester(), autumn_2023)

View File

@ -15,20 +15,19 @@
# #
import os import os
import subprocess
import re import re
import subprocess
# Image utils
from io import BytesIO
from datetime import date from datetime import date
from PIL import ExifTags # Image utils
from io import BytesIO
from typing import Optional
import PIL import PIL
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from PIL import ExifTags
from django.utils import timezone
def get_git_revision_short_hash() -> str: def get_git_revision_short_hash() -> str:
@ -44,50 +43,54 @@ def get_git_revision_short_hash() -> str:
return "" return ""
def get_start_of_semester(today=date.today()) -> date: def get_start_of_semester(today: Optional[date] = None) -> date:
""" """
This function determines in which semester the given date falls and returns the start date of the corresponding semester. Return the date of the start of the semester of the given date.
If no date is given, today's date is used. If no date is given, return the start date of the current semester.
Parameters: The current semester is computed as follows:
`today` (date, optional): The date to be tested. Defaults to `date.today()`.
Returns: - If the date is between 15/08 and 31/12 => Autumn semester.
`date`: The start date of the semester to which the given date belongs. - If the date is between 01/01 and 15/02 => Autumn semester of the previous year.
- If the date is between 15/02 and 15/08 => Spring semester
Context: :param today: the date to use to compute the semester. If None, use today's date.
- If the date is between 15/08 and 31/12, it belongs to the autumn semester. :return: the date of the start of the semester
- If the date is between 01/01 and 15/02, it also belongs to the autumn semester, but for the year before the given date.
- Otherwise, if the date is between 15/02 and 15/08, it belongs to the spring semester.
""" """
autumn_month, autumn_day = settings.SITH_SEMESTER_START_AUTUMN if today is None:
spring_month, spring_day = settings.SITH_SEMESTER_START_SPRING today = timezone.now().date()
autumn = date(today.year, autumn_month, autumn_day) autumn = date(today.year, *settings.SITH_SEMESTER_START_AUTUMN)
spring = date(today.year, spring_month, spring_day) spring = date(today.year, *settings.SITH_SEMESTER_START_SPRING)
# between 15/08 (included) and 31/12 -> autumn semester if today >= autumn: # between 15/08 (included) and 31/12 -> autumn semester
if today >= autumn:
return autumn return autumn
if today >= spring: # between 15/02 (included) and 15/08 -> spring semester
# between 15/02 (included) and 15/08 -> spring semester
if today >= spring:
return spring return spring
# between 01/01 and 15/02 -> autumn semester of the previous year
# else : between 01/01 and 15/02 -> autumn semester where the year is the one before of the given date
return autumn.replace(year=autumn.year - 1) return autumn.replace(year=autumn.year - 1)
def get_semester(d=date.today()): def get_semester_code(d: Optional[date] = None) -> str:
"""
Return the semester code of the given date.
If no date is given, return the semester code of the current semester.
The semester code is an upper letter (A for autumn, P for spring),
followed by the last two digits of the year.
For example, the autumn semester of 2018 is "A18".
:param d: the date to use to compute the semester. If None, use today's date.
:return: the semester code corresponding to the given date
"""
if d is None:
d = timezone.now().date()
start = get_start_of_semester(d) start = get_start_of_semester(d)
if ( if (start.month, start.day) == settings.SITH_SEMESTER_START_AUTUMN:
start.month == settings.SITH_SEMESTER_START_AUTUMN[0]
and start.day == settings.SITH_SEMESTER_START_AUTUMN[1]
):
return "A" + str(start.year)[-2:] return "A" + str(start.year)[-2:]
else: return "P" + str(start.year)[-2:]
return "P" + str(start.year)[-2:]
def file_exist(path): def file_exist(path):

View File

@ -15,7 +15,7 @@
# #
from __future__ import annotations from __future__ import annotations
from typing import Tuple from typing import Tuple, Optional
from django.db import models from django.db import models
from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists
@ -536,10 +536,7 @@ class Counter(models.Model):
.order_by("-perm_sum") .order_by("-perm_sum")
) )
def get_stats_starting_date(self) -> date: def get_top_customers(self, since: Optional[date] = None) -> QuerySet:
return get_start_of_semester()
def get_top_customers(self, since=get_start_of_semester()) -> QuerySet:
""" """
Return a QuerySet querying the money spent by customers of this counter Return a QuerySet querying the money spent by customers of this counter
since the specified date, ordered by descending amount of money spent. since the specified date, ordered by descending amount of money spent.
@ -549,6 +546,8 @@ class Counter(models.Model):
- the nickname of the customer - the nickname of the customer
- the amount of money spent by the customer - the amount of money spent by the customer
""" """
if since is None:
since = get_start_of_semester()
return ( return (
self.sellings.filter(date__gte=since) self.sellings.filter(date__gte=since)
.annotate( .annotate(
@ -571,15 +570,17 @@ class Counter(models.Model):
.order_by("-selling_sum") .order_by("-selling_sum")
) )
def get_total_sales(self, since=get_start_of_semester()) -> CurrencyField: def get_total_sales(self, since=None) -> CurrencyField:
""" """
Compute and return the total turnover of this counter Compute and return the total turnover of this counter
since the date specified in parameter (by default, since the start of the current since the date specified in parameter (by default, since the start of the current
semester) semester)
:param since: timestamp from which to perform the calculation :param since: timestamp from which to perform the calculation
:type since: datetime | date :type since: datetime | date | None
:return: Total revenue earned at this counter :return: Total revenue earned at this counter
""" """
if since is None:
since = get_start_of_semester()
if isinstance(since, date): if isinstance(since, date):
since = datetime.combine(since, datetime.min.time()) since = datetime.combine(since, datetime.min.time())
total = self.sellings.filter(date__gte=since).aggregate( total = self.sellings.filter(date__gte=since).aggregate(

View File

@ -11,7 +11,9 @@
{% block content %} {% block content %}
<h3>{% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}</h3> <h3>{% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}</h3>
<h4>{% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %} ({{ counter.get_stats_starting_date() }})</h4> <h4>
{% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %} ({{ current_semester }})
</h4>
<table> <table>
<thead> <thead>
<tr> <tr>
@ -35,7 +37,9 @@
</tbody> </tbody>
</table> </table>
<h4>{% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %} ({{ counter.get_stats_starting_date() }})</h4> <h4>
{% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %} ({{ current_semester }})
</h4>
<table> <table>
<thead> <thead>
<tr> <tr>

View File

@ -48,7 +48,7 @@ import pytz
from datetime import timedelta, datetime from datetime import timedelta, datetime
from http import HTTPStatus from http import HTTPStatus
from core.utils import get_start_of_semester from core.utils import get_start_of_semester, get_semester_code
from core.views import CanViewMixin, TabedViewMixin, CanEditMixin from core.views import CanViewMixin, TabedViewMixin, CanEditMixin
from core.views.forms import LoginForm from core.views.forms import LoginForm
from core.models import User from core.models import User
@ -1361,6 +1361,7 @@ class CounterStatView(DetailView, CounterAdminMixin):
kwargs.update( kwargs.update(
{ {
"counter": counter, "counter": counter,
"current_semester": get_semester_code(),
"total_sellings": counter.get_total_sales(since=semester_start), "total_sellings": counter.get_total_sales(since=semester_start),
"top_customers": counter.get_top_customers(since=semester_start)[:100], "top_customers": counter.get_top_customers(since=semester_start)[:100],
"top_barman": office_hours[:100], "top_barman": office_hours[:100],

18
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
[[package]] [[package]]
name = "alabaster" name = "alabaster"
@ -550,6 +550,20 @@ files = [
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
] ]
[[package]]
name = "freezegun"
version = "1.2.2"
description = "Let your Python tests travel through time"
optional = false
python-versions = ">=3.6"
files = [
{file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"},
{file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"},
]
[package.dependencies]
python-dateutil = ">=2.7"
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.4" version = "3.4"
@ -1558,4 +1572,4 @@ testing = ["coverage"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "9d38fb0dd50ef0a154bd6690afcb24be9fbdb2adbba1c4762b1ed0cdb9508eb2" content-hash = "62519616aff5a472dac3dd8071a6404b1ee8eab12a197af717a0520f7ded0331"

View File

@ -59,6 +59,7 @@ testing = ["coverage"]
docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"]
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
freezegun = "^1.2.2" # used to test time-dependent code
django-debug-toolbar = "^4.0.0" django-debug-toolbar = "^4.0.0"
ipython = "^7.28.0" ipython = "^7.28.0"
black = "^23.3.0" black = "^23.3.0"

View File

@ -31,7 +31,7 @@ from django.core.exceptions import ValidationError
from datetime import timedelta, date from datetime import timedelta, date
from core.models import User from core.models import User
from core.utils import get_start_of_semester, get_semester from core.utils import get_start_of_semester, get_semester_code
from club.models import Club from club.models import Club
@ -164,14 +164,14 @@ class TrombiUser(models.Model):
if m.description: if m.description:
role += " (%s)" % m.description role += " (%s)" % m.description
if m.end_date: if m.end_date:
end_date = get_semester(m.end_date) end_date = get_semester_code(m.end_date)
else: else:
end_date = "" end_date = ""
TrombiClubMembership( TrombiClubMembership(
user=self, user=self,
club=str(m.club), club=str(m.club),
role=role[:64], role=role[:64],
start=get_semester(m.start_date), start=get_semester_code(m.start_date),
end=end_date, end=end_date,
).save() ).save()