From a27a130f290c08242bd00b2b3adba69376ac2d44 Mon Sep 17 00:00:00 2001 From: imperosol Date: Fri, 8 Sep 2023 00:09:21 +0200 Subject: [PATCH] Actually fix wrong counter date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/ --- core/static/core/style.scss | 2 +- core/tests.py | 107 ++++++++++++++++---------- core/utils.py | 75 +++++++++--------- counter/models.py | 15 ++-- counter/templates/counter/stats.jinja | 8 +- counter/views.py | 3 +- poetry.lock | 18 ++++- pyproject.toml | 1 + trombi/models.py | 6 +- 9 files changed, 142 insertions(+), 93 deletions(-) diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 7abfa8ca..9ba72e7d 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -1031,7 +1031,7 @@ thead { } tbody > tr { - &:nth-child(even) { + &:nth-child(even):not(.highlight) { background: $primary-neutral-light-color; } &.clickable:hover { diff --git a/core/tests.py b/core/tests.py index d644caab..61d497a5 100644 --- a/core/tests.py +++ b/core/tests.py @@ -17,16 +17,16 @@ import os from datetime import date, timedelta +import freezegun from django.core.cache import cache from django.test import Client, TestCase from django.urls import reverse -from django.core.management import call_command from django.utils.timezone import now from club.models import Membership -from core.models import User, Group, Page, AnonymousUser 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 """ @@ -620,48 +620,73 @@ class UserIsInGroupTest(TestCase): 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): - 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 - 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 - - 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 + self.assertEqual(get_semester_code(self.spring_semester_march), "P23") + self.assertEqual(get_semester_code(self.spring_first_day), "P23") + 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( - 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( - get_start_of_semester(t2_autumn_day), date(2024, autumn_month, autumn_day) - ) - 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) + get_start_of_semester(self.autumn_semester_september), automn_2024 ) + 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) diff --git a/core/utils.py b/core/utils.py index 5f115ab1..d30e3ebf 100644 --- a/core/utils.py +++ b/core/utils.py @@ -15,20 +15,19 @@ # import os -import subprocess import re - -# Image utils - -from io import BytesIO +import subprocess from datetime import date -from PIL import ExifTags +# Image utils +from io import BytesIO +from typing import Optional import PIL - from django.conf import settings from django.core.files.base import ContentFile +from PIL import ExifTags +from django.utils import timezone def get_git_revision_short_hash() -> str: @@ -44,50 +43,54 @@ def get_git_revision_short_hash() -> str: 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. - If no date is given, today's date is used. + Return the date of the start of the semester of the given date. + If no date is given, return the start date of the current semester. - Parameters: - `today` (date, optional): The date to be tested. Defaults to `date.today()`. + The current semester is computed as follows: - Returns: - `date`: The start date of the semester to which the given date belongs. + - If the date is between 15/08 and 31/12 => Autumn semester. + - 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: - - If the date is between 15/08 and 31/12, it belongs to the autumn 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. + :param today: the date to use to compute the semester. If None, use today's date. + :return: the date of the start of the semester """ - autumn_month, autumn_day = settings.SITH_SEMESTER_START_AUTUMN - spring_month, spring_day = settings.SITH_SEMESTER_START_SPRING + if today is None: + today = timezone.now().date() - autumn = date(today.year, autumn_month, autumn_day) - spring = date(today.year, spring_month, spring_day) + autumn = date(today.year, *settings.SITH_SEMESTER_START_AUTUMN) + spring = date(today.year, *settings.SITH_SEMESTER_START_SPRING) - # between 15/08 (included) and 31/12 -> autumn semester - if today >= autumn: + if today >= autumn: # between 15/08 (included) and 31/12 -> autumn semester return autumn - - # between 15/02 (included) and 15/08 -> spring semester - if today >= spring: + if today >= spring: # between 15/02 (included) and 15/08 -> spring semester return spring - - # else : between 01/01 and 15/02 -> autumn semester where the year is the one before of the given date + # between 01/01 and 15/02 -> autumn semester of the previous year 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) - if ( - start.month == settings.SITH_SEMESTER_START_AUTUMN[0] - and start.day == settings.SITH_SEMESTER_START_AUTUMN[1] - ): + if (start.month, start.day) == settings.SITH_SEMESTER_START_AUTUMN: return "A" + str(start.year)[-2:] - else: - return "P" + str(start.year)[-2:] + return "P" + str(start.year)[-2:] def file_exist(path): diff --git a/counter/models.py b/counter/models.py index 3a503ea7..476aaf13 100644 --- a/counter/models.py +++ b/counter/models.py @@ -15,7 +15,7 @@ # from __future__ import annotations -from typing import Tuple +from typing import Tuple, Optional from django.db import models from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists @@ -536,10 +536,7 @@ class Counter(models.Model): .order_by("-perm_sum") ) - def get_stats_starting_date(self) -> date: - return get_start_of_semester() - - def get_top_customers(self, since=get_start_of_semester()) -> QuerySet: + def get_top_customers(self, since: Optional[date] = None) -> QuerySet: """ Return a QuerySet querying the money spent by customers of this counter 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 amount of money spent by the customer """ + if since is None: + since = get_start_of_semester() return ( self.sellings.filter(date__gte=since) .annotate( @@ -571,15 +570,17 @@ class Counter(models.Model): .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 since the date specified in parameter (by default, since the start of the current semester) :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 """ + if since is None: + since = get_start_of_semester() if isinstance(since, date): since = datetime.combine(since, datetime.min.time()) total = self.sellings.filter(date__gte=since).aggregate( diff --git a/counter/templates/counter/stats.jinja b/counter/templates/counter/stats.jinja index 7fe668fd..d6cc14f0 100644 --- a/counter/templates/counter/stats.jinja +++ b/counter/templates/counter/stats.jinja @@ -11,7 +11,9 @@ {% block content %}

{% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}

-

{% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %} ({{ counter.get_stats_starting_date() }})

+

+ {% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %} ({{ current_semester }}) +

@@ -35,7 +37,9 @@
-

{% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %} ({{ counter.get_stats_starting_date() }})

+

+ {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %} ({{ current_semester }}) +

diff --git a/counter/views.py b/counter/views.py index 483e13f5..6bbc819d 100644 --- a/counter/views.py +++ b/counter/views.py @@ -48,7 +48,7 @@ import pytz from datetime import timedelta, datetime 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.forms import LoginForm from core.models import User @@ -1361,6 +1361,7 @@ class CounterStatView(DetailView, CounterAdminMixin): kwargs.update( { "counter": counter, + "current_semester": get_semester_code(), "total_sellings": counter.get_total_sales(since=semester_start), "top_customers": counter.get_top_customers(since=semester_start)[:100], "top_barman": office_hours[:100], diff --git a/poetry.lock b/poetry.lock index ff8f76b3..23ef0bda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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]] name = "alabaster" @@ -550,6 +550,20 @@ files = [ {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]] name = "idna" version = "3.4" @@ -1558,4 +1572,4 @@ testing = ["coverage"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "9d38fb0dd50ef0a154bd6690afcb24be9fbdb2adbba1c4762b1ed0cdb9508eb2" +content-hash = "62519616aff5a472dac3dd8071a6404b1ee8eab12a197af717a0520f7ded0331" diff --git a/pyproject.toml b/pyproject.toml index d2fca6f0..cb1a6aaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ testing = ["coverage"] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] [tool.poetry.dev-dependencies] +freezegun = "^1.2.2" # used to test time-dependent code django-debug-toolbar = "^4.0.0" ipython = "^7.28.0" black = "^23.3.0" diff --git a/trombi/models.py b/trombi/models.py index e4439c1a..18f36526 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -31,7 +31,7 @@ from django.core.exceptions import ValidationError from datetime import timedelta, date 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 @@ -164,14 +164,14 @@ class TrombiUser(models.Model): if m.description: role += " (%s)" % m.description if m.end_date: - end_date = get_semester(m.end_date) + end_date = get_semester_code(m.end_date) else: end_date = "" TrombiClubMembership( user=self, club=str(m.club), role=role[:64], - start=get_semester(m.start_date), + start=get_semester_code(m.start_date), end=end_date, ).save()