diff --git a/app/but/jury_but.py b/app/but/jury_but.py index a7a37ab6..3c1e1b18 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -526,7 +526,7 @@ class DecisionsProposeesAnnee(DecisionsProposees): return formsemestre.annee_scolaire() def annee_scolaire_str(self) -> str: - "L'année scolaire, eg '2021 - 2022'" + "L'année scolaire, eg '2021 - 2022' ou '2021' en hémisphère sud" formsemestre = self.formsemestre_impair or self.formsemestre_pair return formsemestre.annee_scolaire_str().replace(" ", "") diff --git a/app/models/config.py b/app/models/config.py index f137cccc..e869a93a 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -355,7 +355,9 @@ class ScoDocSiteConfig(models.ScoDocModel): @classmethod def get_month_debut_periode2(cls) -> int: - """Mois de début de l'année scolaire.""" + """Mois de début de la seconde période (semestre) de l'année. + Par défaut, 12 (décembre). Sera souvent juillet en hémisphère sud. + """ return cls._get_int_field("month_debut_periode2", scu.MONTH_DEBUT_PERIODE2) @classmethod diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index dc08a847..7e0eb1b7 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -699,18 +699,27 @@ class FormSemestre(models.ScoDocModel): """ if self.semestre_id <= 0: return False # formations sans semestres + # août (8) en métropole, janvier (1) en hémisphère sud: + month_debut = ScoDocSiteConfig.get_month_debut_annee_scolaire() + if month_debut < 4: + # Hémisphère sud: utilise date début deuxième période + # typiquement juillet + pivot = ScoDocSiteConfig.get_month_debut_periode2() + return ( + # impair commençaant en fin d'année civile + (self.semestre_id % 2 and self.date_debut.month >= pivot) + or + # pair commençant en début d'année civile + ((not self.semestre_id % 2) and self.date_debut.month < pivot) + ) + + # Hémisphère nord return ( # impair - ( - self.semestre_id % 2 - and self.date_debut.month < scu.MONTH_DEBUT_ANNEE_SCOLAIRE - ) + (self.semestre_id % 2 and self.date_debut.month < month_debut) or # pair - ( - (not self.semestre_id % 2) - and self.date_debut.month >= scu.MONTH_DEBUT_ANNEE_SCOLAIRE - ) + ((not self.semestre_id % 2) and self.date_debut.month >= month_debut) ) @classmethod @@ -719,8 +728,8 @@ class FormSemestre(models.ScoDocModel): date_debut: datetime.date, year=False, periode=None, - mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE, - mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, + mois_pivot_annee: int | None = None, + mois_pivot_periode: int | None = None, ) -> bool: """Vrai si la date_debut est dans la période indiquée (1,2,0) du semestre `periode` de l'année scolaire indiquée @@ -734,6 +743,10 @@ class FormSemestre(models.ScoDocModel): """ if not year: year = scu.annee_scolaire() + if mois_pivot_annee is None: + mois_pivot_annee = ScoDocSiteConfig.get_month_debut_annee_scolaire() + if mois_pivot_periode is None: + mois_pivot_periode = ScoDocSiteConfig.get_month_debut_periode2() # n'utilise pas le jour pivot jour_pivot_annee = jour_pivot_periode = 1 # calcule l'année universitaire et la période @@ -758,10 +771,10 @@ class FormSemestre(models.ScoDocModel): def comp_periode( cls, date_debut: datetime, - mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE, - mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, - jour_pivot_annee=1, - jour_pivot_periode=1, + mois_pivot_annee: int | None = None, + mois_pivot_periode: int | None = None, + jour_pivot_annee: int = 1, + jour_pivot_periode: int = 1, ) -> tuple[int, int]: """Calcule la session associée à un formsemestre commençant en date_debut sous la forme (année, période) @@ -782,6 +795,10 @@ class FormSemestre(models.ScoDocModel): pp < pa -----------------|-------------------|----------------> (A-1, P:1) pp (A-1, P:2) pa (A, P:1) """ + if mois_pivot_annee is None: + mois_pivot_annee = ScoDocSiteConfig.get_month_debut_annee_scolaire() + if mois_pivot_periode is None: + mois_pivot_periode = ScoDocSiteConfig.get_month_debut_periode2() pivot_annee = 100 * mois_pivot_annee + jour_pivot_annee pivot_periode = 100 * mois_pivot_periode + jour_pivot_periode pivot_sem = 100 * date_debut.month + date_debut.day @@ -930,7 +947,7 @@ class FormSemestre(models.ScoDocModel): return scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month) def annee_scolaire_str(self): - "2021 - 2022" + """2021 - 2022 (ou "2021" si hémisphère sud)""" return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month) def mois_debut(self) -> str: diff --git a/app/scodoc/codes_cursus.py b/app/scodoc/codes_cursus.py index e796b9b4..3c4e6d15 100644 --- a/app/scodoc/codes_cursus.py +++ b/app/scodoc/codes_cursus.py @@ -778,7 +778,7 @@ class CursusMasterLMD(TypeCursus): TYPE_CURSUS = CodesCursus.MasterLMD NAME = "Master LMD" NB_SEM = 4 - COMPENSATION_UE = True # variabale inutilisée + COMPENSATION_UE = True # variable inutilisée UNUSED_CODES = set((ADC, ATT, ATB)) diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 25245424..27ece135 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -1429,13 +1429,23 @@ def timedate_human_repr(): def annee_scolaire_repr(year, month): - """representation de l'annee scolaire : '2009 - 2010' + """Représentation de l'annee scolaire : '2009 - 2010' à partir d'une date. + Dans l'hémisphère sud, l'année scolaire coincide avec l'année civile. + On considère que si le mois de début de l'année scolaire est au + premier trimestre (jan-fev-mar), alors l'affichage n'indique que l'année + en cours (eg "2024"). """ - if month >= MONTH_DEBUT_ANNEE_SCOLAIRE: # apres le 1er aout + from app.models.config import ScoDocSiteConfig + + month_debut = ScoDocSiteConfig.get_month_debut_annee_scolaire() + if month_debut < 4: + # Hémisphère sud: + return str(year) if month >= month_debut else str(year - 1) + # Hémisphère nord + if month >= month_debut: # par ex. apres le 1er aout return f"{year} - {year + 1}" - else: - return f"{year - 1} - {year}" + return f"{year - 1} - {year}" def annee_scolaire() -> int: @@ -1450,10 +1460,12 @@ def annee_scolaire_debut(year, month) -> int: Par défaut (hémisphère nord), l'année du mois de août précédent la date indiquée. """ - if int(month) >= MONTH_DEBUT_ANNEE_SCOLAIRE: + from app.models.config import ScoDocSiteConfig + + month_debut = ScoDocSiteConfig.get_month_debut_annee_scolaire() + if int(month) >= month_debut: return int(year) - else: - return int(year) - 1 + return int(year) - 1 def date_debut_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime: @@ -1461,11 +1473,15 @@ def date_debut_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime Si annee_sco n'est pas spécifié, année courante (par défaut, l'année scolaire en métropole commence le 1er aout) """ + from app.models.config import ScoDocSiteConfig + if annee_sco is None: annee_sco = annee_scolaire() try: return datetime.datetime( - year=annee_sco, month=MONTH_DEBUT_ANNEE_SCOLAIRE, day=1 + year=annee_sco, + month=ScoDocSiteConfig.get_month_debut_annee_scolaire(), + day=1, ) except ValueError as exc: raise ScoValueError("année scolaire invalide") from exc @@ -1478,11 +1494,15 @@ def date_fin_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime: # on prend la date de début de l'année scolaire suivante, # et on lui retire 1 jour. # On s'affranchit ainsi des problèmes de durées de mois. + from app.models.config import ScoDocSiteConfig + if annee_sco is None: annee_sco = annee_scolaire() try: return datetime.datetime( - year=annee_sco + 1, month=MONTH_DEBUT_ANNEE_SCOLAIRE, day=1 + year=annee_sco + 1, + month=ScoDocSiteConfig.get_month_debut_annee_scolaire(), + day=1, ) - datetime.timedelta(days=1) except (TypeError, ValueError) as exc: raise ScoValueError("année scolaire invalide") from exc diff --git a/tests/unit/test_periode.py b/tests/unit/test_periode.py index f32d6ebe..490b1a5c 100644 --- a/tests/unit/test_periode.py +++ b/tests/unit/test_periode.py @@ -3,7 +3,7 @@ """Test Periodes Utiliser comme: - pytest tests/unit/test_periode.py + pytest tests/unit/_test_periode.py Calcule la session associée à un formsemestre sous la forme (année, période) année: première année de l'année scolaire @@ -32,8 +32,30 @@ import datetime from app.models import FormSemestre from app.scodoc.sco_formsemestre import sem_in_semestre_scolaire +# Nota: pour accélerer (éviter de recrer l'app), +# on regroupe ces petits tests dans une seule fonction -def test_default(): + +def test_periode(test_client): + "Joue tous les tests de ce module" + _test_default() + _test_automne_nord() + _test_noel_nord() + _test_ete_nord() + _test_printemps_sud() + _test_automne_sud() + _test_noel_sud() + _test_ete_sud() + _test_nouvel_an_sud() + _test_nouvel_an_special_pp_before_pa() + _test_nouvel_ete_pp_before_pa() + _test_automne_special_pp_before_pa() + _test_sem_in_periode1_default() + _test_sem_in_periode2_default() + _test_sem_in_annee_default() + + +def _test_default(): # with default assert (2021, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 1, 1)) assert (2021, 2) == FormSemestre.comp_periode( @@ -41,59 +63,59 @@ def test_default(): ) -def test_automne_nord(): +def _test_automne_nord(): assert (2022, 1) == FormSemestre.comp_periode(datetime.datetime(2022, 9, 1)) -def test_noel_nord(): +def _test_noel_nord(): assert (2022, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 12, 15)) -def test_ete_nord(): +def _test_ete_nord(): assert (2021, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 7, 30)) -def test_printemps_sud(): +def _test_printemps_sud(): assert (2022, 1) == FormSemestre.comp_periode( datetime.datetime(2022, 1, 1), 1, 1, 1, 8 ) -def test_automne_sud(): +def _test_automne_sud(): assert (2022, 2) == FormSemestre.comp_periode( datetime.datetime(2022, 8, 2), 1, 8, 1, 1 ) -def test_noel_sud(): +def _test_noel_sud(): assert (2022, 2) == FormSemestre.comp_periode( datetime.datetime(2022, 12, 30), 1, 8, 1, 1 ) -def test_ete_sud(): +def _test_ete_sud(): assert (2022, 1) == FormSemestre.comp_periode( datetime.datetime(2022, 7, 30), 1, 8, 1, 1 ) -def test_nouvel_an_sud(): +def _test_nouvel_an_sud(): assert (2021, 2) == FormSemestre.comp_periode( datetime.datetime(2022, 1, 1), 3, 8, 1, 1 ) -def test_nouvel_an_special_pp_before_pa(): +def _test_nouvel_an_special_pp_before_pa(): assert (2023, 1) == FormSemestre.comp_periode( datetime.datetime(2024, 1, 10), 8, 2, 1, 1 ) -def test_nouvel_ete_pp_before_pa(): +def _test_nouvel_ete_pp_before_pa(): assert (2023, 2) == FormSemestre.comp_periode(datetime.datetime(2024, 6, 1), 8, 2) -def test_automne_special_pp_before_pa(): +def _test_automne_special_pp_before_pa(): assert (2024, 1) == FormSemestre.comp_periode(datetime.datetime(2024, 9, 20), 8, 2) @@ -105,7 +127,7 @@ sem_next_year = {"date_debut_iso": "2023-08-16"} sem_prev_year = {"date_debut_iso": "2022-07-31"} -def test_sem_in_periode1_default(): +def _test_sem_in_periode1_default(): assert True is sem_in_semestre_scolaire(sem_automne, 2022, 1) assert False is sem_in_semestre_scolaire(sem_nouvel_an, 2022, 1) assert False is sem_in_semestre_scolaire(sem_printemps, 2022, 1) @@ -114,7 +136,7 @@ def test_sem_in_periode1_default(): assert False is sem_in_semestre_scolaire(sem_prev_year, 2022, 1) -def test_sem_in_periode2_default(): +def _test_sem_in_periode2_default(): assert False is sem_in_semestre_scolaire(sem_automne, 2022, 2) assert True is sem_in_semestre_scolaire(sem_nouvel_an, 2022, 2) assert True is sem_in_semestre_scolaire(sem_printemps, 2022, 2) @@ -123,7 +145,7 @@ def test_sem_in_periode2_default(): assert False is sem_in_semestre_scolaire(sem_prev_year, 2022, 1) -def test_sem_in_annee_default(): +def _test_sem_in_annee_default(): assert True is sem_in_semestre_scolaire(sem_automne, 2022, 0) assert True is sem_in_semestre_scolaire(sem_nouvel_an, 2022) assert True is sem_in_semestre_scolaire(sem_printemps, 2022, 0)